← Back to React Course | Chapter 5: Core Hooks | Lesson 5 of 12

useReducer Hook

useReducer is like a vending machine for state -- you insert an action, and a single set of rules decides exactly what the new state should be.

Writing a Reducer Function

A reducer is a plain function that takes the current state and an action, then returns the new state -- similar to how Array.reduce works. It centralizes all the logic for how state changes in one place, rather than scattering setter calls throughout the component.

Note: A reducer should always return a brand-new state object/value, never mutate the one it received.

Warning: Forgetting a default: return state; case means dispatching an unrecognized action type silently does nothing, which can be confusing to debug.

Example: Writing a Reducer Function

markup
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
    function counterReducer(state, action) {
      switch (action.type) {
        case "increment": return state + 1;
        case "decrement": return state - 1;
        default: return state;
      }
    }
    function Counter() {
      const [count, dispatch] = React.useReducer(counterReducer, 0);
      return (
        <div>
          <p>{count}</p>
          <button onClick={() => dispatch({ type: "increment" })}>+</button>
          <button onClick={() => dispatch({ type: "decrement" })}>-</button>
        </div>
      );
    }
    ReactDOM.createRoot(document.getElementById('root')).render(<Counter />);
  </script>
</body>
</html>

Dispatching Actions

Instead of calling a setter function directly, useReducer gives you a dispatch function -- you call it with an action object describing what happened, and the reducer decides how state should change in response. This separates 'what happened' from 'how state changes because of it'.

Note: Action objects conventionally have a type field describing the event, plus any extra data the reducer needs (a payload).

Warning: Dispatch does not directly set state -- it's the reducer function that computes the actual new state from the action.

Example: Dispatching Actions

markup
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
    function reducer(state, action) {
      switch (action.type) {
        case "setName": return { ...state, name: action.payload };
        default: return state;
      }
    }
    function NameForm() {
      const [state, dispatch] = React.useReducer(reducer, { name: "" });
      return (
        <div>
          <input onChange={(e) => dispatch({ type: "setName", payload: e.target.value })} />
          <p>Name: {state.name}</p>
        </div>
      );
    }
    ReactDOM.createRoot(document.getElementById('root')).render(<NameForm />);
  </script>
</body>
</html>

When to Choose useReducer over useState

useReducer shines when a component's state involves several related values that change together, or when the next state depends on complex logic based on the previous state. For simple, independent values, useState remains simpler and more direct.

Note: If you find a component's state updates involve a lot of interconnected if logic, that's a signal useReducer might organize it better than several useState calls.

Warning: Reaching for useReducer for a single boolean toggle or simple counter adds unnecessary complexity compared to a plain useState.

Example: When to Choose useReducer over useState

markup
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
    function formReducer(state, action) {
      switch (action.type) {
        case "updateField": return { ...state, [action.field]: action.value };
        case "reset": return { name: "", email: "" };
        default: return state;
      }
    }
    function SignupForm() {
      const [state, dispatch] = React.useReducer(formReducer, { name: "", email: "" });
      return (
        <div>
          <input placeholder="Name" onChange={(e) => dispatch({ type: "updateField", field: "name", value: e.target.value })} />
          <p>Name: {state.name}</p>
        </div>
      );
    }
    ReactDOM.createRoot(document.getElementById('root')).render(<SignupForm />);
  </script>
</body>
</html>
Common Mistakes
  1. Writing a reducer function that mutates the existing state instead of returning a brand-new state object.
  2. Forgetting to include a default case in the reducer's switch statement, silently ignoring unknown actions.
  3. Reaching for useReducer for very simple state that a plain useState would handle just as well.
Chapter Summary
  • useReducer is an alternative to useState for managing more complex state logic.
  • It takes a reducer function and an initial state, returning the current state and a dispatch function.
  • You update state by dispatching action objects, not by calling a setter directly.
  • It's especially useful when state updates depend on multiple related pieces of logic.
Browser Support

Available since React 16.8, when Hooks were introduced.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.