Lesson 18 +10 XP

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.