Field note / react

publishedupdated 2026-05-04#react#hooks#useeffect#useref#usememo

React Hooks & Effects

A practical guide to hook ordering, effect discipline, refs, memoization, and the stale-closure bugs that show up in real React code.

TL;DR

  • Hooks are just an API layered on top of React’s render model, so I use them with that model in mind.
  • useEffect is for synchronizing with external systems, not as a general “after render” dump bucket.
  • Most hook bugs are dependency bugs, stale closures, or state-ownership mistakes.
  • useMemo, useCallback, and useRef are powerful only when I understand exactly what stability or mutability they are buying me.

Context

Hooks made React code cleaner for me, but they also made it easier to hide complexity in function bodies. I keep the mental model simple: state lives with renders, refs persist without re-rendering, and effects connect React to the outside world.

The Approach

I usually split hooks into four buckets:

  • state: useState, useReducer
  • refs: useRef
  • effects: useEffect, useLayoutEffect
  • memoization: useMemo, useCallback

The stale closure bug is the one I see most often:

useEffect(() => {
  const id = setInterval(() => {
    setCount(count + 1)
  }, 1000)

  return () => clearInterval(id)
}, [])

The cleaner fix is:

useEffect(() => {
  const id = setInterval(() => {
    setCount((current) => current + 1)
  }, 1000)

  return () => clearInterval(id)
}, [])

I also try to avoid effects when a value can be derived during render:

const filtered = items.filter((item) => item.active)

That is simpler than storing filtered in state and synchronizing it with an effect.

I also keep three hook internals in mind:

  1. hooks are stored by call order, which is why they cannot live inside conditionals
  2. useLayoutEffect runs after DOM mutation but before paint, so I reserve it for measurement or flicker prevention
  3. useRef is my “latest value” tool when I need mutable current state without triggering another render

Trade-offs

  • Hooks make component logic composable, but they also make hidden dependencies easier to introduce.
  • Effects are necessary for subscriptions, timers, and network coordination, but overusing them makes components harder to reason about.
  • useLayoutEffect is useful, but it blocks paint and should stay rare.
  • Memoization can reduce work, but I only keep it when it clearly protects something expensive or stabilizes an API boundary.

References