MediumHooks📖 Theory Question

What is the stale closure problem in React hooks and how do you fix it?

💡

Hint

A closure captures variables at creation time — if state changes but the closure isn't recreated, it reads old values

Full Answer

A stale closure occurs when a function captures a value from a previous render and never gets updated to see the new value.

// ❌ Classic example — setInterval with stale count
function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      console.log(count); // always logs 0 — stale closure!
      setCount(count + 1); // always sets to 1 — bug!
    }, 1000);
    return () => clearInterval(id);
  }, []); // empty deps — closure captures count=0 forever
}

// ✅ Fix 1 — functional update (doesn't need to close over count)
setCount(c => c + 1); // always gets the latest count

// ✅ Fix 2 — add count to deps (effect re-runs on every count change)
useEffect(() => {
  const id = setInterval(() => {
    setCount(count + 1);
  }, 1000);
  return () => clearInterval(id); // cleans up old interval
}, [count]);

// ✅ Fix 3 — useRef stores mutable value, not affected by closures
const countRef = useRef(count);
useEffect(() => { countRef.current = count; }, [count]);

useEffect(() => {
  const id = setInterval(() => {
    setCount(countRef.current + 1); // always reads latest
  }, 1000);
  return () => clearInterval(id);
}, []);
💡 The eslint-plugin-react-hooks exhaustive-deps rule catches most stale closure issues at lint time. When you're tempted to suppress it, think about whether a functional update or ref would solve the problem instead.

Practice this in a timed sprint →

5 free questions, no signup required

⚡ Start Sprint