React Foundations·Components, state and data

State with useReducer

Replace tangled useState calls with a reducer when updates follow many related transitions.

Intermediate 13 min read Lesson 3 of 300 Challenge included
What you will be able to do
  • Recognise when several state values move together.
  • Model each transition as an action with a type field.
  • Write a pure reducer that returns new state without mutating.
  • Dispatch actions from event handlers instead of calling many setters.

In plain English

When a component juggles many pieces of state that change together, useState calls start to tangle. A reducer is a pure function that takes the current state and an action, then returns the next state. An action is a plain object that describes what happened, usually with a type field. useReducer hands you the state and a dispatch function, so every transition is explicit and grouped in one place.

Step by step

  1. 1List every event the component can receive.
  2. 2Define one action type for each event, carrying only the needed data.
  3. 3Write the reducer as a pure switch over the action types.
  4. 4Replace the setters with dispatch calls in the handlers.
Quiz.tsxTSX
import { useReducer } from 'react'

type QuizState = {
  index: number
  answers: Record<number, number>
}

type QuizAction =
  | { type: 'answer'; question: number; choice: number }
  | { type: 'next' }
  | { type: 'reset' }

function reducer(state: QuizState, action: QuizAction): QuizState {
  switch (action.type) {
    case 'answer':
      return { ...state, answers: { ...state.answers, [action.question]: action.choice } }
    case 'next':
      return { ...state, index: state.index + 1 }
    case 'reset':
      return { index: 0, answers: {} }
  }
}

function Quiz() {
  const [state, dispatch] = useReducer(reducer, { index: 0, answers: {} })

  function choose(choice: number) {
    dispatch({ type: 'answer', question: state.index, choice })
  }

  return <p>Question {state.index + 1}</p>
}

Common mistakes

  • Reaching for useReducer for a single boolean is unnecessary and makes the component harder to read.
  • Mutating the current state inside a reducer means React cannot see that anything changed.
  • Putting side effects such as fetching inside the reducer breaks its purity and causes repeated work.
  • Copying one logical update across many separate useState calls makes some transitions impossible to keep consistent.
Your turn

Add a reset action

Medium

Extend a quiz reducer with a reset action that returns the initial state, and a button that dispatches it.

TSX · starter
type QuizState = { index: number; score: number }

function reducer(state: QuizState, action: { type: 'correct' }): QuizState {
  if (action.type === 'correct') {
    return { ...state, score: state.score + 1 }
  }
  return state
}
Check your understanding before moving on.
Knowledge checkQuestion 1

What must a reducer be?

Select an answer to continue
Study tools

Make it yours.

Keep a private note for this lesson, then turn the key ideas into flashcards you can revisit.

Sign in to save notes and flashcards.Both are private to your account and stay with your progress.
Sign in
Finished reading?

Lock in what you just learned.

Mark this lesson complete to save your place and move one step closer to the certificate.

Lesson 3 of 300 · 0% of course