Technology · React

React useReducer Hook

Manage complex state logic with the useReducer hook, actions, reducers, and common patterns.

TL;DR
  1. 01Use useReducer for state with several related update rules.
  2. 02Write a reducer function that handles each action type.
  3. 03Dispatch actions to trigger state changes in components.

Basic Usage

  • Import useReducer from React at the top of your component file.
    import { useReducer } from "react";
  • Call useReducer with a reducer function and an initial state value.
    const [state, dispatch] = useReducer(reducer, { count: 0 });
  • The hook returns the current state and a dispatch function for actions.
  • Reducers are pure functions that take state and an action, then return new state.
  • Use this pattern when state updates depend on the current state.

Writing Reducers

  • Define a reducer function that handles each action type with a switch.
    function reducer(state, action) {
      switch (action.type) {
        case "increment":
          return { count: state.count + 1 };
        case "decrement":
          return { count: state.count - 1 };
        default:
          return state;
      }
    }
  • Always return a new state object instead of mutating the existing one.
  • Include a default case to handle unknown action types safely.
  • Keep reducers pure — no API calls, timers, or random values inside.
  • This makes state changes predictable and easy to test.

Dispatching Actions

  • Call dispatch with an action object to trigger a state update.
    <button onClick={() => dispatch({ type: "increment" })}>
      +
    </button>
  • Actions are plain objects with a required type field.
  • Add extra fields to pass data along with the action.
    dispatch({ type: "add", payload: 5 });
  • The reducer reads these fields to compute the new state.
  • Dispatch can be passed down to child components through props.

When to Use It

  • Reach for useReducer when state has many related update paths.
  • Use it when the next state depends on multiple pieces of current state.
  • Use it when actions need to be predictable and easy to test.
  • Use useState for simple booleans, strings, and counters instead.
  • Use Context plus reducer for app-wide state without external libraries.

Common Patterns

  • Initialize lazily with a third argument for expensive setup logic.
    const [state, dispatch] = useReducer(reducer, props, (p) => ({
      count: p.initial
    }));
  • Type actions with a discriminated union in TypeScript for safety.
    type Action =
      | { type: "increment" }
      | { type: "add"; payload: number };
  • Pair useReducer with useContext to share state across the tree.
  • Split large reducers into smaller helper functions for clarity.
  • Use useImmerReducer from libraries when deep updates feel tedious.

Tip: Pair useReducer with useContext to build a clean global store without pulling in Redux or Zustand for small apps.

Warning: Never mutate the state object directly inside a reducer, since React relies on a new reference to detect changes and re-render.

React useMemo and useCallbackReact Accessibility