Do you want to request a feature or report a bug?
No
What is the current behavior?
setState's behaviour
If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via https://jsfiddle.net or similar (template: https://jsfiddle.net/reactjs/69z2wepo/).
Not a bug, But it is raising issue among developers and may create bugs.
What is the expected behavior?
Explanation
Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
Latest version.
Simple example.
We are setting initial state in the constructor.
setState({
questions: {
"1": {display: "none"}
}
})
Later we are updating like this:
var questions = this.state.questions
questions["1"].display = "block"
this.setState({ questions: questions })
Why is this approach bad? Or not bad? Can anybody explain with examples? What approach we take can lead to ugly codes.
It's technically supported but makes it harder to have good performance in larger apps because you can't referentially compare data structure to bail out of updates anymore. There are some explanations about this in Optimizing Performance.
Also, future versions of React will be able to delay certain updates, split work into chunks to keep the UI responsive, and prioritize the updates more efficiently. Your app wouldn't be able to take advantage of that if you directly mutate the data.
To elaborate on @gaearon's initial point: certain lifecycle methods like shouldComponentUpdate and componentWillUpdate take a nextState parameter. If you are mutating this.state then you would be unable to compare the previous and current values, limiting what you can do in those lifecycle methods.
Here's an example that implements a counter. It has a really basic shouldComponentUpdate implementation that is meant to ensure the component only updates when this.state.count has updated. Seems reasonable.
increment() {
this.state.count += 1;
this.setState({ count: this.state.count });
}
shouldComponentUpdate(nextProps, nextState) {
return nextState.count !== this.state.count;
}
But this example mutates this.state which means the shouldComponentUpdate check cannot tell if state has been updated since this.state.count and nextState.count are equal. So the component will never update.
Here's an updated example that does the same thing, except it doesn't mutate this.state.
increment() {
this.setState({ count: this.state.count + 1});
}
You can see that the component does update as expected!
So to summarize: a number of public APIs assume this.state will not be mutated, allowing you to compare previous and current state and make decisions based off whether they differ. Mutating state makes that impossible.
Thanks for comments!
For complex nested state objects, updating whole structure can cause performance issue.
var tasks = [ { user: {
name: "",
comments: [
{ text: "1", value: "1"},
{ text: "2", value: "2"},
{ text: "3", value: "3"}
] }
} ]
In the state we have state => { tasks: tasks };
If I don't want to mutate the state, then I have to create new array, loop users, loop comments just to set one comment's value.
Yes maybe I can use React.addons.update to set, merge, or push but, it will be hard to consume as a widget.
One common solution to this is to normalize the data. For example, you can have shallow usersByID, commentsByID, etc. This way, changing a single comment wouldn't cause the user map to be copied.
Alternatively you can use a library providing persistent data structures like Immutable. It allows you to write code with efficient deep changes. We use it pretty extensively at Facebook for this exact purpose.
@ulugbekov I've found that the Redux documentation on normalizing state is pretty good and recommend it if you're looking for more information on the topic! Even if you're not using Redux, it gives a good overview of how to normalize state in general.
Also, since this is a usage question and we use this issue tracker only for bugs and feature requests, I'm going to close this out, but feel free to ask any clarifying questions 馃憤
@gaearon, I do not think mutating state directly just a performance or coding practice issue, I can really cause an error. i.e. In React chrome developer tool, I see the mutated state, but the component still renders the previous state, I definitely invoked setState after mutating the state.I error can not happen consistently, just sometimes but I can not see any pattern
I think it is related to how React work internally. If that is the case, I suppose in the documentation provide the big error to prevent from mutating state loudly rather than saying it is not a good practice
Most helpful comment
One common solution to this is to normalize the data. For example, you can have shallow
usersByID,commentsByID, etc. This way, changing a single comment wouldn't cause the user map to be copied.Alternatively you can use a library providing persistent data structures like Immutable. It allows you to write code with efficient deep changes. We use it pretty extensively at Facebook for this exact purpose.