useReducer HookThe 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.
useReducer WorksuseReducer takes two main arguments:
It returns an array containing:
const [state, dispatch] = useReducer(reducerFunction, initialState);
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.
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(); } }
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>} <<span style="color: #569CD6;">button</span> <span style="color: #008c8f;">onClick</span>={() <span style="color: #569CD6;">=></span> <span style="color: #B22222;">dispatch</span>({ <span style="color: #008c8f;">type</span>: <span style="color: #CE9178;">'increment'</span> })}> Add Item </<span style="color: #569CD6;">button</span>> <<span style="color: #569CD6;">button</span> <span style="color: #008c8f;">onClick</span>={() <span style="color: #569CD6;">=></span> <span style="color: #B22222;">dispatch</span>({ <span style="color: #008c8f;">type</span>: <span style="color: #CE9178;">'decrement'</span> })}> Remove Item </<span style="color: #569CD6;">button</span>> <<span style="color: #569CD6;">button</span> <span style="color: #008c8f;">onClick</span>={() <span style="color: #569CD6;">=></span> <span style="color: #B22222;">dispatch</span>({ <span style="color: #008c8f;">type</span>: <span style="color: #CE9178;">'reset'</span> })}> Empty Cart </<span style="color: #569CD6;">button</span>> </<span style="color: #569CD6;">div</span>>); }
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 useReducerWhen should you choose useReducer over useState?
Use useState when:
Use useReducer when:
isLoading to true, clear error, fetch data. Later: set isLoading false, set data, or set error). A reducer handles these combined updates elegantly in a single dispatch.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.
What is the primary purpose of the dispatch function returned by useReducer?