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.
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.
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 componentThe 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.
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.
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
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.
Frequently Asked Questions
What is a form action in React 19?
What does useActionState return?
Does useActionState work without Server Components?
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.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.