Skip to content

Step 1 of 5 · Reading · ~2 min

Read

Hooks & State

The useEffect Hook

Render must be pure - it may run, be thrown away, and run again. Anything that reaches outside the component (network, timers, subscriptions, direct DOM work) goes in an effect, which runs after the commit.

javascript

The dependency array

  • [id] - re-run whenever id changes.
  • [] - run once on mount; clean up on unmount.
  • omitted entirely - run after every render. Almost never what you want.

Deps are compared item by item with Object.is, so a fresh object or arrow function in the list makes the effect re-run every time.

Cleanup

An effect may return a function. It runs before the next run of that same effect, and once more on unmount.

javascript

Unmount is the half people remember. The other half is the one that leaks: change roomId from 1 to 2 with no cleanup and you now hold two open sockets, both writing into the same component.

Storing an effect in a slot

javascript

That comparison is the exercise ahead: given the old deps and the new ones, decide whether this render runs the effect, skips it, or cleans up.

Traps

Leaving a value out of the deps freezes it at the render that created the closure - the stale-closure bug again. Putting an unstable value in re-runs the effect constantly. And two overlapping fetches can resolve out of order, so a slow response for the previous id overwrites the fast one for the current id; cleanup is where you cancel or invalidate it.

Newer APIs narrow the need for effects - useSyncExternalStore for external state, Suspense and Server Components for data loading - but the commit-then-effect ordering above is unchanged underneath all of them.

Up nextSignals & Fine-Grained ReactivityHooks & State

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…