useOptimistic
useOptimistic provides immediate UI feedback while an action runs in the background.
Example: ArchiveButton with Pending State
From app/dashboard/_components/ArchiveButton.tsx:
'use client';
import { useOptimistic } from 'react';
export function ArchiveButton({ slug, archived }) {
const [optimisticArchived, setOptimisticArchived] = useOptimistic(archived);
const isPending = optimisticArchived !== archived;
return (
<form
data-pending={isPending || undefined}
action={async () => {
// Use updater function to read current pending state
let newValue;
setOptimisticArchived(current => {
newValue = !current;
return newValue;
});
await toggleArchivePost(slug, newValue);
}}
>
<button type="submit">
{optimisticArchived ? 'Unarchive' : 'Archive'}
</button>
</form>
);
}Why Use Updater Functions?
When users click rapidly, multiple actions queue up. Each closure captures the same optimisticArchived value:
// ❌ Stale closure - both clicks see archived=false
setOptimisticArchived(!optimisticArchived); // false → true
setOptimisticArchived(!optimisticArchived); // false → true (stale!)
// ✅ Updater function reads from React's queue
setOptimisticArchived(current => !current); // false → true
setOptimisticArchived(current => !current); // true → falseStyling Parent Elements with CSS :has()
Expose pending state via data-pending attribute. Parent Server Components can style based on this using Tailwind's has-data-pending: variant:
// PostList.tsx (Server Component - no 'use client' needed!)
<Card className="has-data-pending:animate-pulse has-data-pending:bg-muted/70">
<ArchiveButton slug={post.slug} archived={post.archived} />
</Card>How It Works
- User submits the form
setOptimisticArchivedimmediately updates the UI and setsdata-pending- CSS
:has([data-pending])triggers parent styles (pulse animation) - The Server Function runs in the background
- When complete, the real
archivedprop replaces the optimistic value data-pendingis removed, styles revert
Important: Requires Action Context
The optimistic setter must be called inside an Action—a function passed to an action prop or wrapped in startTransition. Form action props are automatically called inside startTransition.
Use for actions with high success rates: toggles, likes, bookmarks.