Why is react setState method immutable?
Asked Answered
C

5

11

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?

Cyr answered 15/9, 2017 at 6:15 Comment(0)
D
7

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

Drews answered 14/6, 2018 at 2:43 Comment(0)
D
2

This code is immutable, because slice() method is used. If you try:

someState = {squares: [1,2,3,4,5]}
squares = someState.squares.slice()

You'll get new array created by slice() method.

You can test it that way:

squares = someState.squares.slice()
squares2 = someState.squares
squares[0] = 9    // doesn't change someState
squares2[1] = 9   // changes someState
someState.squares // [1,9,3,4,5] - as I said

And if you have doubts about this.setState({squares: squares}); - yes, of course after running this you have new state, but in fact this state is not modified old state object, but new object created from old parts. So if you try:

oldState = this.state
this.setState({squares: squares})

You'll see that new state will differ than saved old:

this.state == oldState //false

In case of this.state.squares[i] = 'X'; oldState would be modified too and that is exactly what we call mutability. All copied parts of old state changes with it and that causes many problems.

Digitoxin answered 15/9, 2017 at 6:34 Comment(0)
M
1

You can do this, but you should not, the reason behind is that, if you use

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

It will be overridden with next

this.setState({squares: squares});

So, your app will not have accurate data.

From Doc:

Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.

Check more about this in https://facebook.github.io/react/docs/react-component.html#state

Marchant answered 15/9, 2017 at 6:20 Comment(0)
L
0

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.

Lochner answered 9/11, 2020 at 7:43 Comment(0)
U
0

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

Uproar answered 12/3, 2022 at 9:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.