How to make Server Actions feel instant — useOptimistic for immediate UI updates, useActionState for form state management, pending state patterns, and rolling back on error. The bridge between server-actions-mutations and at-scale.
Optimistic UI: useOptimistic, useActionState, and the Pending State Pattern
Who this module is for: You have Server Actions working — the form submits, the data saves, the page updates. But the UI feels sluggish: users click a button and stare at a spinner while the server round-trip completes. This module covers the three React 19 hooks that make Server Actions feel instant —
useOptimisticfor immediate UI updates,useActionStatefor form state management, and the pending state pattern for visual feedback.
Why Optimistic UI Matters
A Server Action invokes a round-trip to your server: the browser sends a request, Next.js executes the action, revalidates the cache, and the updated UI flows back down. Even on a fast connection, this takes 200–800ms. On a mobile network, 1–3 seconds.
Users have been conditioned by native apps and real-time UIs to expect instant feedback. A button that does nothing for 500ms feels broken. Optimistic UI solves this by updating the UI immediately — before the server confirms — and rolling back if the server reports an error.
The pattern has three layers:
- Pending state — show that something is happening (spinner, disabled state)
- Optimistic update — show the expected result immediately
- Rollback — revert if the server returns an error
React 19 provides a hook for each layer.
useActionState: Form State Management
useActionState (formerly useFormState in React 18) connects a Server Action to a component's state. It gives you the action's return value and a wrapped action function that you pass to a form.
What useActionState gives you:
state— the most recent return value from the Server Action (or initial state on first render)action— a wrapped version of your Server Action to pass to<form action={action}>isPending—truewhile the action is in flight
The signature change: your Server Action must accept prevState as the first argument before formData. The prevState is the previous return value — useful for accumulating state across multiple submissions.
useOptimistic: Instant UI Updates
useOptimistic lets you show a temporary "optimistic" value while an async operation is pending, then automatically revert to the actual value when the operation completes.
How useOptimistic Works Internally
useOptimistic takes two arguments:
- The real state (synced with the server)
- An update function:
(currentState, optimisticValue) => newState
When you call addOptimistic(value):
- The component immediately re-renders with
updateFn(currentState, value) - While the associated async operation is pending, React shows the optimistic state
- When the async operation completes (or if the component re-renders with new real state), the optimistic state is discarded and the real state takes over
The rollback is automatic — if your Server Action throws, React reverts the optimistic update because the pending state resolves.
Combining All Three: A Complete Example
The canonical pattern: useActionState for form management + useOptimistic for immediate list updates:
The temporary items render with reduced opacity and a "(saving...)" tag. When the server responds, setTodos(result.todos) updates the real state with the server-assigned IDs, and the optimistic overlay is replaced.
The Pending State Pattern
Beyond spinners, a well-designed pending state communicates exactly what is happening and prevents double-submission.
Disable the Trigger
Always disable the submit button (or the entire form) while isPending is true:
Skeleton vs Spinner
For content that takes > 300ms, a skeleton is better UX than a spinner. Use Suspense boundaries with skeleton fallbacks for the loading state:
useFormStatus for Deep Components
useFormStatus (from react-dom) reads the pending state of the nearest parent <form> — useful for submit buttons nested inside form components:
Error Handling and Rollback Patterns
Explicit Rollback with Error Boundaries
For operations that must roll back cleanly (financial, irreversible):
Toasts for Async Feedback
Optimistic UI removes the "loading" state, but users still need to know when something fails:
When NOT to Use Optimistic UI
Optimistic UI is a UX enhancement, not a correctness guarantee. Do not use it when:
- The operation is not idempotent — if showing the wrong result before server confirmation causes confusion (e.g., payment confirmations, inventory deduction)
- Rollback is disorienting — if the rollback experience is worse than waiting (e.g., the user navigated away expecting success)
- The operation is fast — if the server responds in < 100ms, optimistic UI adds complexity with no UX benefit
- You need the server response to continue — if the UI flow depends on a server-assigned ID or token, you must wait for the response
The useTransition Alternative
For non-form async operations (button clicks that trigger state transitions), useTransition provides isPending without the form machinery:
useTransition marks the state update as non-urgent — React can interrupt it if higher-priority updates arrive (user input). Useful for transitions that should not block typing or other interactions.
Summary
useActionState— connects a Server Action to component state; gives youstate,action, andisPending; Server Action signature gainsprevStateas first paramuseOptimistic— shows an immediate temporary value; auto-reverts to real state when the async operation completes; rollback is automatic on erroruseFormStatus— reads pending state from the nearest parent form; avoids prop-drillingisPendinginto nested submit buttonsuseTransition— marks async state updates as non-urgent; providesisPendingfor non-form button interactions- The pattern: optimistic update → async server call → sync real state on success → auto-rollback on failure
- When to skip it: payment flows, irreversible operations, fast operations (< 100ms), flows that depend on server-assigned values
Next: P-4 — Authentication with Auth.js v5 — session management, OAuth providers, database sessions, and protecting routes with middleware.
How does useOptimistic handle rollbacks if the associated asynchronous Server Action throws an error?
When managing deep form components, what is the benefit of using useFormStatus from react-dom?
In which scenario is using Optimistic UI generally NOT recommended?
Test your knowledge with more question sets
Sign in to access a wider variety of questions and get notified when new practice sets are added to this module.
Sign in & RegisterDiscussion
0Join the discussion