Form state with useReducer
Where reducers actually start to feel worth it.
BasicCourse
Manage a multi-field form with useReducer instead of individual useState calls. Forms are the use case where reducers start earning their keep.
The Problem
A form with five fields managed via useState:
const [name, setName] = useState("");const [email, setEmail] = useState("");const [message, setMessage] = useState("");const [submitting, setSubmitting] = useState(false);const [error, setError] = useState(null);This works but couples each state update to its own setter. When submit logic needs to update multiple fields atomically (e.g., set submitting: true and clear error at the same time), you have to call multiple setters.
The Reducer
const initialState = { name: "", email: "", message: "", submitting: false, error: null,};
function formReducer(state, action) { switch (action.type) { case "FIELD": return { ...state, [action.field]: action.value }; case "SUBMIT": return { ...state, submitting: true, error: null }; case "SUCCESS": return { ...initialState }; case "ERROR": return { ...state, submitting: false, error: action.message }; default: return state; }}Usage
function ContactForm() { const [state, dispatch] = useReducer(formReducer, initialState);
function handleChange(e) { dispatch({ type: "FIELD", field: e.target.name, value: e.target.value }); }
async function handleSubmit(e) { e.preventDefault(); dispatch({ type: "SUBMIT" }); try { await submitForm(state); dispatch({ type: "SUCCESS" }); } catch (err) { dispatch({ type: "ERROR", message: err.message }); } }
return ( <form onSubmit={handleSubmit}> <input name="name" value={state.name} onChange={handleChange} /> <input name="email" value={state.email} onChange={handleChange} /> <textarea name="message" value={state.message} onChange={handleChange} /> {state.error && <p>{state.error}</p>} <button disabled={state.submitting}>Send</button> </form> );}Why It Matters
The SUBMIT action sets submitting: true and clears error in one dispatch. That’s what reducers are good for — coordinated state transitions that would require multiple setState calls to express clearly.