Episode 2 — React Frontend Architecture NextJS / 2.7 — Useful Hooks in React

2.7 — Useful Hooks in React: Quick Revision

Compact cheat sheet. Print-friendly. One glance per concept.

How to use this material:

  1. Use this for quick refreshers before interviews or coding sessions
  2. If a concept feels unfamiliar, revisit the full sub-topic file
  3. Focus on the code patterns — they're the most practical
  4. Test yourself: cover the right column and try to recall from the left

2.7.a — Understanding React Hooks

What Are Hooks?

ConceptKey Point
DefinitionFunctions starting with use that access React internals from function components
Three problems solved1) Logic reuse (replaced HOCs/render props) 2) Code organization (group by concern) 3) No more this confusion
Mental model shiftClass: "when does this run?" → Hooks: "what am I synchronizing with?"
Closure modelEach render has its own snapshot of state, props, handlers, effects
Internal mechanismLinked list indexed by call order (why rules exist)
Custom hooksCompose built-in hooks; each consumer gets independent state copy

Built-in Hooks Quick Reference

useState(init)              → [value, setter]     — UI-visible state
useEffect(fn, deps)         → void                — Side effects after paint
useContext(Context)          → value               — Read from Provider tree
useRef(init)                → { current }          — DOM refs + mutable values
useCallback(fn, deps)       → cachedFn             — Stable function reference
useMemo(() => val, deps)    → cachedValue          — Cached computation
useReducer(reducer, init)   → [state, dispatch]    — Complex state transitions
useId()                     → ":r0:" string        — SSR-safe unique ID
useTransition()             → [isPending, startFn] — Non-urgent updates
useDeferredValue(value)     → deferredValue        — Show stale while computing
useLayoutEffect(fn, deps)   → void                 — Before paint (DOM measure)

2.7.b — Rules of Hooks

The Two Rules

RuleMeaningWhy
Top level onlyNo hooks in if/for/nested functions/after early returnsReact uses call ORDER to match hooks to stored state
React functions onlyOnly in components or custom hooks (starting with use)Hooks attach to Fiber nodes — no component = no Fiber

Common Violations & Fixes

// ❌ Conditional hook          ✅ Conditional logic INSIDE hook
if (x) useEffect(fn);          useEffect(() => { if (x) { fn(); } }, [x]);

// ❌ Hook after return          ✅ Hooks before returns
if (!data) return null;         const [s, setS] = useState(0);
const [s, setS] = useState(0);  if (!data) return null;

// ❌ Hook in loop               ✅ Extract to child component
items.map(i => {                items.map(i => <Item key={i.id} />)
  const [v, set] = useState(0); function Item() { const [v, set] = useState(0); }
});

ESLint Plugin

{ "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "warn" }

Exhaustive Deps Fixes

WarningFix
Missing primitive depAdd to array
Missing function depMove inside effect OR wrap in useCallback
Missing object depWrap in useMemo OR move inside effect
Want latest without re-runUse useRef
Intentional fire-onceSuppress with // eslint-disable-next-line + comment

2.7.c — Commonly Used Hooks

useState Patterns

const [v, set] = useState(init);          // Basic
const [v, set] = useState(() => expensive); // Lazy init (runs once)
set(5);                                    // Direct value
set(prev => prev + 1);                     // Updater (chains correctly)
set(prev => ({ ...prev, age: 26 }));       // Object (must spread!)
PatternWhen
Direct value set(5)New state independent of previous
Updater set(prev => ...)New state depends on previous
Lazy init useState(() => ...)Initial value is expensive to compute
Object spreadUpdating one field of object state
Key reset <Comp key={id} />Reset ALL state when switching items

useEffect Patterns

useEffect(() => { ... });           // Every render
useEffect(() => { ... }, []);       // Mount only
useEffect(() => { ... }, [dep]);    // When dep changes
useEffect(() => {                   // With cleanup
  const sub = subscribe();
  return () => sub.unsubscribe();
}, []);
PatternCode
Fetch + abortconst ctrl = new AbortController(); fetch(url, {signal}) + cleanup ctrl.abort()
Debounced searchsetTimeout in effect + cleanup clearTimeout
Subscriptionsubscribe in effect + unsubscribe in cleanup
Event listeneraddEventListener + cleanup removeEventListener

useContext Pattern

const Ctx = createContext(null);
function Provider({ children }) {
  const value = useMemo(() => ({ ... }), [deps]); // Memoize!
  return <Ctx.Provider value={value}>{children}</Ctx.Provider>;
}
function useCtx() {
  const ctx = useContext(Ctx);
  if (!ctx) throw new Error('Must be inside Provider');
  return ctx;
}

useRef Quick Reference

Use CaseCode
DOM referenceconst ref = useRef(null); <div ref={ref} />
Timer IDconst timer = useRef(null); timer.current = setInterval(...)
Previous valueconst prev = useRef(); useEffect(() => { prev.current = value; });
Latest callbackconst cbRef = useRef(cb); cbRef.current = cb;
Render countconst count = useRef(0); count.current++;

useCallback vs useMemo Decision

Need it?
├── Passing fn to React.memo child → useCallback ✅
├── Used as useEffect dependency → useCallback ✅
├── Expensive computation → useMemo ✅
├── Object/array to memo'd child → useMemo ✅
├── Context provider value → useMemo ✅
└── Everything else → Neither ❌ (no benefit)

useReducer vs useState

useStateuseReducer
1-2 simple values3+ related values
Direct replacementsNamed action types
Simple logicComplex transitions
Test in componentTest reducer as pure function

Hook Return Values

useState     → [value, setter]           (array)
useReducer   → [state, dispatch]         (array)
useContext   → value                     (direct)
useRef       → { current: value }        (object)
useMemo      → cached value              (direct)
useCallback  → cached function           (direct)
useEffect    → void                      (nothing)
useId        → ":rN:" string             (direct)
useTransition→ [isPending, startFn]      (array)

Core Formulas

Hooks model:   UI = f(state)  — each render is a snapshot
Closure rule:  setTimeout captures render-time values, not latest
State update:  setX(prev => ...) chains correctly; setX(val) doesn't
Effect timing: render → paint → effect (non-blocking)
Layout timing: render → layoutEffect → paint (blocking)
Ref rule:      .current mutations don't trigger re-render
Context rule:  Provider value change → ALL useContext consumers re-render
Memo rule:     Don't memoize until you measure a problem

When in doubt: useState for visible data, useRef for invisible data, useEffect for external sync, useContext for tree data.