Imagine a loader with three values: a status, a result, and an error. Those values are interdependent because only certain combinations make sense. While loading, neither a result nor an error should exist. After success, the result matters; after failure, the error matters. section 10.2.1
Update the whole loading state
A reducer lets each action produce a complete, consistent state: const initialState = { status: "INITIALIZE", result: null, error: null }; function reducer(state, { type, payload }) { switch (type) { case "LOADING": return { status: "LOADING", result: null, error: null }; case "SUCCESS": return { status: "SUCCESS", result: payload, error: null }; case "FAILURE": return { status: "FAILURE", result: null, error: payload }; default: return state; } } const [state, dispatch] = useReducer(reducer, initialState); When a request fails, dispatch({ type: "FAILURE", payload: "Loading failed" }) instead of coordinating separate status and error setters. section 10.2.1
The important part is clearing values that no longer belong to the new status. If a reload leaves an old result or error in place, the component can enter an invalid state. Separate useState values can still work, but a reducer puts the transition rules in one place when the data flow becomes complex. section 10.2.1 section 7.1.2
