javascriptreactjsdeep-copyspread-syntax

React State modification despite the fact I used spread operator to avoid direct modification of it


I tried to use spread operator to avoid modifications of my state without the use the set State. But despite that, my state is still modified.

I can eventually make a deepCopy of my state but I don't really understand why it is needed here.

Thanks in advance for your help !

const resultTypeIdsNeedsCheckAll = [2,3]
  const selectResultType = (resultType) => {
    setResultType(resultType);
    const selectedStatParamsCopy = {...selectedStatParams}
    // Check if resultType.id is in the array resultTypeIdsNeedsCheckAll
    if (resultTypeIdsNeedsCheckAll.includes(resultType.id)) {

        selectedStatParamsCopy.parentSuggestions.slice(0, selectedStatParamsCopy.parentSuggestions.length - 1).map(parentSugg => {
          parentSugg.childSuggestions.map(childSugg => childSugg.isActiveCheckBox = 1)
          parentSugg.isAbsenceOfPatientInfoChecked = 1
        })

      }
    }

Solution

  • If your goal here is to update state with a new copy of state, or if your goal is to create some copy of state for some other purpose, in either case what you shouldn't do is directly mutate state. Which is exactly what these operations do:

    childSugg.isActiveCheckBox = 1
    

    and:

    parentSugg.isAbsenceOfPatientInfoChecked = 1
    

    With the caveat that I don't know your data model or what operation you're trying to perform, but it's not clear what that .slice() operation is even trying to do. What is clear however is that you want to make deeper and deeper copies of any object you want to modify.

    A common pattern to do that is nested spread operators and .map() operations, at each level where a copy is needed. For example, consider this:

    const newStateValue = {
      ...selectedStatParams,
      parentSuggestions: selectedStatParams.parentSuggestions.map(parentSugg => {
        return {
          ...parentSugg,
          isAbsenceOfPatientInfoChecked: 1,
          childSuggestions: parentSugg.childSuggestions.map(childSugg => {
            return {
              ...childSugg,
              isActiveCheckBox: 1
            };
          })
        };
      })
    }
    

    So, starting at the top level of the selectedStatParams object (which I'm assuming is an object stored in state that you want to update):

    As you can see, it's just the same simple pattern repeated at each level of nesting. The spread operator is used to simply say "copy the rest of the properties that this operation doesn't need to modify". And .map() is used to project any array into a new array in which that pattern is repeated.

    So at no point are you assigning a value to an existing property, but instead always creating new objects in which you can set those property values to whatever you need.