Three patterns, one render cycle
.map(), and each item needs a stable key so
React can track identity across reorders. Conditionals are just
JS (&&, ? :) deciding which JSX exists.
The effect runs only at step 3 — after the screen is painted. That is the whole point: the user sees the UI, then React synchronizes with external systems.
The dependency array is the whole contract
| deps | when the effect runs | typical use |
|---|---|---|
[] |
once, on mount (+ cleanup on unmount) | set document.title, init a 3rd-party widget, append a marker |
[a, b] |
on mount + whenever a or b change (Object.is) |
re-sync when a prop/state changes (e.g. [roomId] → reconnect) |
| no array | after every render | almost never wanted — risks an infinite loop if it sets state |
Don't choose your deps. React (and the linter) expect every value the effect reads to be in the array. If you don't want a re-run, edit the effect code so it stops needing that value — never lie to the array (that's how stale-closure bugs are born).
1 · the JSX you write (edit me)
The mount effect has [] deps, so it fires exactly once after
the first paint and drops the green marker into
#effect-sink (a node outside the React tree — that is
what makes the side effect observable by the gold-check). The list maps
over 4 todos with stable id keys. The &&
conditional renders nothing while show is false.
2 · live render — list + effect + conditional
createRoot(#root).render(<App/>) mounts the tree. The
list (4 keyed items) appears immediately. After paint, the mount
effect fires and writes the green marker into #effect-sink.
Click show details to toggle the conditional element in/out.
list items rendered: 0 · effect markers: 0 · conditional element: —