React 19 introduces one of the most impactful features since hooks were released: Actions. If you’ve ever juggled loading states, error states, form submissions, optimistic UI, or chained async updates, Actions are going to dramatically simplify your code.
For years, React developers relied on useState, useEffect, or third-party form libraries to manage async mutations. With React 19, async updates become a first-class React primitive — tightly integrated with transitions, forms, error boundaries, optimistic UI, and Suspense.
This blog breaks down why Actions matter, how they work, and how to start using them today.
Let’s say you want to update a user’s name.
Before React 19, you had to manually handle:
That often looked like this:
function UpdateName() {const [name, setName] = useState("");const [error, setError] = useState(null);const [isPending, setIsPending] = useState(false);const handleSubmit = async () => {setIsPending(true);const error = await updateName(name);setIsPending(false);if (error) {setError(error);return;}redirect("/path");};}
It works… but it’s repetitive and error-prone.
React 19 changes everything by making async functions first-class citizens inside transitions.
A React Action:
<form>This simplifies async UI logic dramatically.
Instead of manually setting loading/error states, use a transition:
function UpdateName() {const [name, setName] = useState("");const [isPending, startTransition] = useTransition();const handleSubmit = () => {startTransition(async () => {const error = await updateName(name);if (!error) redirect("/path");});};}
What React manages for you:
isPending becomes true the moment the Action runsReact treats these async operations like atomic UI transitions.
An Action:
This aligns React closer to how modern apps handle mutations (e.g., Remix, Next.js, frameworks with server actions).
<form> — Even CleanerReact 19 also upgrades <form> elements.
You can now attach an action directly to a form:
<form action={submitAction}>
And combine it with the new useActionState hook:
const [error, submitAction, isPending] = useActionState(async (prev, formData) => {const error = await updateName(formData.get("name"));return error ?? null;},null);
Now your UI logic becomes incredibly simple:
<form action={submitAction}><input type="text" name="name" /><button type="submit" disabled={isPending}>Update</button>{error && <p>{error}</p>}</form>
React now handles for you:
No more boilerplate.
Actions solve long-standing issues React developers struggle with:
No manual setIsPending needed.
Works seamlessly with useOptimistic.
Errors trigger error boundaries automatically.
No need for external libraries for simple cases.
The same mental model works on client and server.
Most React form components shrink by 50–70%.
Combine Actions with useOptimistic for instant feedback:
const [optimisticName, setOptimisticName] = useOptimistic(currentName);const submitAction = async formData => {const newName = formData.get("name");setOptimisticName(newName);const updatedName = await updateName(newName);onUpdateName(updatedName);};
UI updates instantly while the server call is running.
With Actions, React is moving toward:
This is React returning to its core idea:
UI is a function of state — even async state.
Actions let React own more of the complexity you’ve been handling manually for years.