React: From Components to Production · Forms and Validation

Form Actions with useActionState and useFormStatus

Handle submissions, pending states and errors with React 19 Actions.

React 19 lets you pass a function to <form action={fn}>. useActionState wraps an action, giving you the latest result (e.g., errors) and an isPending flag; useFormStatus lets a child (like a submit button) read the parent form's pending state; useOptimistic shows optimistic UI while the action runs. Forms are reset automatically after successful actions. In frameworks with Server Functions, the same action can run on the server.

import { useActionState } from "react";
import { useFormStatus } from "react-dom";

type Result = { error?: string; ok?: boolean };

async function subscribe(_prev: Result, formData: FormData): Promise<Result> {
  const email = String(formData.get("email") ?? "");
  if (!email.includes("@")) return { error: "Enter a valid email" };
  const res = await fetch("/api/subscribe", { method: "POST", body: JSON.stringify({ email }) });
  return res.ok ? { ok: true } : { error: "Something went wrong" };
}

function SubmitButton() {
  const { pending } = useFormStatus();
  return <button disabled={pending}>{pending ? "Subscribing…" : "Subscribe"}</button>;
}

export function Newsletter() {
  const [state, formAction] = useActionState(subscribe, {});
  return (
    <form action={formAction}>
      <input name="email" type="email" aria-describedby="email-error" />
      <SubmitButton />
      {state.error && <p id="email-error" role="alert">{state.error}</p>}
      {state.ok && <p role="status">Thanks for subscribing!</p>}
    </form>
  );
}

Common pitfalls

  • Calling useFormStatus in the same component that renders the <form> — it only works in children.
  • Forgetting that action functions receive (previousState, formData) with useActionState.
  • Relying on client-side validation alone.

Key points

  • <form action> accepts functions in React 19
  • useActionState: result + pending
  • useFormStatus in child components

Further reading: react.dev: useActionState

Practice questions

Why doesn't useFormStatus report pending when used in the component rendering the form?

Answer

It reads the status of the nearest parent <form>. It must be called from a component rendered inside the form, such as a submit button component.

What arguments does an action wrapped by useActionState receive?

Answer

The previous state (initially the initial state you passed) and the submitted FormData.

Open in the app to mark lessons complete, take notes and track your progress.