Loading lessons...
Functional State Updates
Functional State Updates
When your next state depends on the previous state value, passing a raw value to the state setter can cause subtle bugs when state updates are batched or asynchronous.
The State Batching Problem
React batches multiple state updates inside event handlers to prevent excessive re-renders.
function BrokenCounter() {
const [count, setCount] = useState(0);
const handleClick = () => {
// ❌ Bug: All three lines read 'count' as 0 during this render cycle!
setCount(count + 1); // setCount(0 + 1)
setCount(count + 1); // setCount(0 + 1)
setCount(count + 1); // setCount(0 + 1)
// Result after click: count becomes 1, NOT 3!
};
return <button onClick={handleClick}>Add 3 (Broken): {count}</button>;
}
Using Functional State Updates
To guarantee you are updating based on the latest up-to-date state, pass an updater function to the setter function. The updater function receives the pending previous state as its argument.
function WorkingCounter() {
const [count, setCount] = useState(0);
const handleClick = () => {
// ✅ CORRECT: Functional updates queue state transformations sequentially
setCount(prevCount => prevCount + 1); // 0 -> 1
setCount(prevCount => prevCount + 1); // 1 -> 2
setCount(prevCount => prevCount + 1); // 2 -> 3
// Result after click: count becomes 3!
};
return <button onClick={handleClick}>Add 3 (Working): {count}</button>;
}
When to Use Functional Updates
- Whenever new state is calculated directly from previous state (counters, toggles, list append/removes).
- Inside asynchronous callbacks like
setTimeout,setInterval, or Promise resolutions.
// Toggle boolean state functionally
const toggleTheme = () => {
setIsDarkMode(prevMode => !prevMode);
};
Summary & TL;DR
- Pass an updater function
setState(prev => prev + 1)when the new state relies on previous state values. - Functional updates ensure state calculations read the most accurate pending state queue.
- Crucial for toggles, counters, and asynchronous timer callbacks.