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
useFormStatusin 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.