score:2

Accepted answer

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.

score:0

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

score:0

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
});

score:0

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!


Related Query

More Query from same tag