简体   繁体   中英

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 ie 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 ie (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.

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

From the Docs

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>
    );
  }
}

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 ie 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.

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 ie 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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM