Search code examples
reactjsimmutabilityuse-state

How can I prevent initial state array from being mutated?


In my container component I have a state that gets initialized with an object that I use as data.

I clone the state array to prevent the initial state from being mutated but it still gets mutated, which I don't want to happen since I will need to compare the current state with the initial state later on.

The who system is kept inside the CubeOfTruthSystem component

function CubeOfTruthSystem() {
  const [cubeIndex, setCubeIndex] = useState(0);
  const [faceIndex, setFaceIndex] = useState(0);

  return (
    <React.Fragment>
      <CubeSelector handleClick={(index) => setCubeIndex(index)} />
      <CubeContainer cubeIndex={cubeIndex} faceIndex={faceIndex} />
      <FaceSelector handleClick={(index) => setFaceIndex(index)} />
      <button id="reset-face" onClick={() => console.log(CubeOfTruth)}>
        Reset
      </button>
    </React.Fragment>
  );
}

The parent component for the state looks like this:

function CubeContainer({ cubeIndex, faceIndex }) {
  const [cube, setCube] = useState(CubeOfTruthData);

  const handleCellClick = (id, row) => {
    const cubeClone = [...cube];
    const item = cubeClone[cubeIndex].faces[faceIndex].data[0].find(
      (item) => item.id === id
    );

    item.state = "active";

    cubeClone[cubeIndex].faces[faceIndex].data = activateAdjacentCells(
      id,
      row,
      cubeClone[cubeIndex].faces[faceIndex].data,
      item
    );

    setCube(cubeClone);
  };

  return (
    <div id="cube-container">
      {cube[cubeIndex].faces[faceIndex].data.map((row) => {
        return row.map((item) => {
          return (
            <CubeItem item={item} handleClick={handleCellClick} key={item.id} />
          );
        });
      })}
    </div>
  );
}

And this is the child component

function CubeItem({ item, handleClick }) {
  const handleBgClass = (cellData) => {
    if (cellData.state === "inactive") {
      return cellData.bg + "-inactive";
    } else if (cellData.state === "semi-active") {
      return cellData.bg + "-semi-active";
    } else {
      return cellData.bg;
    }
  };

  return (
    <button
      className={`cell-item ${handleBgClass(item)}`}
      disabled={item.state === "inactive" ? true : false}
      onClick={() => handleClick(item.id, item.row)}
    />
  );
}

In the CubeOfTruth component, I'm trying to get the initial state (which is the CubeOfTruth array), but after changing the state, cube, cubeClone and CubeOfTruth all have the same values. How can I make sure CubeOfTruth never gets mutated?


Solution

  • You're trying to clone cube array but you're making a shallow copy of it. If you want to prevent mutation of nested properties you should make a deep copy instead.

    Replace this:

    const cubeClone = [...cube];
    

    With this:

    const cubeClone = JSON.parse(JSON.stringify(cube));
    

    Or use some library like lodash

    const cubeClone = _.cloneDeep(cube)