Capítulo 61 de 116

Chapter 61: useReducer

Core Idea

useReducer(reducer, initialArg, init?) is the API reference for the pattern introduced conceptually in Ch 31: it returns [state, dispatch], where dispatch(action) hands an action object to a pure reducer(state, action) function that computes and returns the next state.

Key Concepts

  • Signature: const [state, dispatch] = useReducer(reducer, initialArg, init?). reducer(state, action) must be pure and return the complete next state (not a partial patch). The optional third argument init(initialArg) lazily computes the actual initial state from initialArg, useful when computing the initial value is itself expensive and shouldn't re-run on every render.
  • dispatch has a stable identity across re-renders, like a useState setter — safe to omit from Effect/useCallback dependency arrays without causing stale-closure bugs.
  • dispatch calls are batched and queued the same way useState setters are (Ch 24) — several dispatches inside one event handler are processed in order against the reducer, producing one re-render, not one per dispatch.
  • A reducer's switch should be exhaustive — an unrecognized action.type reaching the default case is a real bug and should typically throw, not silently return unchanged state, so mistakes surface immediately during development.
  • Lazy initialization via init: useReducer(reducer, initialArg, init) calls init(initialArg) only once, on mount — contrast with computing the same expensive value inline as the second argument, which (if written as a function call rather than a literal) would re-run every render.

Code Examples

function tasksReducer(tasks, action) {
  switch (action.type) {
    case 'added': return [...tasks, { id: action.id, text: action.text }];
    case 'deleted': return tasks.filter(t => t.id !== action.id);
    default: throw Error('Unknown action: ' + action.type);
  }
}

const [tasks, dispatch] = useReducer(tasksReducer, []);
dispatch({ type: 'added', id: nextId(), text });
  • What it demonstrates: the canonical [state, dispatch] shape, with a pure reducer function handling every recognized action type and throwing on unrecognized ones.

Key Takeaways

  1. Reach for useReducer when related state updates would otherwise be scattered across many useState calls and handlers — it centralizes the "how state changes" logic in one testable function.
  2. Use the lazy init argument (not an inline function call in the second argument slot) for expensive initial-state computation.
  3. dispatch's stable identity is a genuine (not incidental) guarantee — rely on omitting it from dependency arrays.

Connects To

  • Ch 31 (Extracting State Logic into a Reducer): the conceptual walkthrough this reference formalizes.
  • Ch 33 (Scaling Up with Reducer and Context): combining this Hook with Context for shared state.
  • Ch 24 (Queueing a Series of State Updates): the same batching model this Hook's dispatch follows.