Search code examples
reactjsstateimmutabilitysetstate

Does React create a deep or shallow copy of prevState internally?


I'm just starting with programming and have recently written my first app in React, and while it does work, I'm not sure if I'm handling my internal state correctly. My question is whether the setState method creates an immutable deep copy of the "prevState" or not necessarily? To show an example:

 menuAddRecHandler = () => {
    this.setState((prevState) => {
      const updatedState = {...prevState};
      const dayInd = updatedState.menu.recipes.findIndex(item => item.dayNum === updatedState.menuInternal.currentDay);
      updatedState.menu.recipes[dayInd].recList.push({
        name: prevState.menuInternal.menuRecSelect,
        portions: prevState.menuInternal.menuNumPortInput
      });
      return updatedState;
    })
  }

In my handler I am pushing an object to a nested array of the updatedState object which was copied over from prevState by the spread operator. Now I know that the spread operator makes only a shallow copy, but is the prevState supplied internally by the setState method also shallow, and does that mean that I am actually mutating my state directly by invoking this handler? If so, how do I fix this issue? Thank you.


Solution

  • From the docs:

    The first argument is an updater function with the signature:

    (state, props) => stateChange

    state is a reference to the component state at the time the change is being applied. It should not be directly mutated. Instead, changes should be represented by building a new object based on the input from state and props.

    How can you achieve the same result without mutating?

    menuAddRecHandler = () => {
      this.setState((prevState) => {
        return {
          ...prevState,
          menu: {
            ...prevState.menu,
            recipes: prevState.menu.recipes.map((item) => {
              if(item.dayNum === prevState.menuInternal.currentDay) {
                return {
                  ...item,
                  recList: [
                    ...item.recList,
                    {
                      name: prevState.menuInternal.menuRecSelect,
                      portions: prevState.menuInternal.menuNumPortInput
                    }
                  ]
                }
              }
              return item;
            })
          }
        }
      });
    }
    

    This is obviously very error prone, the idea is to have less deeper states. The docs for Redux offer some tips for normalizing state.

    You can also look at immutable state libraries like ImmutableJS or Immer