useReducer — Patterns and Best Practices
12 min read
Scaling State Logic with useReducer
useReducer shines when state transitions are complex, numerous, or need to be tested in isolation. Pairing it with useContext lets you build a lightweight global store without a third-party library.
Context + useReducer mini-store
jsx
import { createContext, useContext, useReducer } from "react";
const CartContext = createContext(null);
function cartReducer(state, action) {
switch (action.type) {
case "add":
return { ...state, items: [...state.items, action.item] };
case "remove":
return { ...state, items: state.items.filter(i => i.id !== action.id) };
case "clear":
return { items: [] };
default:
throw new Error("Unknown action: " + action.type);
}
}
export function CartProvider({ children }) {
const [cart, dispatch] = useReducer(cartReducer, { items: [] });
return (
<CartContext.Provider value={{ cart, dispatch }}>
{children}
</CartContext.Provider>
);
}
export function useCart() {
const ctx = useContext(CartContext);
if (!ctx) throw new Error("useCart must be used inside CartProvider");
return ctx;
}Initialising state lazily
useReducer accepts a third argument — an init function — to compute initial state lazily. This is useful when the initial state is expensive to compute, or when you want to share a reset action that re-derives the initial state from a prop.
jsx
function init(initialCount) {
return { count: initialCount };
}
function reducer(state, action) {
switch (action.type) {
case "increment": return { count: state.count + 1 };
case "reset": return init(action.payload);
default: return state;
}
}
function Counter({ initialCount }) {
const [state, dispatch] = useReducer(reducer, initialCount, init);
return (
<>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: "increment" })}>+1</button>
<button onClick={() => dispatch({ type: "reset", payload: initialCount })}>Reset</button>
</>
);
}Reducer functions must be pure — no side effects, no mutations of the existing state object. Always return a new object using spread ({ ...state, key: value }).
Ready to test yourself?
Practice React— quiz & coding exercises