1

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.

Tholle
  • 108,070
  • 19
  • 198
  • 189
Subham Tripathi
  • 2,683
  • 6
  • 41
  • 70
  • 4
    setState is asynchronous all of those get called "essentially" at once. Meaning the last one is setting state to 0 + 3. setState method has a callback if you want to wait for it to update then call your other setState's. this.setState({value: value+1}, () => { this.setState({value: value + 2})}) https://medium.com/@wereHamster/beware-react-setstate-is-asynchronous-ce87ef1a9cf3 – Budhead2004 Jun 29 '18 at 14:37

4 Answers4

2

Make sure you pass a function to setState when your update is dependent on current state so it doesn't get overwritten by subsequent setState.

Example

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

  add = () => {
    this.setState(previousState => {
      return { a: previousState.a + 1 };
    });
    this.setState(previousState => {
      return { a: previousState.a + 2 };
    });
    this.setState(previousState => {
      return { a: previousState.a + 3 };
    });
  };

  render() {
    return (
      <div>
        <button onClick={this.add}> click me </button>
        <div> sum {this.state.a} </div>
      </div>
    );
  }
}
Tholle
  • 108,070
  • 19
  • 198
  • 189
2

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.

Ragul Parani
  • 621
  • 7
  • 22
1

"React may batch multiple setState() calls into a single update for performance."

From the Docs

WebbH
  • 2,379
  • 1
  • 15
  • 26
0

state change can happen only through setState, but if you access and modify this.state before setState (this.state.a + 1) it won't reflect. so every time you access this.state.a it's getting 0 i.e initial value.

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

this.setState((previousState) => { // change state using previousState} );

this is the way to have to update state

Poulima Biswas
  • 177
  • 2
  • 4