Technology · React
React useReducer Hook
Manage complex state logic with the useReducer hook, actions, reducers, and common patterns.
TL;DR
- 01Use useReducer for state with several related update rules.
- 02Write a reducer function that handles each action type.
- 03Dispatch actions to trigger state changes in components.
Basic Usage
- Import
useReducerfrom React at the top of your component file.import { useReducer } from "react"; - Call
useReducerwith a reducer function and an initial state value.const [state, dispatch] = useReducer(reducer, { count: 0 }); - The hook returns the current state and a dispatch function for actions.
- Reducers are pure functions that take state and an action, then return new state.
- Use this pattern when state updates depend on the current state.
Writing Reducers
- Define a reducer function that handles each action type with a switch.
function reducer(state, action) { switch (action.type) { case "increment": return { count: state.count + 1 }; case "decrement": return { count: state.count - 1 }; default: return state; } } - Always return a new state object instead of mutating the existing one.
- Include a default case to handle unknown action types safely.
- Keep reducers pure — no API calls, timers, or random values inside.
- This makes state changes predictable and easy to test.
Dispatching Actions
- Call
dispatchwith an action object to trigger a state update.<button onClick={() => dispatch({ type: "increment" })}> + </button> - Actions are plain objects with a required
typefield. - Add extra fields to pass data along with the action.
dispatch({ type: "add", payload: 5 }); - The reducer reads these fields to compute the new state.
- Dispatch can be passed down to child components through props.
When to Use It
- Reach for
useReducerwhen state has many related update paths. - Use it when the next state depends on multiple pieces of current state.
- Use it when actions need to be predictable and easy to test.
- Use
useStatefor simple booleans, strings, and counters instead. - Use Context plus reducer for app-wide state without external libraries.
Common Patterns
- Initialize lazily with a third argument for expensive setup logic.
const [state, dispatch] = useReducer(reducer, props, (p) => ({ count: p.initial })); - Type actions with a discriminated union in TypeScript for safety.
type Action = | { type: "increment" } | { type: "add"; payload: number }; - Pair
useReducerwithuseContextto share state across the tree. - Split large reducers into smaller helper functions for clarity.
- Use
useImmerReducerfrom libraries when deep updates feel tedious.
Tip: Pair
useReducerwithuseContextto build a clean global store without pulling in Redux or Zustand for small apps.
Warning: Never mutate the state object directly inside a reducer, since React relies on a new reference to detect changes and re-render.