Capítulo 33 de 116

Chapter 33: Scaling Up with Reducer and Context

Core Idea

Combining a reducer (Ch 31) with Context (Ch 32) lets any component in a subtree both read shared state and dispatch updates to it, without prop-drilling either the state or the dispatch function through every intermediate layer.

Key Concepts

  • Two contexts, one reducer: a common pattern splits the state value and the dispatch function into two separate Context objects (e.g. TasksContext and TasksDispatchContext) — components that only need to read state subscribe to one; components that only need to dispatch actions subscribe to the other, so a component that only dispatches doesn't re-render when the state itself changes.
  • Provider setup: a top-level component calls useReducer once, then wraps its subtree in both providers, passing tasks to one and dispatch to the other — every descendant can now reach either via useContext, however deeply nested.
  • Consuming: a deeply nested component calls useContext(TasksContext) to read the list, or useContext(TasksDispatchContext) to get dispatch and fire actions — neither requires any prop passed through components in between.
  • Moving all the wiring into one file: as this pattern grows, it's common to extract the reducer, both Context objects, the provider component, and small custom Hook wrappers (useTasks(), useTasksDispatch()) into a single dedicated module — consumers then import a clean Hook API instead of importing raw Context objects and calling useContext themselves everywhere.
  • This is a lightweight alternative to external state-management libraries for a subtree's shared state — it uses only built-in React features, at the cost of the boilerplate (two contexts, a reducer, wrapper Hooks) shown here.

Code Examples

// tasks-context.js — the "wiring into a single file" pattern
const TasksContext = createContext(null);
const TasksDispatchContext = createContext(null);

export function TasksProvider({ children }) {
  const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
  return (
    <TasksContext value={tasks}>
      <TasksDispatchContext value={dispatch}>
        {children}
      </TasksDispatchContext>
    </TasksContext>
  );
}

export function useTasks() { return useContext(TasksContext); }
export function useTasksDispatch() { return useContext(TasksDispatchContext); }
  • What it demonstrates: the full pattern consolidated into one module — reducer + two providers + two custom Hooks — so the rest of the app imports useTasks()/useTasksDispatch() instead of raw Context plumbing.

Key Takeaways

  1. Splitting state-context and dispatch-context is a real performance/clarity technique, not just style — dispatch never changes identity, so subscribing to only the dispatch context avoids unnecessary re-renders.
  2. Wrapping the provider + reducer + custom Hooks in one file gives consumers a clean useTasks()/useTasksDispatch() API, hiding the Context machinery entirely.
  3. This pattern is the natural ceiling of built-in React state management — past this scale of complexity, teams often evaluate a dedicated state library, but many apps never need to.

Connects To

  • Ch 31 (Extracting State Logic into a Reducer): the reducer this pattern wraps in Context.
  • Ch 32 (Passing Data Deeply with Context): the Context mechanics this chapter combines with a reducer.
  • Ch 41 (Reusing Logic with Custom Hooks): the useTasks()/useTasksDispatch() wrapper pattern generalized.