In React 19 7,897 the action prop of a <form> accepts a function instead of a URL. React intercepts the submit, builds a FormData from the fields, calls your function with it, and — if the function returns without throwing — resets the form. The call runs inside a transition, so the UI stays responsive and the pending state is visible to useFormStatus. <button> and <input type="submit"> take a matching formAction prop, which lets one form route to different handlers.
function Subscribe() {
const [saved, setSaved] = useState(null);
async function subscribe(formData) { // React hands you the FormData
await fetch('https://api.example.com/subscribe', { method: 'POST', body: formData });
setSaved(formData.get('email')); // the fields reset themselves
}
return (
<form action={subscribe}>
<input name="email" type="email" required />
<button>Subscribe</button>
{saved && <p>Subscribed {saved}.</p>}
</form>);
}Note what is absent: no onSubmit, no event.preventDefault(), no state variable per field. Every input needs a name, because name is the FormData key — and FormData goes to fetch directly, which is how file uploads come along for free. The reset happens only on success, so a handler that throws leaves the user's typing in place, and noValidate on the <form> turns off the browser's own pass when you want to own the messages.