Skip to Content
FrontendReactVeryadvance4.1 React 19 Actions & Async UI

React 19 Actions & Async UI ⚡

React 19 (stable since Dec 5, 2024) streamlines async workflows with first-class action primitives, pending UI helpers, and optimistic rendering hooks.

1) Actions Concept 🧠

  • An action is any async function passed to React APIs (forms, transitions) that returns data or throws errors.
  • React knows when an action is running and exposes pending/error state automatically.
async function saveProfile(formData) { "use server"; // optional in RSC contexts const response = await fetch("/api/profile", { method: "POST", body: formData }); return response.json(); }

Actions can run client-side (regular async functions) or server-side (RSC with "use server").

2) Async Transitions + Pending UI 🔄

  • Wrap slow state updates in startTransition or useTransition to keep urgent interactions snappy.
  • When transitions wrap actions, React marks them as non-blocking and exposes isPending.
const [isPending, startTransition] = useTransition(); function handleSearch(term) { startTransition(async () => { const results = await searchAction(term); setResults(results); }); }

Render fallback UI while isPending is true.

3) Form Actions 📄

React 19 lets you attach async functions directly to forms with <form action={fn}>.

function ProfileForm() { return ( <form action={updateProfile}> <input name="displayName" /> <SubmitButton /> </form> ); }
  • React automatically gathers FormData, invokes updateProfile, handles pending state, and can reset the form on success.
  • No manual onSubmit + preventDefault boilerplate.

4) useActionState 🧾

Tracks the state returned by an action plus pending/errors.

const [state, action, isPending] = useActionState(updateProfile, { message: "" }); return ( <form action={action}> <input name="displayName" /> <button disabled={isPending}>Save</button> {state.message && <p>{state.message}</p>} </form> );
  • state mirrors the latest resolved value from the action.
  • Great for surfacing success messages or server validation results.

5) useFormStatus 🧠

Read the pending/error state of the closest parent form inside nested components.

import { useFormStatus } from "react-dom"; function SubmitButton() { const { pending } = useFormStatus(); return ( <button type="submit" disabled={pending}> {pending ? "Saving..." : "Save"} </button> ); }
  • Works even if the button is several levels deep in the component tree.
  • Encourages reusable buttons, status badges, and loading indicators.

6) useOptimistic ✨

Show optimistic UI while awaiting action results.

import { useOptimistic } from "react"; function TodoList({ todos }) { const [optimisticTodos, addOptimisticTodo] = useOptimistic(todos); async function addTodoAction(formData) { const title = formData.get("title"); addOptimisticTodo(prev => [...prev, { id: crypto.randomUUID(), title, pending: true }]); return createTodoOnServer(title); } return ( <form action={addTodoAction}> <input name="title" /> <button>Add</button> <ul> {optimisticTodos.map(todo => ( <li key={todo.id}>{todo.pending ? "🟡" : "🟢"} {todo.title}</li> ))} </ul> </form> ); }
  • Automatically reconciles optimistic state when the real result arrives (or reverts on error).

Key Takeaways ✅

  • Actions reduce boilerplate: attach async logic directly to transitions and forms.
  • useActionState + useFormStatus expose pending + result state without prop drilling.
  • useOptimistic delivers polished UX while awaiting confirmation.
  • Form actions make React feel like a full-stack framework when paired with RSC.

Recap 🔄

React 19 treats async work as a first-class citizen: define actions once, wire them to transitions or forms, track progress via useActionState/useFormStatus, and delight users with useOptimistic. Less glue code, more resilient UX.

Last updated on