Calling a state setter doesn't synchronously update the state variable available in the currently-executing function — React schedules the update and applies it before the next render, which means code immediately after a setState call, within that same function, still sees the old value. React also batches multiple state updates that occur within the same event handler, and since React 18, within most other contexts too, into a single re-render, rather than re-rendering separately after each individual update, both for efficiency and to avoid showing intermediate, inconsistent UI states partway through a sequence of related updates.
1Understanding Asynchronous State
Calling a state setter doesn't synchronously update the state variable available in the currently-executing function — React schedules the update and applies it before the next render, which means code immediately after a setState call, within that same function, still sees the old value. React also batches multiple state updates that occur within the same event handler, and since React 18, within most other contexts too, into a single re-render, rather than re-rendering separately after each individual update, both for efficiency and to avoid showing intermediate, inconsistent UI states partway through a sequence of related updates.
If you need to react to a state value right after it changes, put that logic inside a useEffect watching that specific value, rather than expecting the updated value to be immediately available on the very next line after calling its setter.
import { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
console.log(count); // still logs the OLD value
};
return <button onClick={handleClick}>Count: {count}</button>;
}2Practical Example
Here is a real-world application of Asynchronous State showing how it is used in production React code.
import { useState } from 'react';
function Example() {
const [a, setA] = useState(0);
const [b, setB] = useState(0);
const handleClick = () => {
setA(a + 1);
setB(b + 1);
console.log('This causes only ONE re-render, not two');
};
return <p>{a} / {b}</p>;
}3Best Practices
Follow these guidelines when working with Asynchronous State:
1. Never assume a state variable reflects its new value on the line immediately following its setter call within the same function execution
2. Use useEffect with the relevant value in its dependency array to run logic specifically in response to that value having changed, rather than expecting an immediate synchronous update
3. Use the functional updater form when a new state value depends on the current one, since it always receives the latest pending value regardless of batching
Tip: If you need to react to a state value right after it changes, put that logic inside a useEffect watching that specific value, rather than expecting the updated value to be immediately available on the very next line after calling its setter.
import { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
console.log(count); // still logs the OLD value
};
return <button onClick={handleClick}>Count: {count}</button>;
}