Handling Forms via Form ActionsEntry 1 of 10

Form Actions: <form action={fn}>

In React 19, a form's action prop can be a function. React calls it with the form's FormData when the form is submitted.

react.dev<form>

Notes

  • No onSubmit, no event.preventDefault(), no new FormData(...): React prevents the page reload and builds the FormData for you.
  • Every input still needs a name; that's how FormData finds its value.
  • Read values with formData.get('email') and formData.getAll('acquisition') (Working with Forms, Entry 4).
  • The function can be sync or async. React runs it inside a Transition, which is what makes pending state (Entry 5) and optimistic updates (Entry 8) possible.
  • In plain HTML, action is a URL string. React only treats it differently when you pass a function.

What / Why / When

What

A submit handler that receives FormData directly.

Why

Less boilerplate, and it unlocks useActionState, useFormStatus, and useOptimistic.

When

Forms you only need to read on submit (uncontrolled inputs).

Use case

A signup form that logs the entered email.

Code compare

React
export default function Signup() {  function signupAction(formData: FormData) {    const enteredEmail = formData.get("email");    console.log(enteredEmail);  }​  return (    <form action={signupAction}>      <h2>Welcome on board!</h2>      <p>We just need a little bit of data from you to get you started.</p>​      <div className="control">        <label htmlFor="email">Email</label>        <input id="email" type="email" name="email" />      </div>​      <button type="submit">Sign up</button>    </form>  );}

Angular has no action functions. The equivalent is (ngSubmit) with a Reactive Form; the values come from the form model instead of FormData.

Angular
@Component({  selector: "app-signup",  imports: [ReactiveFormsModule],  template: `    <form [formGroup]="form" (ngSubmit)="signup()">      <h2>Welcome on board!</h2>      <label for="email">Email</label>      <input id="email" type="email" formControlName="email" />      <button type="submit">Sign up</button>    </form>  `,})export class SignupComponent {  form = new FormGroup({    email: new FormControl("", { nonNullable: true }),  });​  signup() {    console.log(this.form.controls.email.value);  }}

Differences and gotchas

  • formData.get() returns FormDataEntryValue | null (string | File | null). In TypeScript, convert before using: String(formData.get('email') ?? '').
  • A form action also works with <button formAction={fn}> (Entry 7).

Quick reference

React 19Angular
<form action={signupAction}><form [formGroup] (ngSubmit)="signup()">
formData.get('email')form.controls.email.value
No preventDefault()No preventDefault()