I've been using React for a bit now, and I've always updated an object's state using the spread operator as I remember reading you should directly change current state.
E.g now I write something like this:
const changeState = () => {
setState(prevState => {...prevState, existingKey: 'new value'})
}
But I've just come across a section "State Updates are Merged" in the react docs (https://reactjs.org/docs/state-and-lifecycle.html#state-updates-are-merged). If I'm understanding it correctly it's essentially saying I can just write this instead:
const changeState = () => {
setState({existingKey: 'new value'})
}
and all other keys in the object will remain as is, only the key I have specified will be updated in state. Am I reading this correctly? I've never seen this on stackoverflow, I always see people saying to use spread operators, is there a reason why?
The reason the functional state update and shallow copying is recommended is that is works for all use cases. The state-updates-are-merged link you reference actually applies to class-based component's this.setState method.
Enqueueing multiple updates within a single render cycle, loops, etc...
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
The net result here is only a single count + 1 update as each update overwrote the previous one.
this.setState(prevState => ({ count: prevState.count + 1 }));
this.setState(prevState => ({ count: prevState.count + 1 }));
this.setState(prevState => ({ count: prevState.count + 1 }));
The net result here is now count + 3.
Referencing the previous state in callbacks to avoid stale enclosures.
componentDidMount() {
setInterval(() => this.setState({ count: this.state.count + 1 }), 1000);
}
Here the initial count state is closed over in callback scope and will never update.
componentDidMount() {
setInterval(() => this.setState(prevState => { count: prevState.count + 1 }), 1000);
}
Only the root properties are merged, more deeply nested state being updated also needs to be shallow copied.
this.setState(prevState => ({
...prevState,
property: {
...prevState.property,
nestedProperty: 'new value',
},
}));
On the off-hand chance you are using the useState hook, state updates are not shallowly merged.
useState
Unlike the setState method found in class components, useState does not automatically merge update objects. You can replicate this behavior by combining the function updater form with object spread syntax:
const [state, setState] = useState({}); setState(prevState => { // Object.assign would also work return {...prevState, ...updatedValues}; });
If the next state depends on the previous state in any way, use a functional state update. Otherwise, the regular update is sufficient.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With