Back to React
React
medium
mid

Can you explain the most important React hooks you use day to day?

The five hooks that cover 95% of component code: useState (local reactive state), useEffect (subscriptions/side-effects with cleanup), useRef (mutable values that survive re-renders + DOM access), useMemo (cache an expensive value across renders), useCallback (cache a function identity for memoized children).

7 min read·~10 min to think through

Five hooks every React engineer should know cold.

1. useState — local component state

tsx
const [count, setCount] = useState(0);
const [user, setUser] = useState<User | null>(null);

setCount(c => c + 1); // functional updater — safe under batching
  • Returns [value, setter].
  • Setter triggers a re-render.
  • Pass an initializer function (useState(() => heavy())) for one-time expensive init.

2. useEffect — side effects with cleanup

tsx
useEffect(() => {
  const id = setInterval(tick, 1000);
  return () => clearInterval(id); // cleanup
}, []);
  • Runs after commit, not during render.
  • Cleanup runs before next effect AND on unmount.
  • Empty deps = mount/unmount only. Missing deps = stale closures.

3. useRef — mutable value, no re-render

tsx
const inputRef = useRef<HTMLInputElement>(null);
const renderCount = useRef(0);

renderCount.current++; // mutating .current does NOT trigger re-render

Two uses: DOM access (<input ref={inputRef} />) and instance variables that need to survive re-renders without causing them.

4. useMemo — cache a computed value

tsx
const sorted = useMemo(
  () => items.slice().sort(expensiveCompare),
  [items],
);

Skip the recomputation if deps haven't changed. Only worth it for genuinely expensive work or to keep referential equality for downstream memoization.

5. useCallback — cache a function identity

tsx
const onClick = useCallback(() => doThing(id), [id]);

Sugar for useMemo(() => fn, deps). Only useful when the function is passed to a React.memo'd child or used in another hook's deps — otherwise it's overhead.

When NOT to reach for them

  • useMemo/useCallback everywhere: profile first, the bookkeeping itself isn't free.
  • useState for derived values: compute in render instead — const fullName = first + ' ' + last.
  • useEffect for state-from-props: use a key to reset, or compute in render.

Beyond the five

useContext, useReducer, useTransition, useId, useSyncExternalStore, useDeferredValue round out the modern toolkit.

Follow-up questions

  • When would you choose useReducer over useState?
  • What's the difference between useMemo and useCallback?
  • How do you avoid stale closures in useEffect?

Common mistakes

  • Reading a ref's .current during render to drive UI — it doesn't trigger re-renders.
  • Adding useCallback/useMemo without a downstream memoized consumer — pure overhead.
  • Missing deps in useEffect, then suppressing the lint warning instead of fixing it.

Performance considerations

  • Hook calls themselves are cheap. The cost comes from re-renders triggered by useState and the closures captured by useEffect/useCallback. Profile before scattering useMemo — the dependency check + extra closure can cost more than the recomputation you're avoiding.

Edge cases

  • StrictMode in dev double-invokes effects — cleanup must be idempotent.
  • useState's initializer runs once even though the arg expression evaluates every render — wrap heavy init in a function.
  • useRef returns the same object across renders, but .current is mutable and not reactive.

Real-world examples

  • A form: useState for fields, useEffect to debounce a save, useRef for the imperative focus() on submit-fail, useMemo to sort a typeahead suggestion list, useCallback to pass a stable onChange to memoized inputs.

Senior engineer discussion

Senior signal is knowing when NOT to use them. State that can be derived shouldn't be useState. Effects that exist to sync state to state are usually a 'lift it up' smell. The React docs section 'You might not need an effect' is the standard reference.

Related questions