Background
I've been playing around with React and made a simple todo list app to explore some concepts. While testing my code, I realized that returning a new state from setState()
doesn't seem to be necessary when mutating/updating a state stored collection.
According to several other StackOverflow questions and the React documentation, the code I wrote should either not work, or produce unexpected results. However, in all of my testing, the code does seem to work and is more readable and concise than the suggested methods for mutating collections.
Why does my code work, and what, if any, is the danger of using it as a pattern?
I've created two versions of my app. One which uses a method for updating collections as mentioned in this question, and another which has setState()
return an empty object instead. I've uploaded full versions of the app to CodePen so you can see that they behave identically.
- original
- empty return
- A third version simplifies the state mutation even more and also seems to work.
Code Segments
Here is the key code segment from the original. I'm using a callback for setState() as a way to guarantee that the state read and state write happen automatically (in case of asynchronous state changes between calls).
addTask(taskText) {
this.setState(prevState => {
const updatedTasks = prevState.tasks;
updatedTasks.set(prevState.uid, taskText); // use the original, un-incremented uid
return {
uid: ++prevState.uid, // increment prev uid and store it as new state
tasks: updatedTasks, // set tasks to updated tasks
}
}, this.logState);
}
However, it seems that returning the actual changed state is unnecessary for collections (or any other reference-value object). As the following code also works. The only differences are that I hoisted the uid to a class property and didn't bother to return the updated map.
addTask(taskText) {
this.setState(prevState => {
const updatedTasks = prevState.tasks;
updatedTasks.set(this.uid, taskText); // use the original, un-incremented uid
this.uid++;
return {
// return an empty object
//uid: ++prevState.uid, // update uid to one more than prev uid
//tasks: updatedTasks, // set tasks to updated tasks
}
}, this.logState);
}
If I don't care about wrapping the mutation inside of an arrow function as an asynchrounous guard, then the method can be simplified even further:
addTask(taskText) {
this.state.tasks.set(this.uid, taskText); // use the original, un-incremented uid
this.uid++;
this.setState({});
}
It seems that the only thing that really matters is the setState()
call itself. My initial understanding was that React was pretty efficient, and onyl checks for state changes that it's told about. However, the last example seems to indicate setState()
causes React to examine all of its known state for any changes (regardless of how they got there), and then update/render accordingly.
Changes to primitives would still need to be wrapped inside the returned setState() object, but for objects (or at least Maps), what is the downside to eliminating the complexity and just using a naked setState() call?
Reference
I'm using the following:
- React and ReactDOM v16.2.0 (15.1.0 on the CodePens)
- Babel 6.26.0 (with stage-2 support for class properties)
- ECMAScript 6