Pebellslearn by building
0%
RoadmapsMatrixCoursesSandboxSign in
State  ›  Lesson

useReducer — State Changes by Name

useReducer 16 minuseReducer moves state-update logic into one pure reducer function that takes the current state and an action and returns the next state.

useReducer — State Changes by Name

The reading list can now add a book, remove one, mark one as read, and clear finished ones. Each handler has its own setBooks(...) with its own copy-and-change logic. Spread across a component, that logic is hard to see as a whole. useReducer gathers it into one place.

The rule

Handlers say what happened; the reducer decides what changes.

import { useReducer } from 'react';

function booksReducer(books, action) {
  switch (action.type) {
    case 'added':
      return [...books, action.book];
    case 'removed':
      return books.filter(b => b.id !== action.id);
    case 'marked_read':
      return books.map(b => b.id === action.id ? { ...b, read: true } : b);
    default:
      throw new Error('Unknown action: ' + action.type);
  }
}

function ReadingList() {
  const [books, dispatch] = useReducer(booksReducer, initialBooks);

  return books.map(book => (
    <BookCard
      key={book.id}
      book={book}
      onRemove={() => dispatch({ type: 'removed', id: book.id })}
      onRead={() => dispatch({ type: 'marked_read', id: book.id })}
    />
  ));
}

Handlers say what happened. Instead of computing the next array, a handler dispatches an action: a plain object with a type and whatever details are needed. Past-tense names like 'added' read naturally.

The reducer decides what changes. booksReducer(state, action) receives the current state and the action and returns the next state. React calls it and re-renders with the result.

The reducer follows the same rules as rendering: it must be pure. No fetching, no timers, no mutating the old state — always return a new value. Because it is a plain function with no React inside, you can test it by calling it directly: booksReducer([], { type: 'added', book }) should return an array holding that one book.

The default case throws on an unknown action type. That turns a typo such as 'remove' instead of 'removed' into a loud error instead of a silent no-op, which is much easier to track down.

The shortcut

Choose by counting: one or two simple updates → useState; many kinds of update to the same data → useReducer. Both give identical results; the reducer just organises the logic.

Following one click through a reducerStep 1 of 4
  1. Click

    The user clicks Remove on Dune (id: 'b2').

A common slip: forgetting to return in a case, which makes the reducer return undefined and wipes the state.

Drills

Answer fast — trust the rule.

quick drill

What does useReducer(booksReducer, initialBooks) return?

quick drill

What does a reducer receive?

quick drill

Which reducer case is correct for 'added'?

quick drill

A case forgets its return, so the reducer returns undefined for that action. What happens?

Remember

  • Handlers say what happened; the reducer decides what changes.
  • Many kinds of update to the same data → useReducer.
  • Trap: mutating state, side effects, or a missing return inside the reducer.

Practice — level up

0/1 solved

Write your answer, then compare it with a rubric and a model answer.

ExplainFrom three setters to one reducer

The reading list adds, removes and marks books as read, each with its own setBooks(...) logic spread across handlers. Explain how you would move this into useReducer: what the reducer looks like, what the handlers do instead, and one rule the reducer must follow.