Skip to content
Optimal Lab

Frontend · interactive

Hooks and stale closures

Each render of a React component is a fresh function call with its own props and state. A callback created during one render keeps seeing that render's values. An interval set up once, in the first render, sees count = 0 forever.

useEffect(() => {
  const id = setInterval(() => setCount(count + 1), 1000);
  return () => clearInterval(id);
}, []);  // runs once
aftercallback sees countcount becomes
1 s01
2 s01
3 s01
4 s01
5 s01

intervals created: 1

The effect ran once, so its callback closed over the first render, where count is 0. Every tick sets count to 0 + 1. The counter is stuck at 1.

Version

Step 1 of 4

  1. 1

    Every render has its own values

    count isn't a live variable; it's a constant for that render. New render, new constant.

    Closures capture what was in scope.

  2. 2

    Dependencies decide when effects re-run

    [] means once. Values you read inside should be listed; the exhaustive-deps lint rule checks this.

    Lying about deps causes stale reads.

  3. 3

    Updater functions and refs

    setX(prev => …) avoids reading state; a ref holds a mutable latest value when you really need one.

    useEffectEvent is the newer tool for this.

  4. 4

    Always return a cleanup

    Clear intervals, unsubscribe, abort fetches. Strict Mode runs effects twice in development to catch missing cleanups.

    Many effects shouldn't be effects at all.

In production

Tools and technologies

Every React developer hits this with effects and intervals.

Not tied to one language — use these names when you search docs or design a system.