A counter with increment and decrement buttons can use one state value and two actions. The buttons dispatch instructions; a reducer decides how each instruction changes the count. section 10.2
Listing 10.5 Counter component with reducer
import { useReducer } from "react";
function reducer(state, { type }) { #1
switch (type) {
case "INCREMENT": #2
return state + 1; #2
case "DECREMENT": #2
return state - 1; #2
default:
return state;
}
}
function Counter() {
const [counter, dispatch] = useReducer(reducer, 0); #3
return (
<section>
<h1>Counter: {counter}</h1>
<div>
<button onClick={
() => dispatch({ type: "INCREMENT" }) #4
}>
Increment
</button>
<button onClick={
() => dispatch({ type: "DECREMENT" }) #4
}>
Decrement
</button>
</div>
</section>
);
}
function App() {
return <Counter />;
}
export default App;
In this counter, `dispatch({ type: "INCREMENT" })` asks the reducer to return `state + 1`. The decrement action returns `state - 1`. section 10.2
`const [state, dispatch] = useReducer(reducer, initialState)` starts with an initial state and supplies a dispatch function. Calling `dispatch` with an action passes that action and the current state to `reducer`, which returns the next state. Unlike a `useState` setter, dispatch tells the reducer what happened rather than directly supplying the replacement value. section 10.2
Keep a loader's state together
A loader needs a status, result, and error. Instead of coordinating separate setters when a request fails, hold these values in one state object. Actions such as `LOADING`, `FAILURE`, and `SUCCESS` let the reducer update the object for each event; failure and success actions can carry an error or result as their payload. Define each transition carefully: the illustrated reducer spreads the previous state, so an old result or error remains unless a case clears it. section 10.2.1 section 7.1.2
function reducer(state, { type, payload }) {
switch (type) {
case "INITIALIZE":
return {
status: "INITIALIZE", #1
result: null, #1
error: null, #1
};
case "LOADING":
return {
...state,
status: "LOADING", #2
};
case "FAILURE":
return {
...state,
status: "FAILURE", #3
error: payload, #3
};
case "SUCCESS":
return {
...state,
status: "SUCCESS", #4
result: payload, #4
};
}
}
The reducer switches on the action type and returns an object for each loader transition. Compare which fields each case resets, preserves, or replaces. section 10.2.1
Reach for `useReducer` when related values and their transitions become difficult to manage with separate `useState` calls. It is not required for every state update: a simple counter can use either hook. In a task manager, moving from `useState` to a reducer became convenient as the state grew more complex. section 7.1.2 section 13.7


