Forms and Actions
|
This section documents React 19.x for the web, using function components and Hooks throughout (class components appear only where React still requires them, such as error boundaries). React Native is out of scope and gets only a pointer. The React 19 additions follow the current official React documentation. This content was generated with the assistance of AI and should be verified against react.dev before being relied on in production, since React APIs continue to evolve between releases. This section’s bibliography lists the reference material consulted while preparing these pages. |
React handles forms in two styles: the classic controlled inputs + onSubmit approach, and the React 19
Actions model, which wires an async function directly to a <form> and manages pending, error, and
optimistic state for you.
Controlled inputs
Bind each field’s value to state and update it in onChange. The component is the single source of truth.
function SignupForm({ onSubmit }) {
const [email, setEmail] = useState('');
const [note, setNote] = useState('');
const [plan, setPlan] = useState('free');
function handleSubmit(e) {
e.preventDefault();
onSubmit({ email, plan });
}
return (
<form onSubmit={handleSubmit}>
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
<textarea value={note} onChange={(e) => setNote(e.target.value)} />
<select value={plan} onChange={(e) => setPlan(e.target.value)}>
<option value="free">Free</option>
<option value="pro">Pro</option>
</select>
<button type="submit">Create account</button>
</form>
);
}
For a form with many fields you do not need to react to per keystroke, leave them uncontrolled and read
new FormData(e.target) on submit. See
<form>.
React 19 Actions
Pass a function to <form action> (or to startTransition). React calls it with the form’s FormData, marks
the surrounding transition pending while it runs, resets uncontrolled fields on success, and surfaces
thrown errors.
import { useActionState } from 'react';
function CommentForm({ postId }) {
const [state, formAction, isPending] = useActionState(
async (prevState, formData) => {
const text = formData.get('text');
try {
await addComment(postId, text);
return { ok: true, error: null };
} catch (err) {
return { ok: false, error: err.message };
}
},
{ ok: false, error: null }
);
return (
<form action={formAction}>
<textarea name="text" required />
<button disabled={isPending}>{isPending ? 'Posting…' : 'Post'}</button>
{state.error && <p role="alert">{state.error}</p>}
</form>
);
}
-
useActionState(fn, initialState)→[state, action, isPending].stateis the last value the action returned. -
useFormStatus()(fromreact-dom) — lets a child of the<form>(e.g. a<SubmitButton>) read{ pending }without prop drilling. -
useOptimistic(value, updateFn)— render an immediate optimistic value while the action is in flight, automatically reverting if it fails.
import { useFormStatus } from 'react-dom';
function SubmitButton() {
const { pending } = useFormStatus();
return <button disabled={pending}>{pending ? 'Saving…' : 'Save'}</button>;
}
When the action is a server function, <form action> also works before JavaScript loads (progressive
enhancement) — the browser posts the form and the server responds. References:
useActionState,
useFormStatus,
useOptimistic,
Server Functions.