The State and Updating State lessons covered useState for most component state. useReducer is an alternative, better suited to state that changes through several distinct, well-defined actions rather than one simple value.
A reducer is a function that takes the current state and an action, and returns the new state — the same pattern this course's array .reduce() examples use, just applied to component state.
function counterReducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 }
case 'decrement':
return { count: state.count - 1 }
case 'reset':
return { count: 0 }
default:
throw new Error(`Unknown action: ${action.type}`)
}
}import { useReducer } from 'react'
function Counter() {
const [state, dispatch] = useReducer(counterReducer, { count: 0 })
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</div>
)
}dispatch is the only way to trigger a state change — a component never sets state directly, it describes what happened (the action) and lets the reducer decide the result.
| Use useState when... | Use useReducer when... |
|---|---|
| State is a single value or a few independent ones | Several pieces of state update together in response to the same events |
| Updates are simple — set this to that | The next state genuinely depends on the current state and which action fired |
| There's no shared logic between different updates | The same transition logic needs to be testable on its own, separate from the component |
Why this is easier to test
A reducer function has no dependency on React at all — it's a plain function taking state and an action, returning new state. That makes it trivial to unit test without rendering anything.
function formReducer(state, action) {
switch (action.type) {
case 'field-changed':
return { ...state, [action.field]: action.value }
case 'reset':
return { name: '', email: '' }
default:
return state
}
}
function SignupForm() {
const [form, dispatch] = useReducer(formReducer, { name: '', email: '' })
return (
<input
value={form.name}
onChange={(e) => dispatch({ type: 'field-changed', field: 'name', value: e.target.value })}
/>
)
}