2

It is said that we should never mutate this.state, but I think it means "never mutate this.state directly but always use this.setState() to mutate it instead.

But what if we mutate it directly but also follow it by a this.setState()?

The following code actually works. Why is the number updated on screen (even componentDidUpdate() is invoked) -- is it by luck and should not be depended on?

Does the line

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

actually tells React that numbers is modified and therefore, check for any updated value? Or is React supposed to ignore it, because if React takes it as numbers referring to itself, it means nothing has changed. There is no key in the list items, so I think the default is the index of entry is used as keys. So the keys didn't change, meaning the list items haven't changed, and why is the list updated on screen?

To just update an array element, it seems so complicated. There seems to be two acceptable methods:

  1. Immutability Helpers
  2. this.setState({ numbers: newNumbersArray })

Is there any official docs that describe how to get it done, instead of blog posts or a Stack Overflow question that is full of different opinions? In Vue.js, it seems all it takes is numbers.splice() to replace an array element and that's it.

With this.state followed by a this.setState(), and with absence of key, can we count on it working?

class App extends React.Component {

  state = {
    numbers: [2, 4, 6]
  };

  clickHandler() {
    this.state.numbers[1] = Math.random();
    this.setState({ numbers: this.state.numbers });
  }

  componentDidUpdate() {
    console.log("In componentDidUpdate()");
  }

  render() {
    return (
      <div>
        <ul>{this.state.numbers.map(a => <li>{a}</li>)}</ul>
        <button onClick={this.clickHandler.bind(this)}>Click Me</button>
      </div>
    );
  }

}

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

<div id="root"></div>
tony19
  • 125,647
  • 18
  • 229
  • 307
nonopolarity
  • 146,324
  • 131
  • 460
  • 740
  • 2
    Short (but probably not complete) answer: mutating the state directly can lead to bugs since you are circumventing the lifecycle process. It doesn't matter that you use `setState` immediately after, because `setState` is asynchronous which could, theoretically, allow for other state changes to occur between your direct mutation and the `setState`. Realistically it might work fine for simple components, but it's probably going to become a nightmare once your application grows. – Chris Feb 10 '20 at 10:25
  • it is in such situation that I might want to go back to imperative programming... how come changing 1 entry in an array seems like a 2-hour lecture series... or maybe I just haven't found something that is more of an official answer yet – nonopolarity Feb 10 '20 at 10:29
  • It's not that hard, really. If your array contains primitive values (numbers, strings, etc) just create a copy, do your change, and set the copy as the new state. You could set it directly if you want to do some spread operations in-line. – Chris Feb 10 '20 at 10:30
  • uh-huh... I was thinking even if the array is 500 entries of objects, making a copy of 500 references to objects isn't that bad. (we are not talking about a million entries in front end dev). As of right now, Immutability Helpers https://reactjs.org/docs/update.html seems like legacy code and the link it points to https://github.com/kolodny/immutability-helper is so complicated. – nonopolarity Feb 10 '20 at 10:34
  • You would only need to make a copy of the particular object in the array you want to mutate. So one copy for the array, and one more copy for the object you want to change. Then you can do the change to said copy and then set the state. – Chris Feb 10 '20 at 10:37
  • Maybe you can find something useful in official [docs](https://reactjs.org/docs/faq-state.html) – SuleymanSah Feb 10 '20 at 11:02

1 Answers1

2

Short (but probably not complete) answer: mutating the state directly can lead to bugs since you are circumventing the lifecycle process. It doesn't matter that you use setState immediately after, because setState is asynchronous which could, theoretically, allow for other state changes to occur between your direct mutation and the setState. Realistically it might work fine for simple components, but it's probably going to become a nightmare once your application grows.

Here are a few ways to do what you tried:

clickHandler() {
  const copy = [...this.state.numbers]; // or this.state.numbers.slice();
  copy[1] = Math.random();
  this.setState({ numbers: copy });
}

Alternative:

clickHandler() {
  this.setState({ numbers:
    [
      ...this.state.numbers.slice(0, 1),
      Math.random(),
      ...this.state.numbers.slice(2, arr.length)
    ]
  });
}

There are more ways of course, but just to name a few...

Chris
  • 57,622
  • 19
  • 111
  • 137