Real Example: Registration Form
Let's see how useReducer saves us when form fields start " communicating " with each other.
1. Reducer Structure
The most popular way to write a reducer is through the switch construct. This allows easily seeing all possible " scenarios " of events.
const initialState = {
username: '',
email: '',
password: '',
error: null,
isSubmitting: false
};
function formReducer(state, action) {
switch (action.type) {
case 'FIELD_CHANGE':
return {
...state,
[action.field]: action.value, // Dynamic field name
error: null
};
case 'SUBMIT_START':
return { ...state, isSubmitting: true };
case 'SUBMIT_ERROR':
return { ...state, isSubmitting: false, error: action.message };
case 'RESET':
return initialState; // Simplest way to clear form
default:
return state;
}
}
2. What Does This Teach Us?
- Atomicity: Ever forgot to turn off the loading " spinner " on error? With
useReducerthis is impossible. In theSUBMIT_ERRORcase we explicitly write:isSubmitting: false. All state updates in one go. - Scalability: If tomorrow you need to add a " Confirm password " field, you don't need to create a new
useState. You just handle anotherFIELD_CHANGE. - Debugging Ease: Put
console.log(action)at the start of the reducer. Now in the console you'll see the exact history of your application's life: " Pressed " , " Typed " , " Error " , " Reset " .
3. When NOT to Use useReducer?
Don't overcomplicate!
- If you have a search field or simple toggle " Dark/Light theme " — keep
useState. - If you don't have complex logic (e.g., one field doesn't depend on another) —
useStatewill be faster and clearer for colleagues.
Remember: useReducer is heavy artillery. Use it when you feel useState is turning into " spaghetti code " .