ReviseAlgo Logo

Hooks

useReducer

Master the useReducer Hook, covering state machines, reducer pure functions, action dispatching, complex state containers, and useState comparisons.

Last Updated: July 15, 2026 12 min read

1. Learning Objectives

In this lesson, you will master React's hook for complex state management. By the end of this topic, you will be able to:

  • Differentiate when to use useState versus useReducer.
  • Write pure reducer functions to manage state transitions.
  • Dispatch actions containing type identifiers and payloads.
  • Model complex state objects and state machines cleanly.
  • Avoid mutating state inside reducer bodies.

2. Overview

The useReducer Hook is an alternative to useState used for managing complex state structures, or state updates that depend on other state properties. It models state updates using a **unidirectional flow**: components dispatch actions describing what happened, and a pure **reducer function** computes the next state.

3. Why This Topic Matters

Using `useReducer` keeps state updates predictable and organized:

  • Complex State Structures: When a component's state contains multiple nested fields (like user preferences, shopping carts, and input fields), writing multiple `useState` setters becomes difficult to manage. `useReducer` groups all state updates in a single function.
  • Separation of Concerns: Grouping all state update logic in a separate reducer function separates layout rendering logic from business logic.

4. Real-World Analogy

Think of useReducer like **ordering from a cafe kitchen**:

  • useState (Self-Service): You go into the kitchen and prepare the sandwich yourself: "Get bread, add ham, add cheese, toast." You directly manage each step.
  • useReducer (Cafe Order): You look at the menu (actions), select an item, and hand the order ticket (the action object) to the cashier (dispatch). The chef (the reducer function) reads the order and prepares the sandwich using the cafe's standard recipe. The chef handles the steps, and you simply receive the sandwich.

5. Core Concepts

Below is a comparison of `useState` and `useReducer` use cases:

Feature useState useReducer
State Structure Simple values (numbers, strings, flat arrays). Complex, nested objects or state machines.
Update Logic Location Declared inline inside component click handlers. Centralized in a separate, pure reducer function.
Dependent State Updates Requires multiple state setters, risking race conditions. Updates multiple fields atomically in the reducer.
Testing Tested by rendering components. Easy to unit test as a pure JavaScript function.

6. Syntax & API Reference

This example shows how to declare actions and define reducer functions in React:

7. Visual Diagram

This diagram displays the unidirectional data flow cycle inside the `useReducer` Hook:

8. Live Example — Full Working Code

Below is a complete HTML page demonstrating a Todo List built using `useReducer`:

What just happened? Clicking a task dispatches a TOGGLE_TODO action, passing the task's ID as the payload. Clicking the delete cross dispatches a REMOVE_TODO action. The todoReducer function processes these actions, returning a new state array reference to trigger a clean re-render.

9. Interactive Playground

Try It Yourself Challenges:

  1. Add a new action type named CLEAR_COMPLETED to filter out and remove all tasks that are marked as completed.
  2. Modify the reducer to throw an error if an unhandled action type is dispatched, helping you catch typos.

10. Common Mistakes

Mistake Why it happens Wrong Correct
Mutating state inside the reducer function Reducers must be pure functions. Mutating state arguments directly prevents React from detecting the change, so the UI fails to update. case 'add':
state.todos.push(todo);
return state;
case 'add':
return [...state.todos, todo];
(returns a new array reference)
Omitting default return cases in reducer switch statements If an action type is dispatched that does not match any of the switch cases, omitting the default case can cause the reducer to return undefined, breaking the state. switch(action.type) {
case 'add': return ...;
}
(missing default)
default:
return state;
(or throw an error to catch unhandled action types)

11. Best Practices

  • Keep reducers pure: Reducers must be pure, deterministic functions. Never make API calls, run side-effects, or generate random values (like `Math.random()`) inside a reducer.
  • Use descriptive action types: Use uppercase strings for action types (e.g. `'ADD_TODO'`, `'REMOVE_TODO'`) to make them easy to read.
  • Match standard action schemas: Standardize your action structures using `{ type, payload }` schemas.
  • Throw on invalid action types: Throw an error in the default case of your reducer (e.g. `throw new Error()`) to catch typos in action types during development.

12. Browser Compatibility/Requirements

The useReducer Hook is supported in all browsers that run React.

13. Interview Questions

Q1: When should you use `useReducer` instead of `useState` inside a component?

Answer: Use `useReducer` when: (1) state updates are complex or depend on multiple nested values, (2) the next state depends on the previous state in complex ways, (3) updating one state field requires updating other fields atomically, or (4) you want to separate state update logic from UI rendering logic.

Q2: Why must a reducer function be pure? What happens if you run side-effects (like fetch requests) inside a reducer?

Answer: Reducers must be pure to ensure state updates are predictable and deterministic. Since React can execute reducers multiple times during rendering optimizations, running side-effects (like fetch requests or timers) inside a reducer would cause them to run repeatedly, leading to duplicate API requests, memory leaks, and unpredictable UI behavior. Side-effects must be handled inside event handlers or `useEffect` Hooks instead.

14. Debugging Exercise

Identify the bug in this reducer that prevents new items from appearing in the list:

View Solution

Diagnosis: The reducer mutates the state array directly using `state.push()`. Since the array reference does not change, React determines that the state is unchanged and skips the re-render cycle.

Fix: Return a new array reference using the spread operator:

15. Practice Exercises

Exercise 1: Build a shopping cart reducer

Build a component named ShoppingCart. Define actions for ADD_PRODUCT, REMOVE_PRODUCT, and UPDATE_QUANTITY, and manage the shopping cart array using `useReducer`.

16. Scenario-Based Challenge

The Multi-Step Form Wizard Reducer:

You are designing a multi-step checkout form (shipping details, payment options, order review). If the user leaves the page or changes their inputs, you must validate and update fields atomically. Describe how you would use useReducer to manage the form state, and how you would handle resetting fields if validation fails.

17. Quick Quiz

Q1: Which method is called to send action objects to a reducer function?

A) dispatch()

B) submit()

C) execute()

Answer: A — dispatch() is the function used to send action objects to the reducer, triggering a state update cycle.

18. Summary & Key Takeaways

  • The useReducer Hook is used to manage complex component state structures.
  • Reducers are pure functions that compute the next state based on the current state and an action.
  • Components dispatch action objects containing `type` and `payload` properties to trigger state updates.
  • Never mutate state directly inside a reducer; always return a new state reference.

19. Cheat Sheet

useReducer API Element Purpose / Description
const [state, dispatch] = useReducer(reducer, initial); Initializes a state reducer and dispatch function.
dispatch({ type: 'ADD', payload: data }) Dispatches an action to trigger a state update.
function reducer(state, action) { ... } Computes the next state based on the action type.
---