136

I wanted to check what happens when you use this.setState multiple times (2 times for the sake of the discussion). I thought that the component will be rendered twice but apparently it's rendered only once. Another expectation I had was that maybe the second call for setState will run over the first one, but you guessed it - worked fine.

Link to a JSfiddle

var Hello = React.createClass({
  render: function() {
    return (
      <div>
        <div>Hello {this.props.name}</div>
        <CheckBox />
      </div>
    );
  }
});

var CheckBox = React.createClass({
  getInitialState: function() {
    return {
      alex: 0
    };
  },

  handleChange: function(event) {
    this.setState({
      value: event.target.value
    });
    this.setState({
      alex: 5
    });
  },

  render: function() {
    alert('render');
    return (
      <div>
        <label htmlFor="alex">Alex</label>
        <input type="checkbox" onChange={this.handleChange} name="alex" />
        <div>{this.state.alex}</div>
      </div>
    );
  }
});

ReactDOM.render(
  <Hello name="World" />,
  document.getElementById('container')
);

As you'll see, an alert that says 'render' pops up on every render.

Do you have an explanation for why it worked properly?

tanguy_k
  • 11,307
  • 6
  • 54
  • 58
alexunder
  • 2,075
  • 3
  • 16
  • 29
  • 2
    React is pretty clever and will only re-render when the state required to render is changed. In your render method you're only using `this.state.alex` - what happens if you add an element that depends on `this.state.value` as well? – Martin Wedvich Nov 09 '15 at 16:34
  • 1
    @MartinWedvich It will break in case I'm dependent on it. For that you have the 'getInitialState' method - to set the default values so your app won't break. – alexunder Nov 10 '15 at 08:21
  • 1
    related, and useful: https://stackoverflow.com/questions/48563650/does-react-keep-the-order-for-state-updates/48610973#48610973 – andydavies Jun 26 '18 at 15:11

2 Answers2

158

React batches state updates that occur in event handlers and lifecycle methods. Thus, if you update state multiple times in a <div onClick /> handler, React will wait for event handling to finish before re-rendering.

To be clear, this only works in React-controlled synthetic event handlers and lifecycle methods. State updates are not batched in AJAX and setTimeout event handlers, for example.

UPDATE

With the release of React v18.0, state updates within non-React events (promises, setTimeout etc.) are also batched by default.

Ref - https://reactjs.org/blog/2022/03/29/react-v18.html#new-feature-automatic-batching

Animesh Kumar
  • 237
  • 3
  • 11
Chris Gaudreau
  • 2,276
  • 1
  • 15
  • 17
  • 2
    Thanks, it makes sense, any idea how is it done behind the scenes? – alexunder Nov 10 '15 at 08:20
  • 19
    Since React fully controls synthetic event handlers (like `
    `) and component lifecycle methods, it knows that it can safely change the behavior of `setState` for the duration of the call to batch state updates and wait to flush. In an AJAX or `setTimeout` handler by contrast, React has no way to know when our handler has finished executing. Technically, React queues state updates in both cases, but flushes immediately outside of a React-controlled handler.
    – Chris Gaudreau Nov 12 '15 at 14:46
  • @ChrisGaudreau do you have any documentation on this fact? I can't find any reference besides the docs that say they might batch `setState`. – neurosnap Jan 04 '17 at 19:51
  • Is there any decorator or pattern to enable batching update for a custom method? – Benjamin Toueg Feb 08 '17 at 09:16
  • 2
    @neurosnap I don't think the docs go into much detail on this. It is mentioned abstractly in the [State and Lifecycle section](https://facebook.github.io/react/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous) and the [setState docs](https://facebook.github.io/react/docs/react-component.html#setstate). See the code for [ReactDefaultBatchingStrategy](https://github.com/facebook/react/blob/15-stable/src/renderers/shared/stack/reconciler/ReactDefaultBatchingStrategy.js#L52), which is currently the only official batching strategy. – Chris Gaudreau Feb 08 '17 at 10:02
  • 2
    @BenjaminToueg I believe you can use `ReactDOM.unstable_batchedUpdates(function)` to batch `setState` outside of React event handlers. As the name implies, you'll void your warranty. – Chris Gaudreau Feb 08 '17 at 10:09
  • How to call callback after all setState is completed? (now i have one setState and one callback), What if I need to call setState in every subcomponent and after that to call callback. How to call callback function after all setState callbacks? – Anatoli Klamer Aug 16 '17 at 15:19
  • 3
    always good to ref to this guy https://twitter.com/dan_abramov/status/887963264335872000?lang=en – Hertzel Guinness May 16 '18 at 10:02
  • 2
    Also by Dan Abramov, and linked to from the setState doc: https://stackoverflow.com/questions/48563650/does-react-keep-the-order-for-state-updates/48610973#48610973 – Chris Bobbe Feb 19 '20 at 22:59
61

The setState() method does not immediately update the state of the component, it just puts the update in a queue to be processed later. React may batch multiple update requests together to make rendering more efficient. Due to this, special precautions must be made when you try to update the state based on the component's previous state.

For example, the following code will only increment the state value attribute by 1 even though it was called 4 times:

 class Counter extends React.Component{
   constructor(props){
     super(props)
    //initial state set up
     this.state = {value:0}
   }
   componentDidMount(){
    //updating state
    this.setState({value:this.state.value+1})
    this.setState({value:this.state.value+1})
    this.setState({value:this.state.value+1})
    this.setState({value:this.state.value+1})
   }
   render(){
    return <div>Message:{this.state.value}</div>
   }
}

In order to use a state after it has been updated, do all logic in the callback argument:

//this.state.count is originally 0
this.setState({count:42}, () => {
  console.log(this.state.count)
//outputs 42
})

The setState(updater,[callback]) method can take in an updater function as its first argument to update the state based on the previous state and properties. The return value of the updater function will be shallowly merged with the previous component state. The method updates the state asynchronously, so a there is an option callback that will be called once the state has finished updating completely.

Example:

this.setState((prevState, props) => { 
return {attribute:"value"}
})

Here is an example of how to update the state based on previous state:

    class Counter extends React.Component{
      constructor(props) {
        super(props)
    //initial state set up
        this.state = {message:"initial message"}
    }
      componentDidMount() {
    //updating state
        this.setState((prevState, props) => {
          return {message: prevState.message + '!'}
        })
     }
     render(){
       return <div>Message:{this.state.message}</div>
     }
  }
Derek 朕會功夫
  • 92,235
  • 44
  • 185
  • 247
Biboswan
  • 1,145
  • 12
  • 15
  • 1
    Ah, I did not know about the `setState(updater,[callback])`, it's like an a less verbose `Object.assign` thanks for that! – AJ Venturella Oct 18 '18 at 07:16
  • 3
    @Biboswan, hi , I am new to React and wanted to ask you that is it true that all four setStates inside CompountDidMount method are merged and ONLY THE LAST setState will be executed but the other 3 will be ignored? – Dickens Aug 30 '19 at 14:18
  • For me, the key was to declare `previousState` parameter and use that in my updater function. – Saeed Neamati Aug 02 '21 at 08:05
  • 1
    Any idea how React decides whether to batch the update or to apply it immediately? I am talking about how it does this internally at code level. – darKnight Aug 15 '21 at 17:14
  • 1
    @darKnight it is mostly based on the concept of event loop and 16ms goes into 1 frame thing (60 frames per second). For best possible discussion I would suggest you to ask in the internals channel of reactiflux server in discord. – Biboswan Aug 16 '21 at 18:45