Why is react setState method immutable?

Viewed 10078

Following comes from React tutorial:

const squares = this.state.squares.slice();
squares[i] = 'X';
this.setState({squares: squares});

This code changes copied state.squares and assign it to orginal state.squares. Finally this changes original state.squares, so I think this is not different than mutable code like following:

this.state.squares[i] = 'X';

Is there some difference?

5 Answers

I do wonder about this question too. But I find that the answers were unsatisfactory. So here is my take

The answer is actually written in the doc itself http://reactjs.org/docs/state-and-lifecycle.html#do-not-modify-state-directly

so first reason, setState() triggers render()

second reason, state changing is asynchronous in React, so you might have some other components changing state behind the scene while you are still referring to the old unchanged state, which result in wrong state values

Do not mutate the state directly, that's what the doc said.

I was coding a todo list and make the same mistake of mutating the state directly, but in my case I was using hooks. I didn't understand why the screen does not re-render when I run setState. The state did mutate (confirmed by console.log) and even useEffect ran because it detects an updated dependency.

React class that extends Purecomponent has the same behaviour too.The funny thing is, if I were to use class that extends React.Component and use this.setState function the app does rerender the screen.

After I ask and learn, it turns out I need to treat states as immutable.

This code : var newwrongArr = this.state.arr; Does not copy anything, it only refers the value, the proof is that if you mutate newwrongArr the state will also mutate.

If we want to copy, the code should be :

var newArr = [...this.state.arr]; //the three dots are spread operator

or some other function like Object.assign().

My conclusion is, if we mutate newwrongArr and setStatearr(newwrongArr), I think React hooks will decide that rerender is unnecessary because it consider newwrongArr to be the same as the value of state (although the state changes, the rerender does not happen). But if we were to copy the value with spread operator, then setState will consider rerender necessary which is the expected result.

Sorry for the long answer.

Agree with @Karol Selak, I want to make thing more clearer with an example here:

const squares = this.state.squares.slice();
squares[i] = 'X';
this.setState({squares: squares});

This code will do the following steps:

  • Create a new squares variable that is placed in memory, for example, at 12345
  • Set item at index i to 'X' (at this point, old squares and new squares also got the change because the object in the array is a reference)

When we use, for example the useEffect hook as follows:

useEffect(() => {
   // do something
}[state.squares])

React will compare old squares and new squares, in this case, 2 these are different because the address in memory is different. Then the useEffect will run.

In the second case:

this.state.squares[i] = 'X';

The useEffect will not work

Related