9

So I have some confusion regarding the async nature of setState in ReactJS. As per React docs, you shouldn't use this.state inside setState(). But if I have a counter as a state and i want to update it on click like this:

class App extends React.Component {
    state = { counter: 0 }

    onClick = () => {
        this.setState({counter: this.state.counter + 1})
    }

    render() {
        return (
          <div>
            <div>{this.state.counter}</div>
            <p onClick={this.onClick}>Click me</p>
          </div>
        )
    }
}

This works as expected. So why is this code wrong?

UPDATE: I know that setState is async, and it accepts a callback which has previous state as an argument, but I am not sure why I should use it here? I want to refer to the old state inside setState, so why should I use the callback function in this case? Whenever this.setState() is executed, this.state inside it will always refer to the old state, and its value will be changed to the new state only after setState has finished executing, not while it is executing.

darKnight
  • 5,651
  • 13
  • 47
  • 87
  • Possible duplicate of [Editing this.state content before setState in React](https://stackoverflow.com/questions/35304409/editing-this-state-content-before-setstate-in-react) – StackedQ Aug 13 '18 at 11:59

2 Answers2

22

You have access to prevState from within your setState call:

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

That will allow you to safely increment the current state value.

The React documentation summarises why you cannot rely on this.state to be accurate during update: https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous

Steve Vaughan
  • 2,163
  • 13
  • 18
  • But isn't `this.state` inside `setState` already referring to the previous state in my code? So why use the setState callback in this case? – darKnight Aug 13 '18 at 08:17
  • 3
    @maverick Because React can and will batch setState calls, so there is no guarantee at what point that call will be fired. This means that it will read from `this.state` at potentially the wrong time. Using the callback is the safest way to access previous state from _within_ the setState call. – Steve Vaughan Aug 13 '18 at 08:19
  • 1
    I understand that setState is async. Which means that it won't get called immediately. But when it does get called, this.state will still be referring to the old state, which is what I want to access in this case. this.state will update its value after setState has finished execution, but not while it is executing. – darKnight Aug 13 '18 at 08:21
  • The React docs summarise why you should not rely on `this.state` better than I can, they even have a counter example. Answer updated – Steve Vaughan Aug 13 '18 at 09:17
  • Please, see the examples in the official documentation on: https://reactjs.org/docs/hooks-state.html where they use: So I am also confused like @maverick. – vlakov Apr 10 '19 at 16:07
  • That document refers to the `useState` hook and I suspect the example of the traditional `setState` call wasn't really considered with regard to the issue that this Question covers. – Steve Vaughan Apr 11 '19 at 17:14
2

setState accepts a function as a parameter with previous state as argument. Refacto as follows to avoid competition problems:

onClick = () => {
    this.setState(prevState => ({counter: prevState.counter + 1}))
}
Alexandre Annic
  • 9,942
  • 5
  • 36
  • 50
  • What completion problems? Can you give an example? I know that setState accepts a callback, but I am not sure why it should be used in this case? – darKnight Aug 13 '18 at 08:18
  • @maverick Since `setState` is async, you can't assume that `this.state` is the last value you updated. Take a look at this: https://stackoverflow.com/a/48209870/5735030. – Alexandre Annic Aug 13 '18 at 09:12