Handbooks / React / Chapter 3

Hooks

7 pages · ~5 min✓ Reviewed

Builds on State and Props. Next up: Effects and Data Fetching.

ReactPage 1

Hooks Mental Model and Rules

1. What a hook is

  • A hook is a function whose name starts with use
  • Lets a function component keep state and effects across renders
  • React matches hooks to state by call order, not by name

2. The two rules

javascript
const [n, setN] = useState(0);
useEffect(() => {
  if (on) save(n);
}, [on, n]);

put the condition inside the hook, never around it

3. Each render is a snapshot

  1. Renderprops, state fixed
  2. Eventhandler runs
  3. setStateschedules render
  4. New rendernew values
  • Inside one render, count never changes
  • A state update does not change it; it schedules a new render
Remember

Same hooks, same order, every render. That order is how React finds your state.

ReactPage 2

useState and useEffect

1. useState

  • useState(0): initial value used first render only
  • useState(() => expensive()): lazy initializer
  • Updates are batched; value changes on the next render
  • Depends on previous value? Use a functional update
  • State is replaced, not merged: copy with spread, never mutate
javascript
setCount(c => c + 1);
setUser({ ...user, name });

new reference, so React sees the change

2. useEffect

DepsRuns
noneevery render
[]once on mount
[a, b]when a or b changes
  • Runs after paint
  • Return a cleanup: unsubscribe, clear timers, abort fetches
  • Cleanup runs before the next effect and on unmount
javascript
useEffect(() => {
  const id = setInterval(tick, 1000);
  return () => clearInterval(id);
}, []);
ReactPage 3

useRef, useMemo and useCallback

1. useRef: a box that never re-renders

  • useRef(init) returns a stable { current } object
  • Changing ref.current does not trigger a re-render
  • Use it for DOM nodes and mutable values: timer IDs, the previous value
jsx
const inputRef = useRef(null);
<input ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>
  Focus
</button>

ref={inputRef} fills current with the DOM node

2. useMemo vs useCallback

HookCachesRecomputes when
useMemo(() => compute(a, b), [a, b])a valuea or b changes
useCallback(fn, deps)a function identitydeps change
  • useCallback(fn, deps) equals useMemo(() => fn, deps)
  • Useful with React.memo children
  • Useful as an effect dependency

3. When to memoize

Hint, not a guarantee

Memoization is a performance hint. Add it only after measuring a real cost or an identity problem.

ReactPage 4

useReducer, useContext and Custom Hooks

1. useReducer

  • const [state, dispatch] = useReducer(reducer, init)
  • Prefer it over useState for related fields or many transitions
  • dispatch({ type: 'add', payload }) is the only way to change state
  • Reducer is pure: return a new object, never mutate
javascript
function reducer(state, action) {
  switch (action.type) {
    case 'add':
      return { ...state, items: [...state.items, action.payload] };
    default:
      return state;
  }
}

2. useContext

  • useContext(Ctx) reads the nearest <Ctx.Provider value>
  • Value changes → every consumer re-renders
  • So memoize the value object
javascript
const value = useMemo(() => ({ user, setUser }), [user]);
return <Ctx.Provider value={value}>{children}</Ctx.Provider>;

3. Custom Hooks

  • Function named useX that calls other hooks: useFetch, useLocalStorage
  • Each call gets its own independent state
Custom hookContext
Shareslogicstate
Two componentsseparate valuessame value
Logic, not state

Two components calling useFetch do not share values. To share them, put the state in context

ReactPage 5

Common Hook Mistakes

1. Stale and Looping Effects

  • Stale closure: missing deps read old state; list the deps or use a functional update
  • Infinite loop: setState in an effect with no deps, or an object/array literal in deps
  • Updater form setN(n => n + 1) needs no n in deps
javascript
useEffect(() => {
  const id = setInterval(() => setN(n => n + 1), 1000);
  return () => clearInterval(id);
}, []);

no stale n, no dep needed

2. Identity and Mutation

MistakeEffectFix
Literal {}, [] or () => {} in depsnew each render; breaks useMemo, useCallback, React.memomemoize it
arr.push(x) or obj.x = 1, then setState(same)same reference, no re-rendercopy: [...arr, x]

3. Fetch Races

  • No cleanup: a late response overwrites newer state
  • Use an ignore flag or AbortController
javascript
useEffect(() => {
  let ignore = false;
  fetch(url).then(r => r.json())
    .then(d => { if (!ignore) setData(d); });
  return () => { ignore = true; };
}, [url]);
Remember

Fresh deps, fresh copies, cleanup every effect.

ReactPage 6

React Hooks Cheat Sheet

1. State and effects

CallDoes
useState(init)[v, setV]; use setV(prev => next) when it depends on the previous value
useEffect(fn)runs every render
useEffect(fn, [])runs on mount only
useEffect(fn, [x])runs when x changes
  • Return a cleanup function from the effect
  • It runs before the next effect and on unmount

2. Refs, caches, reducers

HookRemember
useRefmutable box, no re-render; DOM nodes, persistent values
useMemocaches a value
useCallbackcaches a function
useReducerdispatch(action) into a pure reducer
useContextreads the nearest Provider value

3. Rules

  • Call hooks at the top level only: no if, loops, nested functions
  • Call them only in components or custom hooks
  • Custom hooks are named useX; each call has its own state
Remember

React matches hooks by call order, so keep that order identical on every render.

ReactPage 7

Check yourself

With n = 0, one click runs setN(n + 1); setN(n + 1);. What is n on the next render?
  • 1, not 2: both calls read the same snapshot, n = 0
  • setN(c => c + 1) twice gives 2
useEffect(() => { setInterval(() => setC(c + 1), 1000) }, []) starts with c = 0. What does c show after 5 seconds?
  • 1, and it stays there: the closure captured c = 0 on mount
  • Fix: setC(x => x + 1), and return a cleanup that clears the interval
items.push(x); setItems(items) shows no change on screen. Why?
  • Same reference, so React sees no change and skips the re-render
  • Fix: setItems([...items, x])