Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62
Subscribe to my email list now at http://jauyeung.net/subscribe/
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.