React is the most used front end library for building modern, interactive front end web apps. It can also be used to build mobile apps.
In this article, we'll look at the async nature of setState and how we should write our code to run multiple setState calls sequentially.
setState's Asynchronous Nature
The setState method is the method to update the component's internal state. It's an asynchronous method that's batched. This means that multiple setState calls are batched before a component is rerendered with the new state.
setState doesn't immediately mutate the state but creates a pending state transaction. This means that accessing the state immediately after call setState can possibly return the old value.
The setState method takes up to 2 arguments. We usually pass in only one. The first argument can be an object or a callback that's used to update the state.
The second argument is a function that's always run after setState is run. For instance, we can pass in a callback in the 2nd argument as follows:
import React from "react";
class App extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
update() {
this.setState(
({ count }) => ({
count: count + 1
}),
() => {
this.setState(({ count }) => ({
count: count + 2
}));
}
);
}
render() {
return (
<>
<button onClick={this.update.bind(this)}>Increment</button>
<p>{this.state.count}</p>
</>
);
}
}
export default App;
In the code above, we have:
update() {
this.setState(
({ count }) => ({
count: count + 1
}),
() => {
this.setState(({ count }) => ({
count: count + 2
}));
}
);
}
which calls setState once, and then calls setState again in the callback. This will ensure that one will run after the other.
Therefore, when we click the Increment, the count state will increase by 3 every time.
setState Takes an Object or Function
As we can see from the code above, setState can take a callback that returns the new state based on the previous state. This is useful for updating states that are based on the previous state.
In the code above, the new count is based on the old count , so passing in a callback is more appropriate than passing in an object since we can guarantee that the original state is the lastest when it's passed in as the callback parameter.
It also takes a props parameter as the second parameter which has the props. For instance, we can use it as follows:
import React from "react";
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
update() {
this.setState(({ count }, { incrementVal }) => ({
count: incrementVal + count
}));
}
render() {
return (
<>
<button onClick={this.update.bind(this)}>Increment</button>
<p>{this.state.count}</p>
</>
);
}
}
export default function App() {
return <Counter incrementVal={5} />;
}
In the code above, we have the Counter component, which has the update method as we have in the previous example. But this ti,e, the setState method takes a callback which has a second parameter. It has the props object.
We can get the prop property values as we did above via the destructuring assignment syntax. Also, we can access the prop object's property like any other JavaScript object property with the dot or bracket notation.
Therefore, since we have:
<Counter incrementVal={5} />
in App , when we click the Increment button, the count state in Counter will update by 5 since that's what we specified.
The most common entity that we pass into setState is probably an object. We just pass in an object that has state properties that we want to change. The ones that aren't included will stay the same.
For instance, if we write:
import React from "react";
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0, foo: "foo" };
}
update() {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<>
<button onClick={this.update.bind(this)}>Increment</button>
<p>{this.state.count}</p>
<p>{this.state.foo}</p>
</>
);
}
}
export default function App() {
return <Counter />;
}
Then this.state.foo has the value 'foo' even after this.setstate in update is run.
Therefore, when we click the Increment button, we see 'foo' displayed no matter how many times we click it.
Conclusion
setState calls may not be always sequential. The batching sometimes may not be controlled by React. Therefore, if we want to make sure that multiple setState calls run sequentially all the time, we should run the second setState in the callback that's passed in as the 2nd argument of setState .
Also, setState can take an object or a function with the previous state and the props objects as the 1st and 2nd parameters respectively.