React useReducer Hook

React useReducer Hook

The useReducer Hook is an alternative to useState. While useState is perfect for simple, independent variables (like a string or a boolean), useReducer is built for managing complex state logic where multiple sub-values depend on each other, or where the next state is deeply dependent on the previous state.

If you have ever used the popular state-management library Redux, you will find useReducer very familiar.


How useReducer Works

useReducer takes two main arguments:

  1. A Reducer Function: A custom function that takes the current state and an action, and returns the new state.
  2. An Initial State: The starting value of your state (often an object).

It returns an array containing:

  1. The current state.
  2. A dispatch function, which you use to send "actions" to the reducer to trigger state changes.
const [state, dispatch] = useReducer(reducerFunction, initialState);

Step-by-Step Example: A Shopping Cart

Let's look at how to build a counter that could represent items in a shopping cart. We want the ability to increment, decrement, and reset the counter.

1. Define the Initial State and Reducer

Outside of your component, you define the initial state and the logic for how the state updates based on specific actions.

import React, { useReducer } from 'react';

// 1. Initial State const initialState = { count: 0 };

// 2. Reducer Function // 'state' is the current state // 'action' is an object that tells the reducer what to do function reducer(state, action) { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': // Prevent negative counts return { count: state.count > 0 ? state.count - 1 : 0 }; case 'reset': return { count: 0 }; default: // If an unknown action is dispatched, throw an error or return current state throw new Error(); } }

2. Using the Hook in the Component

Inside the component, you initialize useReducer and use the dispatch function to send action objects. Action objects typically have a type property (a string describing the action).

function ShoppingCart() {
  // 3. Initialize the hook
  const [state, dispatch] = useReducer(reducer, initialState);

return ( <div> <h2>Items in Cart: {state.count}</h2>

  {<span style="color: #6A9955;">/* 4. Dispatch actions to update the state */</span>}
  &lt;<span style="color: #569CD6;">button</span> <span style="color: #008c8f;">onClick</span>={() <span style="color: #569CD6;">=&gt;</span> <span style="color: #B22222;">dispatch</span>({ <span style="color: #008c8f;">type</span>: <span style="color: #CE9178;">'increment'</span> })}&gt;
    Add Item
  &lt;/<span style="color: #569CD6;">button</span>&gt;
  &lt;<span style="color: #569CD6;">button</span> <span style="color: #008c8f;">onClick</span>={() <span style="color: #569CD6;">=&gt;</span> <span style="color: #B22222;">dispatch</span>({ <span style="color: #008c8f;">type</span>: <span style="color: #CE9178;">'decrement'</span> })}&gt;
    Remove Item
  &lt;/<span style="color: #569CD6;">button</span>&gt;
  &lt;<span style="color: #569CD6;">button</span> <span style="color: #008c8f;">onClick</span>={() <span style="color: #569CD6;">=&gt;</span> <span style="color: #B22222;">dispatch</span>({ <span style="color: #008c8f;">type</span>: <span style="color: #CE9178;">'reset'</span> })}&gt;
    Empty Cart
  &lt;/<span style="color: #569CD6;">button</span>&gt;
&lt;/<span style="color: #569CD6;">div</span>&gt;

); }


Passing Data (Payloads) to the Reducer

Sometimes, your action needs to carry extra data to update the state properly (e.g., adding a specific number to the count, or passing user details). This data is conventionally passed in a property called payload.

// In your reducer:
case 'addAmount':
  return { count: state.count + action.payload };

// In your component: <button onClick={() => dispatch({ type: 'addAmount', payload: 5 })}> Add 5 Items </button>


useState vs useReducer

When should you choose useReducer over useState?


Pro Tip: Keeping your reducer function outside of the component prevents it from being recreated on every render and keeps your component code extremely clean and focused strictly on the UI.


Exercise

?

What is the primary purpose of the dispatch function returned by useReducer?