React 19 Actions
Handle asynchronous form submissions and mutations cleanly with React 19 Actions, useActionState, and useOptimistic updates.
TL;DR
- Execute asynchronous form submissions seamlessly using
actionfunctions. - Track pending state and returned values with
useActionState. - Render instant visual feedback to users with
useOptimistic.
Form Action Fundamentals
Async Form Action PropPass async handler directly to form action.
async function updateName(formData: FormData) {
'use server';
await db.update(formData.get('name'));
}
return <Form action={updateName} />;Action with TransitionTrigger action imperatively with startTransition.
const [isPending, startTransition] = useTransition();
const handleSave = () => {
startTransition(async () => {
await saveCart();
});
};Button Form Action OverrideDirect specific submit button to alternative action.
<ButtonBase formAction={deleteItem}>
Delete Item
</ButtonBase>State with useActionState
Hook InitializationReceive state, action trigger, and pending flag.
const [state, formAction, isPending] = useActionState(
async (prev, formData: FormData) => {
return await registerUser(formData);
},
null
);Pending Submit ButtonDisable button automatically while action runs.
<ButtonBase type="submit" disabled={isPending}>
{isPending ? 'Saving...' : 'Submit'}
</ButtonBase>Action Error ReturnReturn structured validation errors from action.
if (!name) return { error: 'Name is required' };Optimistic Updates with useOptimistic
Declare Optimistic HookDefine temporary state applied before server response.
const [optList, setOptList] = useOptimistic(
messages,
(cur, next: string) => [...cur, { text: next }]
);Dispatch Optimistic ValueTrigger optimistic update before awaiting network.
setOptList(newText);
await sendMessage(newText);Instant Visual FeedbackDisplay message immediately with sending indicator.
<CardView opacity={msg.sending ? 0.6 : 1}>
<Text>{msg.text}</Text>
</CardView>Action Best Practices
Form Reset IntegrationReset form inputs automatically after success.
const formRef = useRef<HTMLFormElement>(null);
const onSubmit = async (data: FormData) => {
await action(data);
formRef.current?.reset();
};Avoid preventDefaultLet React 19 manage submission lifecycle natively.
// NO NEED: e.preventDefault() is obsolete
<Form action={myAction} />Progressive EnhancementActions work before client JavaScript hydrates.
// Server actions execute natively via standard POST
<Form action={serverAction}>Tips
- Pass asynchronous functions directly to the form
actionprop to handle submissions without manual event prevention. - Use
useOptimisticto update UI immediately while background server actions complete, reverting automatically if actions fail.
Warnings
- Do not invoke
preventDefault()inside action handlers because React 19 manages form lifecycle submissions automatically. - Always handle potential submission errors inside
useActionStateactions to prevent unhandled rejection crashes during network loss.
In Practice
Implement a complete user update form using useActionState that manages pending status and validation feedback.
- Define async action function accepting previous state and FormData.
- Initialize useActionState hook with action and initial values.
- Bind returned formAction trigger directly to the form element.
- Render dynamic pending states on the submit button automatically.
async function saveUser(prev: any, data: FormData) {
const name = data.get('name') as string;
if (!name) return { err: 'Name is required' };
return { ok: true, name };
}
export function ProfileForm() {
const [res, action, isPending] =
useActionState(saveUser, null);
return (
<Form action={action}>
<InputBase name="name" />
<ButtonBase type="submit" disabled={isPending}>
{isPending ? 'Saving...' : 'Save'}
</ButtonBase>
{res?.err && <Alert text={res.err} />}
</Form>
);
}FAQ
Actions are asynchronous transition functions passed to form action props or startTransition that automatically manage pending states, errors, and optimistic UI updates.
useActionState accepts an async action and initial state, returning the latest state result, the wrapped action trigger, and a boolean isPending flag.
When the underlying async action throws an error or rejects, React automatically rolls back the optimistic state to the actual server state.