Skip to content
exercises
React

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.