How to Manage Form State with useReducer

Manage a React form with useReducer: keep every field in one object, update one field at a time, show validation errors, and reset with one action.

5 min read

Managing form state with useReducer keeps every field, the error message, and the reset behavior in one reducer instead of separate useState calls. One action updates a single field, and one reset action restores the whole form. This fits forms where several inputs move together, like a signup or settings screen.

One state object for the whole form

Hold the fields and the error message in a single object. That way one reducer owns everything the form shows, and a reset action can restore the entire shape at once.

index.jsindex.js
const initial = { email: "", error: "" };

The email field starts empty and the error starts empty. Keeping them together means one update can clear the error while changing the field. A reset action can then restore both in one return.

A reducer for change, error, and reset

The reducer handles three events: the user typed, validation failed, and the form was reset. Each case returns a fresh object.

index.jsindex.js
function reducer(state, action) {
  switch (action.type) {
    case "changed": return { ...state, [action.field]: action.value, error: "" };
    case "failed": return { ...state, error: "Enter a valid email." };
    case "reset": return initial;
    default: return state;
  }
}

The changed case copies the state, writes the new value into the given field, and clears any old error. The failed case keeps the email the user typed and adds the message, so the input is not wiped on a bad submit.

Render a controlled input

The input reads its value from the reducer state and dispatches a changed action on every keystroke. A label wraps the input, which gives it an accessible name.

App.jsxApp.jsx
function EmailForm() {
  const [state, dispatch] = useReducer(reducer, initial);
  return (
    <form onSubmit={(e) => { e.preventDefault(); dispatch({ type: state.email.includes("@") ? "reset" : "failed" }); }}>
      <label>Email <input value={state.email} onChange={(e) => dispatch({ type: "changed", field: "email", value: e.target.value })} /></label>
      {state.error && <p role="alert">{state.error}</p>}
      <button>Save</button>
    </form>
  );
}

Typing updates email live through the changed action. Submitting checks the email and dispatches either reset or failed, and the alert paragraph announces the error to screen readers.

Add a field without touching the reducer

To add another field, extend the initial object and render one more labeled input. The changed action already writes to whatever field name it receives, so no case in the reducer needs to change. Dispatch changed with the new field name and the input value, and the field starts updating like the others.

Why the handler decides, not the reducer

Validation that reads the value and dispatches a result belongs in the submit handler. The reducer stays pure and only returns the next state, which keeps it testable and side effect free. Client checks are convenience only; the server still has to validate the real request.

For larger forms, see how to write actions and reducers for naming and purity rules. When validation, focus management, and performance become the bottleneck, a library such as React Hook Form earns its keep; how to build forms in React covers that path.

Rune AI

Rune AI

Key Insights

  • Keep the whole form in one state object.
  • One action updates a single field without losing others.
  • Validate in the submit handler, not the reducer.
  • Use a reset action to restore the form.
  • Reach for a form library when validation grows.
RunePowered by Rune AI

Frequently Asked Questions

When should I use useReducer for a form instead of useState?

Use useReducer when the form has several fields, shared validation, or a reset action that touches every field. Keep useState for one or two independent inputs.

Is useReducer a replacement for React Hook Form?

No. useReducer organizes update logic, while a form library adds validation, focus management, and performance helpers. They solve different parts of the problem.

Should I validate inside the reducer?

No. Validate in the submit handler and dispatch a failed action with the message. The reducer only decides what the state should become, and server validation stays authoritative.

Conclusion

useReducer fits forms because fields, errors, and reset behavior all update together. Keep the whole form in one object, update one field per action, validate in the handler, and restore the form with a single reset action.