Hint
State updates are batched; use functional form for updates based on previous state; pass a function to useState for expensive initial computation
const [state, setState] = useState(initialValue);
Functional updates — use when next state depends on current state:
// ❌ Potentially stale closure
setCount(count + 1);
setCount(count + 1); // both read the same stale 'count'
// ✅ Always gets the latest value
setCount(c => c + 1);
setCount(c => c + 1); // correctly increments twice
Batching — React 18 batches all state updates (even in setTimeout, fetch callbacks) into a single re-render:
// React 18: both updates batched into ONE re-render
setTimeout(() => {
setCount(c => c + 1);
setName('Alice');
}, 0);
Lazy initialization — pass a function to avoid re-running expensive setup on every render:
// ❌ computeExpensive() runs every render (result discarded after mount)
const [state, setState] = useState(computeExpensive());
// ✅ Only runs on mount
const [state, setState] = useState(() => computeExpensive());