why is setstate in reactjs async instead of sync?

  • Last Update :
  • Techknowledgy :

This is because setState alters the state and causes rerendering. This can be an expensive operation and making it synchronous might leave the browser unresponsive. Thus the setState calls are asynchronous as well as batched for better UI experience and performance. 1) setState actions are asynchronous and are batched for performance gains. This is explained in the documentation of setState. and passes it into one single setState() to prevent re-rendering due to multiple setState() calls (this is a very primitive description of batching).

This is our Splunktool team suggestion, we tried and its working fine ✌
const [someState, setSomeState] = useState();​
const promisedState = (newState) => new Promise((resolve) => setSomeState(newState, resolve))​
const someFunc = async (value) => {
   await promisedState(value)
   // do some other stuff
}​
// This way is not recommended though as it throws this error
// State updates from the useState() and useReducer() Hooks don't support the second callback argument

You can call a function after the state value has updated:

this.setState({
   foo: 'bar'
}, () => {
   // Do something here. 
});

Also, if you have lots of states to update at once, group them all within the same setState:

Instead of:

this.setState({
   foo: "one"
}, () => {
   this.setState({
      bar: "two"
   });
});

Suggestion : 2

To update the state of a component, you use the setState method. However it is easy to forget that the setState method is asynchronous, causing tricky to debug issues in your code. The setState function also does not return a Promise. Using async/await or anything similar will not work. When the state is actually set can vary. Usually it happens on the next render, but it can sometimes be batched for performance. The setState function takes an optional callback parameter that can be used to make updates after the state is changed. This function will get called once the state has been updated, and the callback will receive the updated value of the state.

1._
handleButtonClicked = evt => {
   this.setState({
      name: evt.currentTarget.value
   })
   this.props.callback(this.state.name) // Will send the old value for name
}
2._
handleButtonClicked = evt => {
   this.setState({
      name: evt.currentTarget.value
   }, () => {
      this.props.callback(this.state.name)
   })
}

Suggestion : 3

This is because setState alters the state and causes rerendering. This can be an expensive operation and making it synchronous might leave the browser unresponsive. Thus the setState calls are asynchronous as well as batched for better UI experience and performance. Why would they make setState async as JS is single threaded language and this setState is not a WebAPI or server call so has to be done on JS's thread only. Are they doing this so that Re-Rendering does not stop all the event listeners and stuff, or there is some other design issue. 1) setState actions are asynchronous and are batched for performance gains. This is explained in the documentation of setState.

You can call a function after the state value has updated:

this.setState({
   foo: 'bar'
}, () => {
   // Do something here. 
});

Also, if you have lots of states to update at once, group them all within the same setState:

Instead of:

this.setState({
   foo: "one"
}, () => {
   this.setState({
      bar: "two"
   });
});

Just do this:

this.setState({
   foo: "one",
   bar: "two"
});

Suggestion : 4

Still, you have the option of moving the state that you want to read immediately into some sideways mutable object, especially if you don’t use it as a source of truth for rendering. Which is pretty much what MobX lets you do 🙂. @gaearon Thanks for the detailed and great explanation. Though there is still something missing here which i think i understand but want to be sure. The other argument I hear sometimes is that you want to reason about the state that was rendered, not the state that was requested. But I'm not sure this principle has much merit either. Conceptually it feels strange to me. Rendering is a side effect, state is about facts. Today, I am 32 years old, and next year I will turn 33, regardless whether the owning component manages to re-render this year or not :).

1._
Component.prototype.setState = (nextState) => {
   this.state = nextState
   if (!this.renderScheduled)
      setImmediate(this.forceUpdate)
}
2._
console.log(this.state.value) // 0
this.setState({
   value: this.state.value + 1
});
console.log(this.state.value) // 1
this.setState({
   value: this.state.value + 1
});
console.log(this.state.value) // 2
3._
-this.setState({
   value: this.state.value + 1
}); +
this.props.onIncrement(); // Does the same thing in a parent
5._
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  componentDidMount() {
    this.refBtn.addEventListener("click", this.onClick);
  }

  componentWillUnmount() {
    this.refBtn.removeEventListener("click", this.onClick);
  }

  onClick = () => {
    console.log("before setState", this.state.count);
    this.setState(state => ({ count: state.count + 1 }));
    console.log("after setState", this.state.count);
  };

  render() {
    return (
      <div>
        <button onClick={this.onClick}>React Event</button>
        <button ref={ref => (this.refBtn = ref)}>Direct DOM event</button>
      </div>
    );
  }
}

Suggestion : 5

React does not guarantee that the state changes are applied immediately. Why does calling react setState method not mutate the state immediately? Why is setState in reactjs Async instead of Sync? Why calling setState method doesn't mutate the state immediately?

You can still perform functions if it is dependent on the change of the state value:

Option 1: Using callback function with setState

this.setState({
   value: newValue
}, () => {
   // It is an callback function.
   // Here you can access the update value
   console.log(this.state.value)
})

Option 2: using componentDidUpdate This function will be called whenever the state of that particular class changes.

componentDidUpdate(prevProps, prevState) {
   //Here you can check if value of your desired variable is same or not.
   if (this.state.value !== prevState.value) {
      // this part will execute if your desired variable updates
   }
}

Imagine incrementing a counter in some component:

  class SomeComponent extends Component{

    state = {
      updatedByDiv: '',
      updatedByBtn: '',
      counter: 0
    }

    divCountHandler = () => {
      this.setState({
        updatedByDiv: 'Div',
        counter: this.state.counter + 1
      });
      console.log('divCountHandler executed');
    }

    btnCountHandler = () => {
      this.setState({
        updatedByBtn: 'Button',
        counter: this.state.counter + 1
      });
      console.log('btnCountHandler executed');
    }
    ...
    ...
    render(){
      return (
        ...
        // a parent div
        <div onClick={this.divCountHandler}>
          // a child button
          <button onClick={this.btnCountHandler}>Increment Count</button>
        </div>
        ...
      )
    }
  }

To stop this behaviour, instead of passing objects as arguments to the setState method, callbacks are passed.

    divCountHandler = () => {
       this.setState((prevState, props) => {
          return {
             updatedByDiv: 'Div',
             counter: prevState.counter + 1
          };
       });
       console.log('divCountHandler executed');
    }

    btnCountHandler = () => {
       this.setState((prevState, props) => {
          return {
             updatedByBtn: 'Button',
             counter: prevState.counter + 1
          };
       });
       console.log('btnCountHandler executed');
    }

Asynchronous setState() makes life very difficult for those getting started and even experienced unfortunately:
- unexpected rendering issues: delayed rendering or no rendering (based on program logic)
- passing parameters is a big deal
among other issues.

Below example helped:

// call doMyTask1 - here we set state
// then after state is updated...
//     call to doMyTask2 to proceed further in program

constructor(props) {
   // ..

   // This binding is necessary to make `this` work in the callback
   this.doMyTask1 = this.doMyTask1.bind(this);
   this.doMyTask2 = this.doMyTask2.bind(this);
}

function doMyTask1(myparam1) {
   // ..

   this.setState({
         mystate1: 'myvalue1',
         mystate2: 'myvalue2'
         // ...
      },
      () => {
         this.doMyTask2(myparam1);
      }
   );
}

function doMyTask2(myparam2) {
   // ..
}