Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does React update when directly mutating state?

In the example below I am using an ES6 Map as a state value in React:

class App extends React.Component {
  constructor(props) {
    super(props);
    const results = new Map();
    results["group1"] = [{ value: "..." }, { value: "..." }];
    this.state = { results };
  }

  onUpdateClick(i) {
    this.state.results["group1"][i].value = i;
    this.setState({});
  }

  onResetClick(i) {
    this.state.results["group1"][i].value = "...";
    this.setState({});
  }

  render() {
    const { results } = this.state;
    return (
      <div>
        {results["group1"].map((r, i) => (
          <div>
            {r.value}&nbsp;
            <button onClick={e => this.onUpdateClick(i)}>update</button>
            <button onClick={e => this.onResetClick(i)}>reset</button>
          </div>
        ))}
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("container"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id='container'></div>

When you click the button, I directly update the Map, in place and then call setState with no arguments. I do not make a clone/deep copy of the map. Based on my understanding of the React docs, this should not work and is explicitly warned against in the docs:

Never mutate this.state directly as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable

(https://reactjs.org/docs/react-component.html#state)

The docs also state that comparisons are shallow, so calling with an empty object should surely result in no merge and therefore no re-render?

Why does this example work?

(also I should note that I also reproduce this behaviour with React v16.9.0)

Edit: I also want to point out (since many answers refer to the fact that I am passing an empty object) that the component is re-rendered (and updated) if I call setState like so:

this.setState({ results: this.state.results })

which seems like it should not cause a re-render

like image 387
jramm Avatar asked Jan 17 '26 08:01

jramm


1 Answers

From the documentation: setState

setState() will always lead to a re-render unless shouldComponentUpdate() returns false.

Which means that this.setState({}); leads to re-render regardless if you pass a change as an argument or not

like image 80
Rostyslav Avatar answered Jan 20 '26 01:01

Rostyslav