Learn / Frameworks / React / useReducer and Complex State

React · Lesson 8 of 15

useReducer and Complex State

Model state as events and a reducer when useState starts to sprawl.

  • Intermediate
  • 16 min read
  • 3 objectives

Before this lessonLesson 7: Data Fetching and Routing

What you will learn

  • Write a reducer
  • Dispatch actions
  • Choose useReducer vs useState

Your Progress

0 of 15 lessons 0%

  • Lessons0 / 15
  • Completed0
  • Est. time left~ 4 hours

Create a free account to keep your progress on every device.

When a component has several related pieces of state that update together, a pile of useState calls becomes hard to follow. useReducer puts the next-state logic in one function: you dispatch an event, the reducer returns the new state.

A reducer is a pure function

It takes the current state and an action, and returns the next state. It must not mutate the previous object.

function cartReducer(state, action) {
  switch (action.type) {
    case "add":
      return { ...state, items: [...state.items, action.item] };
    case "remove":
      return { ...state, items: state.items.filter((i) => i.id !== action.id) };
    case "clear":
      return { ...state, items: [] };
    default:
      throw new Error(`Unknown action ${action.type}`);
  }
}

Wiring it up

import { useReducer } from "react";

const initial = { items: [] };

function Cart() {
  const [state, dispatch] = useReducer(cartReducer, initial);
  const total = state.items.reduce((sum, i) => sum + i.price, 0);
  return (
    <>
      <button onClick={() => dispatch({ type: "add", item: { id: 1, price: 9 } })}>
        Add
      </button>
      <button onClick={() => dispatch({ type: "clear" })}>Clear</button>
      <p>{state.items.length} items · ${total}</p>
    </>
  );
}

dispatch is stable across renders, so you can pass it down without wrapping it in useCallback.

When to prefer it

  • Several fields change together (a form wizard, a cart, a game board).
  • The next state depends on the previous one in a non-trivial way.
  • You want the update rules in one testable function, outside the component.
// Write your solution here
Up next · Lesson 9Performance: memo, useMemo and useCallbackSkip wasted renders and expensive work without premature optimisation.