How to Update State with useReducer Hook in React

The useReducer hook is a powerful tool in React for managing complex state logic in functional components. This article provides a straightforward guide on how to update state using useReducer, explaining the roles of the reducer function, the dispatch action, and how to trigger state changes with practical code examples.

Understanding the useReducer Syntax

The useReducer hook is an alternative to useState. It is local to the component and accepts two main arguments: a reducer function and an initial state.

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

Step 1: Define the Initial State and Reducer Function

The reducer function takes the current state and an action as arguments and returns the new state. By convention, actions are objects with a type property (a string) and an optional payload (additional data).

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'set':
      return { count: action.payload };
    case 'reset':
      return initialState;
    default:
      throw new Error(`Unhandled action type: ${action.type}`);
  }
}

Step 2: Initialize the Hook in Your Component

Call useReducer inside your React component, passing the reducer function and the initial state you defined.

import React, { useReducer } from 'react';

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

  return (
    // JSX goes here
  );
}

Step 3: Dispatch Actions to Update State

To update the state, call the dispatch function. Pass an action object containing the type that matches one of the cases in your reducer switch statement.

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

  return (
    <div>
      <p>Count: {state.count}</p>
      
      {/* Updating state by incrementing */}
      <button onClick={() => dispatch({ type: 'increment' })}>
        Increment
      </button>

      {/* Updating state by decrementing */}
      <button onClick={() => dispatch({ type: 'decrement' })}>
        Decrement
      </button>

      {/* Updating state using a payload */}
      <button onClick={() => dispatch({ type: 'set', payload: 10 })}>
        Set to 10
      </button>

      {/* Resetting state */}
      <button onClick={() => dispatch({ type: 'reset' })}>
        Reset
      </button>
    </div>
  );
}

Key Rules for Updating State

  1. State is Read-Only (Immutable): Do not modify the existing state object directly inside the reducer. Always return a new object (e.g., return { ...state, value: newValue } instead of state.value = newValue).
  2. Pure Functions: The reducer function must be pure. It should only calculate the next state based on the inputs (state and action) and must not trigger side effects like API calls inside the reducer.