Set state based on previous one in render function

Ярослав Терещук :

I recently read the react.js documentation and found inconsistencies in setting the state based on previous state value. Here is that chunk of code:

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

I thought this way () => this.setState({ count: this.state.count + 1 }) of setting state is wrong and you should use callback for that purpose instead. So I've raised the PR in which I add use of callback function with previous state and it was deprecated because

The callback version is useful when you're not certain what the captured value of state is.

I don't really like when you're not certain part of the explanation and can someone explain why do this way () => this.setState({ count: this.state.count + 1 }) of setting the state is correct.

Thanks in advance.

goto1 :

In your current example, where you set your default state to { count: 0 }, you are "safe" to do setState({ count: this.state.count + 1 }) because when you first update your state, 0 + 1 will produce a valid result.

class App extends React.Component {
  state = { count: 0 }
  render() {
    return (
      <div>
         <p>You clicked {this.state.count} times</p>
         <button
           onClick={() => this.setState({ count: this.state.count + 1 })}
         >
           Click me!
         </button>
      </div>
    )
  }
}
ReactDOM.render(
  <App />,
  document.getElementById("app")
)
<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="app"></div>

However, let's assume your initial value for some piece of state is not 0, so calling this.state.count + 1 could produce an invalid result. This is where you'd reach for the callback version because:

you're not certain what the captured value of state is.

class App extends React.Component {
  state = { count: null }
  render() {
    const handleClick = () => {
      this.setState((prevState) => {
        // count is initially `null`, so 
        // `null + 1` could yield an undesired result
        if (!Number.isInteger(prevState.count)) {
          return { count: 1 }
        }
        return { count: prevState.count + 1 }
      })
    }
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={handleClick}>
          Click me!
        </button>
      </div>
    ) 
  }
}
ReactDOM.render(
  <App />,
  document.getElementById("app")
)
<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="app"></div>

This is just an example, but you get the idea.


Your PR was most likely declined because the example in the docs is correct, assuming that is used in the same context where it is "safe" to update your state by doing this.setState({ count: this.state.count + 1 }).

Here's the documentation:

Both ways of updating state are correct and should be used when appropriate. As you can see in the second example, the "callback option" would be a better solution if want to do some checks prior to updating your state.

Still, the example in the documentation is correct and wouldn't produce any benefits if it was using the "callback option" instead.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=174045&siteId=1
Recommended