React 19 Form Actions and useActionState Explained

Use React 19 form actions and useActionState to handle submission, track pending state, and show errors without a manual submit handler.

7 min read

React 19 form actions replace the classic onSubmit plus preventDefault pair with a function passed to the action prop. React calls that function with the submitted FormData and runs it inside a transition, so the page never reloads. useActionState builds on top by giving the action a result state and a pending flag.

The transition part matters: React keeps the previous UI responsive while the action runs, and a pending flag lets the button show progress.

Submit with the action prop

Pass a function to the action prop on a form element. The function receives the FormData object, and React calls it when the user submits, with no reload to prevent.

App.jsxApp.jsx
function search(formData) {
  const query = formData.get("query");
  alert(`Searching for ${query}`);
}
 
export default function SearchForm() {
  return (
    <form action={search}>
      <label htmlFor="query">Search</label>
      <input id="query" name="query" />
      <button type="submit">Search</button>
    </form>
  );
}

Type a query and press Search. The action runs with the field value, and the alert shows it. The input stays uncontrolled, so React reads the DOM value through FormData at submit time rather than tracking every keystroke.

Add result state with useActionState

When the action must report a result, wrap it with useActionState. The Hook takes a reducer action and an initial state, and returns the current state, the action dispatcher, and a pending flag.

App.jsxApp.jsx
import { useActionState } from "react";
async function signupAction(previousState, formData) {
  const email = formData.get("email");
  if (!email) {
    return { message: "Enter an email address." };
  }
  return { message: `Signed up with ${email}` };
}
export default function SignupForm() {
  const [state, formAction, isPending] = useActionState(signupAction, {
    message: "",
  });
  // the return below continues this same component

The reducer action receives the previous state first and the FormData second. It returns the next state, so a validation failure becomes a message in state instead of a thrown error. The dispatcher formAction is what you pass to the form, in the return statement below.

App.jsxApp.jsx
return (
  <form action={formAction}>
    <label htmlFor="email">Email</label>
    <input id="email" name="email" type="email" />
    <button type="submit" disabled={isPending}>
      {isPending ? "Signing up..." : "Sign up"}
    </button>
    {state.message && <p role="status">{state.message}</p>}
  </form>
);

Submit the form and the button disables while isPending is true. The message paragraph renders whatever the action returned, so the user sees the outcome in the UI.

Form action lifecycle

The flow is the same for every form action. The action always runs in a transition, returns a new state for the UI, or throws to the nearest error boundary when something unexpected breaks.

Show errors instead of throwing

Return known failures as state, and throw only for unexpected errors. A returned message renders next to the form, while a thrown error surfaces in an error boundary.

  • Return an error string or object for validation failures you expect.
  • Throw for programming errors the UI cannot recover from.
  • Read the returned state in the same render path as the fields.

This split keeps user-facing mistakes calm and reserves the error boundary for real bugs. For displaying server responses, see how to show server validation errors in React forms.

When to keep using onSubmit

Form actions are the React 19 default, but onSubmit still works in every version and remains the right tool for some jobs. Use onSubmit when the form is fully controlled and you read every value from state, or when you need to run several side effects before submitting. The action prop shines for uncontrolled fields and for passing FormData straight to a server function.

Both can live in the same codebase, so choose per form rather than migrating everything at once.

Know what resets and what does not

After a successful action, React resets the uncontrolled fields in the form, so the inputs clear automatically. A controlled field with a value prop is not reset this way, because its value comes from state that the form does not own. The state held by useActionState also does not reset on its own, because it represents the result you want to keep showing.

The action prop is the React 19 path, while the older onSubmit handler still works in every version. For the pending button alone, useFormStatus explained covers a smaller tool, and how to build forms in React covers the fundamentals.

Rune AI

Rune AI

Key Insights

  • Pass a function to the form action prop instead of onSubmit.
  • The action receives FormData and runs in a transition.
  • useActionState returns state, action, and isPending.
  • Return validation errors as state, not thrown exceptions.
  • Uncontrolled fields reset after a successful submit.
RunePowered by Rune AI

Frequently Asked Questions

What is a form action in React 19?

A function passed to the action prop of a form element. React calls it with the submitted FormData and runs it inside a transition, so no preventDefault call is needed.

What does useActionState return?

An array of three values: the current state, the action dispatcher to pass to the form action prop, and an isPending boolean.

Does useActionState work without Server Components?

Yes. It is a client-side Hook in React 19. Server Functions and progressive enhancement are optional extras that a framework like Next.js wires up.

Conclusion

React 19 form actions replace the manual submit handler, and useActionState adds result state and pending tracking on top. Return errors as state, and let the form reset uncontrolled fields.