useReducer helps you:
✅ Separate state logic from UI.
✅ Manage related state values together.
✅ Make state updates more predictable.
✅ Improve testability and maintainability.
It follows the same concept as Redux — but locally, inside a component.
Instead of directly setting state, you:
const [state, dispatch] = useReducer(reducer, initialState)
Where:
state → current valuedispatch → function to trigger updatesreducer → pure function describing how state changesfunction counterReducer(state, action) {switch (action.type) {case "increment":return state + 1case "decrement":return state - 1case "reset":return 0default:return state}}function Counter() {const [count, dispatch] = React.useReducer(counterReducer, 0)return (<><p>Count: {count}</p><button onClick={() => dispatch({ type: "increment" })}>+</button><button onClick={() => dispatch({ type: "decrement" })}>-</button><button onClick={() => dispatch({ type: "reset" })}>Reset</button></>)}
useReducer Really ShinesYou’ll benefit most from useReducer when:
const initialState = {loading: false,data: null,error: null}
Managing this with multiple useState calls becomes messy.
For example:
Use useState when… | Use useReducer when… |
|---|---|
| Simple values | State is complex |
| Independent fields | Fields change together |
| Few transitions | Many transitions |
| UI-driven logic | Business logic heavy |