When useReducer instead of useState?
useState is perfect for simple, independent values. But when the
state is complex (several related fields) or the updates follow
clear transitions (several distinct ways to change it), spreading the
logic across many setX calls becomes fragile and hard to follow.
useReducer concentrates all that logic in a single function: the reducer.
The reducer: (state, action) => nextState
A reducer is a pure function that receives the current state and an action, and returns the next state. It does not mutate the state: it builds and returns a new one.
function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
return state;
}
}
dispatch: sending actions
In the component, useReducer(reducer, initialState) returns a pair:
the current state and a function called dispatch. To change the state,
you "dispatch" an action describing what happened:
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: "increment" })}>+1</button>
<button onClick={() => dispatch({ type: "decrement" })}>-1</button>
</div>
);
}
The action is usually an object with a type property (and, if needed, extra
data such as payload). The advantage: the logic lives in a single place, it is easy
to read and to test, and the components only say what happened, not how the
state changes.
In the exercises,
useReduceris already available: use it directly, without importing it.