Is SetState Asynchronous?
Learn if React's setState is asynchronous or not including how React handles initial state, re-renders, and how state variables are different from regular JavaScript variables.
Table of Contents 📖
- Why Developers Think setState is Asynchronous
- Why this Isn't Necessarily Asynchronous
- React State Variables
Why Developers Think setState is Asynchronous
Changing state is commonly thought of as asynchronous because when setState is called, the new state isn't available right away. For example, the component below consists of a count state variable that is incremented when a button is clicked.
import {useState} from 'react';
const App = () => {
console.log('Rendering App');
const [count, setCount] = useState(0);
const increment = () => {
console.log('Previous count: ' + count);
setCount(count + 1);
console.log('Updated count: ' + count);
};
return (
<>
<h1>{count}</h1>
<button onClick={increment}>Click</button>
</>
);
};
export default App;
When the button is clicked, the count variable is logged both before and after the setState call.
Rendering App
App.jsx:25 Previous count: 0
App.jsx:27 Updated count: 0
App.jsx:19 Rendering App
App.jsx:25 Previous count: 1
App.jsx:27 Updated count: 1
App.jsx:19 Rendering App
App.jsx:25 Previous count: 2
App.jsx:27 Updated count: 2
Note how the previous count and the updated count logs contain the same value for the count state.
Why this Isn't Necessarily Asynchronous
What makes this seem asynchronous is the fact that the new state isn't available right away. However, the new state is there, it's just available in the next render. Remember, changing the state of a component with setState will cause a re-render, and it is this new render that contains the new state. In other words, the new state is available when the component function is called again.
import {useState} from 'react';
const App = () => {
console.log('Rendering App');
const [count, setCount] = useState(0);
console.log('Rendered count is ' + count);
const increment = () => {
setCount(count + 1);
};
return (
<>
<h1>{count}</h1>
<button onClick={increment}>Click</button>
</>
);
};
export default App;
Running this application will give output similar to the following.
Rendering App
App.jsx:24 Rendered count is 0
App.jsx:19 Rendering App
App.jsx:24 Rendered count is 1
App.jsx:19 Rendering App
App.jsx:24 Rendered count is 2
App.jsx:19 Rendering App
App.jsx:24 Rendered count is 3
App.jsx:19 Rendering App
App.jsx:24 Rendered count is 4
Note how the new count state is logged when the App component function is called.
React State Variables
This happens because a state variable is not a regular JavaScript variable, but rather a snapshot of a component. When state is updated with the setState function, the new state isn't available until the next render, or the new snapshot. This can be confusing because of some magic React abstracts away under the hood. Specifically, when it comes to setting the initial state.
const App = () => {
console.log('Rendering App');
// Initial state that is ignored by React on subsequent renders.
const [count, setCount] = useState(0);
console.log('Rendered count is ' + count);
...
When the function component is called again, the initial state value isn't used because, under the hood, React saves the initial state once and then ignores it for subsequent renders.