Search code examples
javascriptreactjssetstate

continuous setState doesn't work as expected


I have below piece of code -

class Sum extends React.Component {
    constructor(props) {
      super(props)
      this.state = { a : 0 }
    }

    // let's call this ADD-1
    add = () => {
      this.setState({ a: this.state.a + 1 })
      this.setState({ a: this.state.a + 2 })
      this.setState({ a: this.state.a + 3 })
    } 

    render() {
      return (<div>
        <button onClick={this.add}> click me  </button>
        <div> sum  {this.state.a} </div>
      </div>)
    }
}

this renders on clicking the button

sum = 3

where as i was hoping that it will render sum = 6 i.e 1 + 2 + 3

also, if I change my add method to something like to accommodate for prevState race condition-

  // let's call this ADD-2
  add = () => {
    this.setState({ a: this.state.a + 1 })
    this.setState({ a: this.state.a + 2 })
    this.setState(prevState => ({ a: prevState.a + 1 }))
    this.setState(prevState => ({ a: prevState.a + 4 }))
  }

it renders sum = 7 whereas I was hoping for sum = 8 i.e (1 + 2 + 1 + 4)

Now two questions come to my mind:-

1) Why do we see the results as the one mentioned above and not what I have expected?

2) Why don't I see the transition of addition in UI? Say if we consider method tagged as ADD-1, I should be seeing something like sum = 1 then sum = 3 then sum = 6. Is it because of batching of updates but batching puts them in a queue of execution it doesn't override anything in my opinion.


Solution

  • State update maybe asynchronous. Check this answer.

    In an answer by Dan abramov, it is stated that state updates within one event call will only produce a single re-render at the end of the event.

    no matter how many setState() calls in how many components you do inside a React event handler, they will produce only a single re-render at the end of the event.

    And also batching happens only for state updates within a React event handler i.e batching does not happen inside AJAX calls

    promise.then(() => {
      // We're not in an event handler, so these are flushed separately.
      this.setState({a: true}); // Re-renders with {a: true, b: false }
      this.setState({b: true}); // Re-renders with {a: true, b: true }
      this.props.setParentState(); // Re-renders the parent
    });
    

    But you could achieve what you want to by passing a callback to the setState method

    add = () => {
          this.setState({ a: this.state.a + 1 },
            () => {
            this.setState({ a: this.state.a + 2 },
              () => {
              this.setState({ a: this.state.a + 3 })
            })
          })    
        }
    

    The above will return sum = 6.

    When not to use callbacks in setState :

    PureComponent and shouldComponentUpdate can be used to tune up a component’s performance. They work by preventing lifecycle methods from firing when props and state haven’t changed.

    The setState callback fires regardless of what shouldComponentUpdate returns. So, the setState callback will fire, even when state hasn’t changed.