If you have been learning about React's useState hook, you have probably seen two different ways to update state and wondered when to use each one.
        
        
          
            
          
          
        
        setCount(count + 1)           // Direct update
setCount(prev => prev + 1)    // Functional update
      They look similar, but choosing the wrong one can lead to bugs.
The Core Difference
Direct update uses the current value from your component's render:
        
        
          
            
          
          
        
        const [count, setCount] = useState(0);
const increment = () => {
  setCount(count + 1);  // Uses 'count' from this render
};
      Functional update uses the most recent state value that React has:
        
        
          
            
          
          
        
        const [count, setCount] = useState(0);
const increment = () => {
  setCount(prev => prev + 1);  // Uses latest state from React
};
      When It Actually Matters
Multiple Updates in the Same Function
This is where you'll first notice the difference:
        
        
          
            
          
          
        
        const handleClick = () => {
  setCount(count + 1);  // count = 0, sets to 1
  setCount(count + 1);  // count is STILL 0, sets to 1 again
  setCount(count + 1);  // count is STILL 0, sets to 1 again
};
// Result: count becomes 1, not 3
      With functional updates:
        
        
          
            
          
          
        
        const handleClick = () => {
  setCount(prev => prev + 1);  // 0 → 1
  setCount(prev => prev + 1);  // 1 → 2
  setCount(prev => prev + 1);  // 2 → 3
};
// Result: count becomes 3
      Stale Closures in Async Operations
The bigger problem happens with closures - functions that "remember" values from when they were created.
Example: setTimeout
        
        
          
            
          
          
        
        function Counter() {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setTimeout(() => {
      setCount(count + 1);  // Uses count from when setTimeout was called
    }, 3000);
  };
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Add after 3 seconds</button>
    </div>
  );
}
      What happens:
- Count is 0
- Click button (starts timer, remembers count = 0)
- Click button again (starts another timer, also remembers count = 0)
- Wait 3 seconds...
- Both timers execute with count = 0
- Result: count = 1 (not 2!)
Fixed with functional update:
        
        
          
            
          
          
        
        setTimeout(() => {
  setCount(prev => prev + 1);  // Uses count when timer executes
}, 3000);
      Now clicking twice gives you count = 2.
When Either Works Fine
For simple, single updates in response to user actions, both are identical:
        
        
          
            
          
          
        
        <button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(prev => prev + 1)}>+</button>
// These do exactly the same thing
      The Rule
Use functional updates setState(prev => ...) when:
- New state depends on previous state - incrementing, decrementing, toggling
- Multiple setState calls in the same function
- Inside async operations - setTimeout, setInterval, fetch callbacks
- Inside useEffect with empty or limited dependencies
- When in doubt - it never hurts to use functional form
Use direct updates (setState(value)) when:
- Setting a completely new value that doesn't depend on previous state
        
        
          
            
          
          
        
        setCount(0)                  // Reset
setName(event.target.value)  // From input
setData(responseData)        // From API
setIsOpen(false)             // Set to specific value