I'm trying to use a stateful React component with ES6 but when I define a constructor the constructor will only be called once while the component is rendered multiple times (from its parent). Example shown below.
class SubComponent extends React.Component {
constructor(props) {
super(props);
console.log("Creating sub component");
this.state = { count: props.count };
}
render() {
console.log("Rendering sub component", this.state.count);
return (<div>count: {this.state.count}</div>);
}
}
class App extends React.Component {
constructor(props) {
super(props);
console.log("Creating app");
this.state = { count: 0 };
this.tick = this.tick.bind(this);
setInterval(this.tick, 1000);
}
tick() {
this.setState({ count: this.state.count + 1 });
}
render() {
console.log("Rendering app", this.state.count);
return (<SubComponent count={this.state.count} />);
}
}
This will not update the rendered output (it will always be count: 0) but the logs will output:
Creating app
Rendering app 0
Creating sub component
Rendering sub component 0
Rendering app 1
Rendering sub component 0
Rendering app 2
Rendering sub component 0
Rendering app 3
Rendering sub component 0
...
Here's a JSFiddle: http://jsfiddle.net/jor0xu1a/1/
I'm aware that the example SubComponent doesn't need a state but I tried making it as simple as possible to show my problem.
What am I missing?
In SubComponent it is props not state - change it to this.props.count and this will work
I recommend to read Props in getInitialState Is an Anti-Pattern.
Basically, as few components as possible should have state. As the other answers already said, in your case you can just use this.props.count to refer to the current value. There doesn't seem to be any reason why SubComponent should have its own state.
However, if you really want to compute the component's state from the props it receives, it is your responsibility to keep them in sync, with the life cycle method componentWillReceiveProps:
componentWillReceiveProps(nextProps) {
this.setState({count: nextProps.count});
}
You SubComponent should be:
class SubComponent extends React.Component {
constructor(props) {
super(props);
console.log("Creating sub component");
}
render() {
return (<div>count: {this.props.count}</div>);
}
}
My bad, I thought that the constructor (or getInitialState for ES5) is called whenever the component is being re-rendered by the parent (I thought that the parent 're-creates' its children on render) but that's not always the case. I should had read up on it (url) and tried it with ES5 (jsFiddle) before thinking it was something I didn't understand with ES6 and creating a question here.
And yes, the example SubComponent should use this.props but my use case had actual stateful functionality in my real component. I created the example as I thought for some reason that the result weren't the expected outcome when using ES6 (but it was).
Thank you for you feedback!
Related
I am working on a user interface in React.
I am currently facing an issue which I do not really know how to debug.
Somehow the state of a component changes unexpectedly. There is no code on my part which changes the state variable however.
The mutation happens when pressing a button, but I have literally written no code which modifies this state variable. Is there a way in React to see who/which functions made changes to the state? How would one debug this kind of scenario?
You've referred to a "state variable" in your question, but the only code shown in your question uses props, not state.
Props are controlled by the parent element, which can change them even after the component has been created. Here's an example:
class Example extends React.Component {
constructor(props) {
super(props);
console.log("constructor");
}
render() {
console.log("render");
return <div>{this.props.childprop}</div>;
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {value: 0};
}
componentDidMount() {
const handle = setInterval(() => {
this.setState(({value}) => ({value: value + 1}));
}, 500);
this.cleanup = () => {
clearInterval(handle);
};
setTimeout(this.cleanup, 2000);
}
componentWillUnmount() {
if (this.cleanup) {
this.cleanup();
}
}
render() {
const {value} = this.state;
return <Example childprop={value} />;
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
Notice how App's code changes the value of the childProp in Example, after the Example component has been created. Doing so makes the component re-render.
So code in an event handler can easily see an updated prop value, if the parent has changed it.
Ciao, a way to intercept if a component is receiving props update is componentWillReceiveProps() function (now is called UNSAFE_componentWillReceiveProps()). A way to intercept if a component is chaning his state is componentWillUpdate() function (now is called UNSAFE_componentWillUpdate()), Try to use these to debug your application (maybe you will see some unexpected behaviour that could help to understad the cause of your problem).
We have created a re-usable list component using ReactJS. However, due to performance issues, we decided to implement - shouldComponentUpdate method with the conditions on when should my list component render
public shouldComponentUpdate(nextProps: TreeItemInternalProps, nextState: {}): boolean {
if (this.props.treeObject.selected !== nextProps.treeObject.selected) {
return true;
}
if (this.props.treeObject.expanded !== nextProps.treeObject.expanded) {
return true;
}
return false;
}
Meaning, I wanted to render my component only when the value of the checkbox of list item changes.
Let's say, due to some reason, I can't do this anymore. Now, my alternatives are using PureComponent which does a shallow compare. So, it should render only the list item which changed. However, even using after using the PureComponent, the entire component is getting rendered.
To explain the scenario properly, see the following screenshots -
Without using PureComponent / using the conditional check in shouldComponentUpdate
Here, you can see the log "component is rendered" was called only once, meaning, only for the change I did in the prop (this is the same requirement, I want to implement using PureComponent)
With using PureComponent
Here, you can see the log "component is rendered" was called 3 times, even though I had changed just the props of first list item. This is using PureComponent
PureComponent is always rendering for you because most likely your Props or State contain ANY objects or arrays that are being created in the parent component during the parent's re renders.
As others of said, it does a shallow comparison for re-renders (meaning an object reference won't equal a NEW object reference, even if they are DEEP equal), so the parent component is acting as a bottleneck to it's PureComponent child, as every re-render recreates a whole new object reference
If you REALLY wanted to use a PureComponent, you would have to simplify all the other props in your component to be primitives or objects/arrays that do not get a new reference on every re render of the parent
Example of problem:
class Todo extends PureComponent {
render() {
return <div>this.props.foo</div>;
}
}
class MyTodoList extends Component {
render () {
const fooBar = {foo: 'bar'}
return <Todo fooBar={fooBar} />
}
}
Example of fix:
class Todo extends PureComponent {
render() {
return <div>this.props.foo</div>;
}
}
class MyTodoList extends Component {
render () {
return <Todo fooBar={props.fooBar} />
}
}
Probably the best thing you want to do is bring up that object creation as high as you can to a component that does NOT re render on these kind of changes, or simplify every single prop to a non-object.
React.PureComponent does the shallow comparison on their props and states.
So changing the object property of the state or props won't re-render the component.
We need to reconstruct the state or props only when the state or props in interests is changed.
For example, we can write the code as below to re-render only when forceRender flag is set.
class ShalloWCompareComponent extends React.PureComponent {
constructor() {
super();
this.state = {
userArray: [1, 2, 3, 4, 5]
}
}
update() {
const { forceRender } = this.props;
const { userArray } = this.state;
if (forceRender) {
this.setState({
userArray: [...userArray, 6]
});
} else {
this.setState({
userArray: userArray.push(6)
});
}
}
render() {
return <b>Array Length is: {this.state.userArray.length}</b>
}
}
If this component receives the props of forceRender={false}, calling update() wouldn't re-render the component thanks to the shallow comparison of the React.PureComponent.
class RaisablePaper extends Component {
constructor(props) {
super();
this.state = {
state1: "state1",
openNow: props.boxOpen,
};
}
}
I am trying to send value to this class by doing <RaisablePaper boxOpen={this.state.dOpen}/>. But whenever the dOpen gets changed it does not seem to update the openNow. Help would very much appreciated.
You are setting the state before mounting the component in the constructor, which will not be fired again when the props change. For that you can use React's componentWillReceiveProps, which will be called when new props are sent to the component.
class RaisablePaper extends Component {
constructor(props) {
super();
this.state = {
state1: "state1",
openNow: props.boxOpen
};
}
componentWillReceiveProps(props) {
this.setState({
openNow: props.boxOpen
});
}
}
It would be simpler to use the props directly instead of worrying about syncing it to your state. It's a good idea in general to rely on props as much as possible, and only involve state when absolutely necessary.
But Fabian Schultz is absolutely right -- your constructor only runs once, before the component is mounted, so you'll never receive the subsequent updates if the component is relying on state which is initialized during construction.
I'm just imagining how you're using the boxOpen state to show an example; you can follow the same general idea with whatever your render method is doing.
class RaisablePaper extends Component {
render() {
return (
<div className={this.props.boxOpen ? 'is-open' : ''}>
Here's some content...
</div>
);
}
}
I'm fairly new to react and struggle to update a custom component using componentDidMount and setState, which seems to be the recommended way of doing it. Below an example (includes an axios API call to get the data):
import React from 'react';
import {MyComponent} from 'my_component';
import axios from 'axios';
export default class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
GetData() {
return axios.get('http://localhost:5000/<route>');
}
componentDidMount() {
this.GetData().then(
(resp) => {
this.setState(
{data: resp.data}
)
}
)
}
render() {
return (
<MyComponent data={this.state.data} />
);
}
}
Doing console.log(this.state.data) just below render() shows that this.state.data does indeed get updated (from [] to whatever the API returns). However, the problem appears to be that MyComponent isn't rendered afresh by componentDidMount. From the Facebook react docs:
Setting state in this method will trigger a re-rendering.
This does not seem to be the case here: The constructor of MyComponent only gets called once (where this.props.data = []) and the component does not get rendered again. I'd be great if someone could explain why this is and whether there's a solution or a different way altogether to get the updating done.
UPDATE
I've added the code for MyComponent (minus some irrelevant features, as indicated by ...). console.log(data_array) prints an empty array.
import React from 'react';
class DataWrapper {
constructor(data) {
this._data = data;
}
getSize() {
return this._data.length;
}
...
}
export class MyComponent extends React.Component {
constructor(props) {
super(props);
this._dataWrapper = new DataWrapper(this.props.data);
this.state = {
data_array: this._dataWrapper,
};
}
render() {
var {data_array} = this.state;
console.log(data_array);
return (
...
);
}
}
You are falling victim to this antipattern.
In MyComponent constructor, which only gets called the first time it mounts, passed your empty array through new DataWrapper and now you have some local state which will never be updated no matter what your parent does.
It's always better to have one source of truth, just one state object anywhere (especially for things like ajax responses), and pass those around via props. In fact this way, you can even write MyComponent as a simple function, instead of a class.
class Example extends Component {
state = { data: [] }
GetData() { .. }
componentDidMount() {
this.GetData().then(res =>
this.setState({data: new DataWrapper(res.data)})
)
}
render() { return <MyComponent data={this.state.data} /> }
}
...
function MyComponent (props) {
// props.data will update when your parent calls setState
// you can also call DataWrapper here if you need MyComponent specific wrapper
return (
<div>..</div>
)
}
In other words what azium is saying, is that you need to turn your receiving component into a controlled one. Meaning, it shouldn't have state at all. Use the props directly.
Yes, even turn it into a functional component. This helps you maintain in your mind that functional components generally don't have state (it's possible to put state in them but ... seperation of concerns).
If you need to edit state from that controlled component, provide the functions through props and define the functions in the "master" component. So the master component simply lends control to the children. They want anything they talk to the parent.
I'm not posting code here since the ammendment you need to make is negligible. Where you have this.state in the controlled component, change to this.props.
This may seem as a bit of a redundant question but I'm trying to access the final state of a child in React, after it has updated. I've been looking into the React LifeCycle docs (I think that might be the issue, but not sure), searched high and low, and can't quite figure it out.
I've got a component which needs to access the (final) value of the state of a child, once that child has done some updating (AJAX request which then does a few this.setStates).
So far, I'm able to access the entire state of that child, accessing through a ref (Inside componentDidMount), but when I try to access a specific value of said state, it returns null or undefined.
Here's some example code to explain (I'll try to spare you as much useless code as possible):
class Layout extends Component {
constructor(props) {
super(props);
}
componentDidMount(){
// This gives me the updated State where pageTitle = "Whatever"
console.log(this.refs.child1);
// However this gives me the initial State where pageTitle = null
console.log(this.refs.child1.state.pageTitle);
}
render(){
return (<div>
{React.cloneElement(
this.props.children,
{ref: 'child1'}
)}
</div>);
}
}
And here's the child component for reference (note: i'm using axios for my ajax requests):
export class ChildComponent extends Component {
constructor(props) {
super(props);
this.state = {
resultData: result,
pageTitle: null
}
}
componentDidMount(){
this.serverRequest = axios.get(apiUrl)
.then(function(result){
this.setState({
resultData: result,
pageTitle: result.pageTitle
});
}.bind(this))
}
render(){
return(<div>
{use of different this.state.resultData values works fine here}
</div>)
}
}
Appreciate any help that comes this way
To use a callback, add this code to the parent element:
handleAsyncDone(data) {
// Do whatever it is people do with data
}
And then pass that function to the child component, and in the childcomponent, add
this.props.handleAsyncDone(this.state);
Which will pass the child state back up to the parent.