0

Using hooks to update state based on the previous state value, I don't understand why modifying the existing object and passing that to setState() is bad. I know this won't cause a re-render since the state still points to the same reference, but beyond that, what is the issue? I don't understand how cloning the array, modifying that, then passing it to setState() fixes some unknown issue.

const [bigArr, setBigArr] = setState(Array(SOME_BIG_NUMBER).fill(false));
// (1) This seems to work, but is bad for some reason.  But why?
bigArr[325] = true;
setBigArr(bigArr);
// (2) This is preferable for some reason. Why?
bigArrCopy = bigArr.slice();
bigArrCopy[325] = true;
setBigArr(bigArrCopy);
// (3) Is this OK?  Why/Why not?
setBigArr(bigArrCopy => {
    bigArrCopy[325] = true;
    return bigArrCopy;
});
Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
Jo Regg
  • 31
  • 5

3 Answers3

1

I know this won't cause a re-render since the state still points to the same reference, but beyond that, what is the issue?

Is that not enough? The reason to set state is because you want the component to rerender. If you're trying to get it to rerender and it doesn't, that's a pretty serious bug.

The underlying reason why react went with a model of immutable state is that it makes it very simple to tell whether state changed. Do a quick === between two states, and you immediately know whether it has changed. If you mutate your state, this feature is lost, and any code that depends on it breaks.

Nicholas Tower
  • 72,740
  • 7
  • 86
  • 98
0

The first case will not work as expected, it will not re-render the component because React use shallow comparison which means that it will compare location of object and array, if the location not change React will not trigger re-render

// (1) This will not re-render the component, cuz the location of bigArr are not changed
bigArr[325] = true;
setBigArr(bigArr);

Happen the same with third case

You may say you can fix it by call setBigArr([...bigArr]);. But there is still problems

This is an simple situation you get unexpected result

import React, { useState } from "react";
import "./styles.css";

export default function App() {
  const [arr, setArr] = useState([1]);

  const handleClick = () => {
    createTimeout()

    // Case1: directly set state 
    arr[0]= 2
    setArr(arr);

    // Case1: Not directly set state 
    // const newArr = [...arr]
    // newArr[0]= 2
    // setArr(newArr);
  };

  const createTimeout = () =>{
    setTimeout(() =>{
      // You expect number 1 here because you call this before update state
      console.log(arr[0])
    },2000)
  }
  return (
    <div className="App">
      <h1>{arr[0]}</h1>
      <div onClick={handleClick}>change</div>
    </div>
  );
}

We call createTimeout before setState so we will expect number 1 will be logged but: Case 1: you will get number 2 because you mutated original array
Case 2: you will get number 1 (expected)

Check out the codesandbox

Tony Nguyen
  • 3,298
  • 11
  • 19
0

React checks to see if the bigArrState !== prevBigArrState before re-rendering. It does not check the contents of the array. It checks if it's the same instance. In your first example that will result in false, and the component will not re-render. When you use bigArr.slice(), you are creating an entire new array therefore bigArrState !== prevBigArrState results in true, allowing the component to re-render.

Your last example will causes issues because the updater func does not get passed bigArrCopy but rather bigArrState (same instance).

https://reactjs.org/docs/react-component.html#setstate

Rather than creating and storing an entire clone in memory you can do the following:

setBigArr([
  ...bigArr.slice(0, 325),
  true,
  ...bigArr.slice(326),
]);
GitGitBoom
  • 1,822
  • 4
  • 5