setState in ReactJS

To update state in ReactJS, you can directly edit state object but you can do it only in constructor of the component. So to update state other than constructor you need to use setState function.

Why use setState?

It is possible to update state object directly without using setState function but it will not re-render the component, so the new updated state will not be reflected in the view.
So only in constructor, you can directly edit state object, since entire component is going to render after the constructor.
So when you use setState function, it re-renders the component ( by calling render function ) and new updated state object will be reflected.
Let us have a look at an example.


import React from 'react';
import ReactDOM from 'react-dom';

class Counter extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            clickCount: 0
        };
        // You can edit state over here directly like below.
        this.state.clickCount = 1;
        this.onButtonClick = this.onButtonClick.bind(this);
    }
    onButtonClick(e){
        const { clickCount } = this.state;
        //But over here, this.state.clickCount += 1; will not render the component.
        this.setState({
            clickCount: clickCount + 1
        });
    }
    render(){
        const { clickCount } = this.state;
        return (
            <div>
                <div>Total Count : { clickCount }</div>
                <button onClick={this.onButtonClick}>Increase counter</button>
            </div>
        );
    }
}

ReactDOM.render(
    <Counter />,
    document.getElementById('root')
);
                    

Open above code in codepen.

How does setState work?

Basically setState schedules an update to state object. And when state changes, it re-renders the component.

You must be confused with its work. Why scedules an update to state ? Doesn't it directly change the state object?
No, setState does not change state object right away.
Let us have a look at an example.


import React from 'react';
import ReactDOM from 'react-dom';

class Counter extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            clickCount: 0
        };
        this.onButtonClick = this.onButtonClick.bind(this);
    }
    onButtonClick(e){
        console.log("clickCount", this.state.clickCount);
        // output:clickCount 0
        this.setState({
            clickCount: this.state.clickCount + 1
        });
        this.setState({
            clickCount: this.state.clickCount + 1
        });
        this.setState({
            clickCount: this.state.clickCount + 1
        });
        console.log("clickCount", this.state.clickCount);
        //output:clickCount 0
        // You must be expecting 3 but it is 0. And when component re-renders, you must be expecting 3 and click count is 1.
    }
    render(){
        const { clickCount } = this.state;
        return (
            <div>
                <div>Total Count : { clickCount }</div>
                <button onClick={this.onButtonClick}>Increase counter</button>
            </div>
        );
    }
}

ReactDOM.render(
    <Counter />,
    document.getElementById('root')
);
                    

Open above code in codepen.

So there are two confusions in above code.

  • Why clickCount is 1 after re-rendering of the component?
  • Why clickCount is 0 after 3 calls of setState?

We will answer them one by one.

Pass function to setState

Since setState function does not change state object right away, every time when you called setState, this.state.clickCount was 0. And ReactJS also tries to batch all the setState calls. So that is why when component re-renders, value of this.state.clickCount is 1 instead of 3.

To solve above problem, you can pass a function to setState function instead of an object.
Let us have a look at an example.


import React from 'react';
import ReactDOM from 'react-dom';

class Counter extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            clickCount: 0
        };
        this.onButtonClick = this.onButtonClick.bind(this);
    }
    onButtonClick(e){
        console.log("clickCount", this.state.clickCount);
        // output:clickCount 0
        this.setState((state)=>({
          clickCount: state.clickCount + 1
        }));
        this.setState((state)=>({
          clickCount: state.clickCount + 1
        }));
        this.setState((state)=>({
          clickCount: state.clickCount + 1
        }));
        console.log("clickCount", this.state.clickCount);
        //output: clickCount 0
        // You must be expecting 3 but it is 0. 
        //when component re-renders, value will be 3
    }
    render(){
        const { clickCount } = this.state;
        return (
            <div>
                <div>Total Count : { clickCount }</div>
                <button onClick={this.onButtonClick}>Increase counter</button>
            </div>
        );
    }
}

ReactDOM.render(
    <Counter />,
    document.getElementById('root')
);
                    

Open above code in codepen.

In above code, we have passed a function to setState, which has a parameter state object as the updated state, which you can use to update the new state. So clickCount in state object will always be updated.

Asynchronous setState

We already know that setState does not change state object directly. So setState is asynchronous but inside event handlers.
But why is that? Because in any event handler, if child and parent both call setState function, child will not re-render twice. So ReactJS waits for the event handler to end and then re-renders the components. This improves the preformance, specifically in larger apps.
So in any event, ReactJS waits for all the components to call setState function and after the event hanlder, ReactJS will re-render components. So there will be no unnecessary re-renders.

But why not update state object directly by setState?
There are two reasons given in official documentation of ReactJS.

  • This would cause inconsistancies between state and props which would be very hard to debug.
  • This would make some new features impossible to implement.

setState is asynchronous in event handlers. But if you use setState other than event handlers, it can be synchronous depending on the execution context. Let us have a look at an example.


import React from 'react';
import ReactDOM from 'react-dom';

class Counter extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            counter: 0
        };
        this.incCounter = this.incCounter.bind(this);
    }
    componentWillMount(){
      // the below code will not execute setState synchronously setState
      // this.incCounter();
      // this.incCounter();
      // this.incCounter();
      
      // the below code will execute setState synchronously.
      let i = 0;
      let id = setInterval(()=>{
        i++;
        this.incCounter();
        if(i===3){
          clearInterval(id);
        }
      },1000);
    }
    incCounter(){
      console.log("before", this.state.counter);
      this.setState({
        counter: this.state.counter + 1
      });
      console.log("after", this.state.counter);
    }
    render(){
        const { counter } = this.state;
        return (
            <div>
                <div>Total Count : {counter}</div>
            </div>
        );
    }
}

ReactDOM.render(
    <Counter />,
    document.getElementById('root')
);
                    

Open above code in codepen.

So always assume that setState is asynchronous and it will always be.

Conclusion

In this section, we have learned about setState in ReactJS.
In the next section, you will learn about props in ReactJS.