All visualizationsReact · 16 of 18
useEffect timing — built from scratch, painted first
Queue the effect, paint the screen, clean up the old world, then connect the new one — in 18 runnable lines.
💡
THE BIG IDEA
useEffect’s three mysteries — why the effect doesn’t run during render, when it actually runs, and why cleanup remembers OLD values — are all one mechanism: a queue that waits for paint, and closures frozen per render. We build it, run it, and watch a chat app switch rooms in exactly the order real React uses.
Move your mouse over any line to see what it does. Hover a loop line and it plays through every repeat. Or press ▶ Play to watch the whole thing. On a phone, tap the arrows.
1const effects = [];
2function useEffect(fn) { effects.push(fn); }
3let cleanup = null;
4function Chat({ room }) {
5 useEffect(() => {
6 console.log("connect to", room);
7 return () => console.log("disconnect from", room);
8 });
9 console.log("render", room);
10}
11function commit(room) {
12 Chat({ room });
13 console.log("(screen painted)");
14 if (cleanup) cleanup();
15 cleanup = effects.pop()();
16}
17commit("maths");
18commit("physics");
A chat component connects to a room. You switch from "maths" to "physics" — and somehow React knows to disconnect from maths first, at exactly the right moment. Who taught it that?
We build useEffect’s scheduling in 18 lines that really run. Three ghosts die today: "why doesn’t my effect run during render?", "when exactly does it run?", and "why does cleanup remember the OLD room?"
📦 Memory boxes
what the program is remembering right now
nothing remembered yet
🖥️ What the computer shows
the answers the program prints out
nothing yet
1/14
UP NEXT IN REACT
useMemo — the answer shelf (a work counter proves it)
Three renders: the plain filter pays 3 times, the memoized one pays once — and honestly re-pays when a dependency truly changes.
One click, one re-render — how React changes the screenuseMemo — the answer shelf (a work counter proves it)