Mykybo
Mykybo

Reputation: 1499

Mutating state from React's useState hook

Is it, and why is it a bad idea to mutate a state from React's new useState hook? I found no info on the topic.

Consider following code:

const [values, setValues] = useState({})

// doSomething can be called once, or multiple times per render

const doSomething = (name, value) => {
  values[name] = value
  setValues({ ...values })
}

Note the mutation of values. Since doSomething can be called more than once per render, doing this would not work because of the async properties of setState:

const doSomething = (name, value) => {
  setValues({ ...values, [name]: value })
}

Is the approach of mutating values directly the correct one in this case?

Upvotes: 20

Views: 12585

Answers (2)

Erez Cohen
Erez Cohen

Reputation: 1517

Basically I would avoid mutating state in such way simply for the sake of purity.

However, I would argue that in this case it is perfectly fine. When you mutate a value in an inner level in the state it goes unnoticed by React. Only when calling setValues() with a new reference React notes to itself that a new render is pending.

const { useState } = React;

function App() {
  const [values, setValues] = useState({ num: 0 });

  const handleClick = () => {
    doSomething();
    doSomething();
  }
  
  const doSomething = () =>
    setValues((values) => {
      values.num += 1;
      return { ...values };
    });

return (
  <div onClick={handleClick}>
    {JSON.stringify(values)}
  </div>
);
}

ReactDOM.render( < App / > , document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>

(If anyone can provide a counter example I would love to see it).

Upvotes: 2

Tholle
Tholle

Reputation: 112777

You should never mutate state directly as it might not even cause a re-render if you update the state with the same object reference.

const { useState } = React;

function App() {
  const [values, setValues] = useState({});

  const doSomething = (name, value) => {
    values[name] = value;
    setValues(values);
  };

  return (
    <div onClick={() => doSomething(Math.random(), Math.random())}>
      {JSON.stringify(values)}
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>

You can give a function as first argument to setValues just like you are used to in the class component setState, and that function in turn will get the correct state as argument, and what is returned will be the new state.

const doSomething = (name, value) => {
  setValues(values => ({ ...values, [name]: value }))
}

const { useState } = React;

function App() {
  const [values, setValues] = useState({});

  const doSomething = (name, value) => {
    setValues(values => ({ ...values, [name]: value }));
  };

  return (
    <div onClick={() => doSomething(Math.random(), Math.random())}>
      {JSON.stringify(values)}
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>

Upvotes: 19

Related Questions