Frontend / React / 15_useful_hooks.md

The hooks, and which note goes deep on each

Updated 4 interview angles 5 min read source
On this page9
  1. State and effects
  2. Memoisation, after the compiler
  3. Context and external state
  4. Concurrent rendering
  5. React
  6. Writing a custom hook
  7. Where it breaks in production
  8. Related
  9. Interview angle

The hooks, and which note goes deep on each

A map, not a tutorial. Each hook gets what it is for, the trap that comes up in interviews, and where the depth lives. The deep notes are the ones to read; this page is for placing a hook you have half-forgotten.

Two facts frame everything below. Hooks are stored by call order, which is why the Rules of Hooks exist — see Stale Closures and the Rules of Hooks. And the React Compiler is the baseline for new projects, which turned most useMemo/useCallback advice into history — see React Compiler.

State and effects

Hook For The trap
useState local state updates are batched; read stale
useReducer state with transitions reducer must be pure
useEffect sync with something external missing cleanup, wrong deps
useLayoutEffect measure before paint blocks paint; rarely right
useRef a box that survives renders changing it does not re-render

useState — the update is queued, not applied, so setCount(count + 1) twice in one handler adds one. Use the functional form when the next value depends on the previous:

tsx
setCount((c) => c + 1);   // correct twice in a row

useReducer earns its place when several pieces of state change together and the transitions have names — { type: "submit" } reads better than four setX calls, and the reducer is testable without React.

useEffect is for synchronising with something outside React: a subscription, a timer, a DOM API. It is not for deriving state from props — compute that during render. Deps, cleanup and the fetch-in-effect trap are in useEffect Deep — Cleanup, StrictMode Double-Invoke, Dep-Array Bugs, Effect Events.

useRef holds a mutable value across renders without causing one. DOM refs are the obvious use; the less obvious one is holding the latest callback or an interval id. Detail in useRef Hook in React.

Memoisation, after the compiler

useMemo, useCallback and React.memo still exist, and the compiler now inserts the equivalent automatically in most cases. Reach for them by hand only where the compiler cannot see the dependency — an expensive computation whose inputs it cannot prove stable — and measure first. Memoization Heuristics — When useMemo/useCallback/memo Help vs Hurt has the heuristics; React.memo has the mechanism.

Context and external state

useContext reads a value provided above. Every consumer re-renders when the value changes, so a context holding a large, frequently changing object is the classic performance problem — split contexts, or move server state to a data library. See Context Performance Traps.

useSyncExternalStore is how a store outside React (Zustand, Redux, a browser API) integrates without tearing under concurrent rendering. You rarely call it; the libraries do. Knowing why it exists is the interview point.

Concurrent rendering

useTransition marks an update as non-urgent so React can keep the UI responsive — typing in a filter while a large list re-renders behind it. useDeferredValue is the same idea for a value rather than an update. Both are in Concurrent Rendering — useTransition, useDeferredValue, How Priorities Work.

useId generates ids stable across server and client, for aria-* and htmlFor. Never use it for list keys.

React 19

Four additions that change how forms and data loading are written; the deep treatment is React 19 — Actions, useActionState, useOptimistic, use(), ref as Prop.

  • use() reads a promise or a context, and unlike other hooks may be called conditionally. It is what makes Suspense-for-data ergonomic.
  • useActionState wraps a form action: state, the action to pass to <form action>, and a pending flag, in one call.
  • useOptimistic shows the expected result while the action is in flight and reverts on failure, without a cache layer.
  • useFormStatus (from react-dom) lets a submit button inside a form know the form is pending without prop drilling.

Writing a custom hook

A custom hook is a function that calls hooks. That is the entire definition, and the discipline is that it should package one concern:

tsx
function useDebounced<T>(value: T, ms = 300): T {
  const [debounced, setDebounced] = useState(value);
  useEffect(() => {
    const id = setTimeout(() => setDebounced(value), ms);
    return () => clearTimeout(id);      // the cleanup is the point
  }, [value, ms]);
  return debounced;
}

The interview follow-up is always the cleanup: without clearTimeout, every keystroke schedules a timer and the last one wins only by accident.

Three that are worth having in your head because they come up: a debounce as above, usePrevious (a ref updated in an effect), and a fetch hook — which is the one to not write, because it is what TanStack Query is; see TanStack Query and server state.

Where it breaks in production

  • A hook inside a condition or loop. Call order changes between renders, and React reads the wrong slot. The lint rule exists for this.
  • useEffect with no cleanup on anything that subscribes. Leaks and double-firing under Strict Mode’s mount-unmount-mount.
  • State derived in an effect. useEffect(() => setTotal(a + b), [a, b]) is a render behind and a re-render too many. Compute it inline.
  • useLayoutEffect on the server. It warns and does nothing; use it only for DOM measurement that must precede paint.
  • useId as a key. It is stable across hydration, not across list reorders. Keys come from the data.

Interview angle 4

  • “Why can’t hooks be called conditionally?” - React stores hook state by call order, so a skipped call shifts every later hook into the wrong slot. Follow-up: which hook is the exception? use(), by design.
  • “useMemo everywhere?” - not since the compiler. It memoises what it can prove stable; hand-written memoisation is for the cases it cannot see, and only after measuring. Reflexive useCallback is now a signal of outdated habits.
  • “When would you write a custom hook?” - when one concern - a debounce, a subscription, a media query - recurs across components. Not for fetching: that is a cache problem and a library already solves it.
  • “useEffect or compute during render?” - if the value derives from props or state, compute it. An effect is for synchronising with something outside React, and using it to set state is a render behind every time.