useReducer is a React Hook designed for components whose state involves multiple related values or clear action-based transitions. Instead of scattering five setState setters across event handlers, you write one pure (state, action) => newState reducer function and call dispatch({ type: '...' }) to trigger updates.
A reducer takes the current state and an action object, then returns the next state without mutating the original object. Grouping updates inside a switch statement makes business logic easy to read and unit-test outside React.
import { useReducer } from 'react';
function cartReducer(state, action) {
switch (action.type) {
case 'ADD_ITEM': {
const existing = state.items.find((i) => i.id === action.item.id);
if (existing) {
return {
items: state.items.map((i) =>
i.id === action.item.id ? { ...i, qty: i.qty + 1 } : i
),
};
}
return { items: [...state.items, { ...action.item, qty: 1 }] };
}
case 'CLEAR_CART':
return { items: [] };
default:
return state;
}
}
export function CartPanel() {
const [state, dispatch] = useReducer(cartReducer, { items: [] });
return (
<section>
<p>Cart items: {state.items.length}</p>
<button
type="button"
onClick={() => dispatch({ type: 'ADD_ITEM', item: { id: 1, name: 'Keyboard' } })}
>
Add Keyboard
</button>
<button type="button" onClick={() => dispatch({ type: 'CLEAR_CART' })}>
Clear
</button>
</section>
);
}
useReducer Over useStateReach for useState for simple booleans, counters, or single form strings. Switch to useReducer when one user action updates three or more interdependent fields (like loading, data, error, and page) or when passing dispatch down to deeply nested children via Context.
useReducer Mistakesstate Inside a Case Block: Running state.items.push(...) and returning state mutates existing state in place, causing React to skip re-rendering. Always return a brand-new object or array.fetch, localStorage, or random functions inside a reducer; run side effects in event handlers or useEffect.dispatch change identity between renders?No. React guarantees that the dispatch function stays stable across renders, so you can safely omit it from useEffect dependency arrays.
useReducer?Yes. Pass an optional third argument (useReducer(reducer, initialArg, initFn)), and React calls initFn(initialArg) once on mount.
useReducer work with TypeScript discriminated unions?Yes. Typing action as a discriminated union gives autocomplete and exhaustive checks inside your switch statement.
Sign in to track your learning journey, earn industry-recognized certificates, and join our elite developer community.
Quick Access With