Reputation: 6349
I would like to know which form of setState
to use if I want to update the state based on the current state.
React's setState
takes either an object or a function as argument. As I understand, the following does not work as intended because React batches setState
state = {score : 0};
increaseScoreBy2 () {
this.setState({score : this.state.score + 1});
this.setState({score : this.state.score + 1});
}
and it is better to use
increaseScoreBy2 () {
this.setState(prevState => { return { score: prevState.score + 2 }}});
}
But If all I want to do is to update state one time, which form of setState
should be used? Is it a matter of personal preference? Or should we always use functional form when updating state based on the current state to avoid any possible unexpected behavior?
For instance, is there anything wrong with the following code?
const newRegions = this.state.regions.map(...)
this.setState({ regions: newRegions });
Upvotes: 5
Views: 2887
Reputation: 3251
If you only set it one time it doesn't matter. But here's a super quick way to see why updater functions are preferred when you depend on the existing state when updating the state.
function incrementCounter1(){
this.setState({ counter: this.state.counter + 1 })
}
function incrementCounter2(){
this.setState(prevState => {
return {counter: prevState.counter + 1 }
})
}
Lets say the execution of setState()
is delayed by 1 second.
Within that 1 second you call incrementCounter1()
3 times.
Each time this.state.counter
is still 0, and setState()
receives {counter: 1}
.
Hence, the final value of counter is 1 - which is wrong.
Now if within 1 second you call incrementCounter2()
3 times, the function within setState()
is not invoked until React is ready to process the state change and previous state changes have been executed. Therefor, the prevState
object will always have the latest state. Hence the final value of counter is 3 - which is correct!
Upvotes: 0
Reputation: 27823
There's nothing wrong with calling setState
multiple times with object updaters if the updates are not conflicting:
// works
setState({score: this.state.score + 1})
setState({players: [...this.state.players, 'Jack']})
If the updates are conflicting, the previous state should not be taken from this.state
, but rather setState
will supply it to you in the function updater:
// works
setState((prevState)=> ({score: prevState + 1}));
setState((prevState)=> ({score: prevState + 1}));
Both of these work fine.
What's wrong is just calling setState
with the object updater if you depend on the current state, even if it's just one call:
// does not work right
setState({
score: this.state.score + 1,
victory: this.state.score === 5 // this will be true one "turn/goal" later than expected
});
Upvotes: 0
Reputation: 14524
As you have said, yes, the general rule of thumb is "if the next state depends on the previous state, use an updater function."
is there anything wrong with [
this.setState({ regions: this.state.regions.map(...) })
]?
I think it depends on what's in the regions
array and what you pass to map()
. Maybe the best way to answer that question is to ask yourself if a batched update would cause problems:
const mapFcn = ...
const newState = Object.assign(
{},
{ regions: this.state.regions.map(mapFcn) },
{ regions: this.state.regions.map(mapFcn) },
);
In some cases this may not be a problem. If each region is a string, mapFcn = (region) => region.toUpperCase()
probably wouldn't cause any issues.
If regions
is an array of objects and mapFcn
does something like the "classic" increment example ((region) => ({ ...region, count: region.count + 1 })
), that might be a problem.
Upvotes: 2
Reputation: 543
The state takes some time to be updated and it is not a good practice to perform an action on the state immediately after setting it. This makes reading this.state right after calling setState() a potential pitfall. If at all it is required, you can use the callback function that is called once the state is successfully updated. Here is the link to the documentation: https://reactjs.org/docs/react-component.html#setstate
Upvotes: 0