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.

Upvotes: 5

Views: 203

Answers (2)

goto
goto

Reputation: 4425

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.

Upvotes: 1

Joe Lloyd
Joe Lloyd

Reputation: 22363

Use the return function

By passing the function here you will always have access to the previous state.

this.setState((prevState) => ({ count: prevState.count + 1 }))

Note you can also add props as a second arg.

If you would like to read more about this callback check out this article. It goes into great detail.

Upvotes: 0

Related Questions