Chapter 1 of 4
Why a Reducer
The point at which several useState calls stop being the simplest option.
A handful of independent state values is fine with useState. The trouble starts when they depend on each other and every handler has to update three of them in the right order.
function Form() {
const [values, setValues] = useState({});
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const [isDirty, setIsDirty] = useState(false);
const [submitCount, setSubmitCount] = useState(0);
function handleSubmit() {
setIsSubmitting(true);
setErrors({});
setSubmitCount(submitCount + 1);
setIsDirty(false);
// ...and remember to unset isSubmitting on every exit path
}
}What a reducer changes
A reducer is a single pure function that takes the current state and an action, and returns the next state. All the transitions live in one place, described as data.
const [state, dispatch] = useReducer(reducer, initialState);
dispatch({ type: "submitted" });function formReducer(state, action) {
switch (action.type) {
case "changed":
return {
...state,
values: { ...state.values, [action.field]: action.value },
isDirty: true,
};
case "submitted":
return { ...state, isSubmitting: true, errors: {},
submitCount: state.submitCount + 1, isDirty: false };
case "failed":
return { ...state, isSubmitting: false, errors: action.errors };
case "succeeded":
return { ...initialState, submitCount: state.submitCount };
default:
throw new Error("Unknown action: " + action.type);
}
}Reach for a reducer when
- Several state values change together in the same handlers.
- The next state often depends on the previous state.
- The same transition happens from more than one place.
- You want to unit test the logic without rendering anything.