Back to roadmaps react Course

State Machine Management with useReducer

As component complexity grows, managing state using multiple useState calls becomes difficult to track. For states that depend on complex logical transitions, the useReducer hook is a cleaner alternative.


1. What is a Reducer?

A reducer is a pure JavaScript function that takes the current state and an action description object, and returns a brand-new state. It follows this signature:

// Signature of a reducer function
function reducer(state, action) {
  // Compute and return new state object based on action.type
}

2. Setting Up useReducer

The useReducer hook accepts a reducer function and an initial state, returning the current state and a dispatch function to trigger actions:

import { useReducer } from "react";

const [state, dispatch] = useReducer(reducer, initialState);

Let us implement a shopping cart reducer example:

import { useReducer } from "react";

const initialState = { items: [], total: 0 };

function cartReducer(state, action) {
  switch (action.type) {
    case "ADD_ITEM":
      return {
        ...state,
        items: [...state.items, action.payload],
        total: state.total + action.payload.price
      };
    case "REMOVE_ITEM":
      const filtered = state.items.filter(item => item.id !== action.payload.id);
      return {
        ...state,
        items: filtered,
        total: state.total - action.payload.price
      };
    case "CLEAR_CART":
      return initialState;
    default:
      throw new Error(`Unhandled action type: ${action.type}`);
  }
}

export function CartComponent() {
  const [state, dispatch] = useReducer(cartReducer, initialState);

  const addItem = () => {
    dispatch({
      type: "ADD_ITEM",
      payload: { id: Date.now(), name: "Book", price: 15 }
    });
  };

  return (
    <div>
      <p>Cart Items: {state.items.length}</p>
      <p>Total Cost: ${state.total}</p>
      <button onClick={addItem}>Add Item</button>
      <button onClick={() => dispatch({ type: "CLEAR_CART" })}>Clear</button>
    </div>
  );
}

3. When to Choose useReducer over useState

  • Complex State Objects: When your state contains nested objects or arrays that undergo various update operations.
  • Dependent States: When updating one state value requires changing another related state value at the same time.
  • Separation of Logic: When you want to separate the state update logic from the UI rendering code, making it easier to write unit tests.
Published on Last updated: