mc9
mc9

Reputation: 6349

React setState based on the current state

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

Answers (4)

Justin Khoo
Justin Khoo

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

Tibos
Tibos

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

clint
clint

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

Vishal Arora
Vishal Arora

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

Related Questions