React useOptimistic: Build SNAPPY Interfaces - By Sourav Mishra (@souravvmishra)
Master React's useOptimistic hook to create instant, app-like interactions in Next.js applications. A deep dive into optimistic UI updates.
In this guide, I, Sourav Mishra, explore useOptimistic, the React hook that makes your web app feel like a native mobile app.
Optimistic UI is the pattern of showing the successful result of an action before the server actually confirms it. Think of the "Like" button on Twitter/X: it turns red instantly, even if your internet is slow.
The Problem: Waiting for Server Actions
Normally, a Server Action cycle looks like this:
- User clicks "Post Comment".
- Wait for network request.
- Wait for database.
- Wait for revalidation.
- UI updates.
That "Wait" feels sluggish. useOptimistic eliminates it.
The Solution: useOptimistic Hook
This hook allows you to switch to a temporary state immediately while the async action runs in the background.
// components/MessageList.tsx
"use client";
import { useOptimistic, useRef } from "react";
import { sendMessage } from "@/app/actions";
type Message = { id: string; text: string; sending?: boolean };
export function MessageList({ initialMessages }: { initialMessages: Message[] }) {
const formRef = useRef<HTMLFormElement>(null);
const [messages, addOptimisticMessage] = useOptimistic(
initialMessages,
(state, newMessage: Message) => [...state, newMessage]
);
async function action(formData: FormData) {
const text = formData.get("message") as string;
// 1. Show optimistic update immediately
addOptimisticMessage({
id: Math.random().toString(), // Temp ID
text,
sending: true,
});
formRef.current?.reset();
// 2. Run actual server action
await sendMessage(text);
}
return (
<div>
<ul>
{messages.map((m) => (
<li key={m.id} className={m.sending ? "opacity-50" : ""}>
{m.text} {m.sending && "(Sending...)"}
</li>
))}
</ul>
<form action={action} ref={formRef} className="mt-4">
<input name="message" className="border p-2 rounded mr-2" />
<button type="submit" className="bg-blue-500 text-white px-4 py-2 rounded">
Send
</button>
</form>
</div>
);
}
How It Works
- State Init: It takes
initialMessages(real data from server). - Mutation: Calling
addOptimisticMessageupdates the UI state immediately without waiting. - Reconciliation: When
sendMessagefinishes and Next.js revalidates the page, the optimistic state is discarded and replaced by the real new data from the server.
Key Considerations
- Temporary IDs: You often need to generate a fake ID (like
Math.random()) for the key prop, which gets replaced by the real DB ID later. - Feedback: Use visual cues (like
opacity-50) to let the user know the action is still pending, even if the data is visible. - Error Handling: If the server action fails, the optimistic state automatically rolls back when the component re-renders (though you often need manual handling to show an error toast).
Conclusion
useOptimistic bridges the gap between client-side speed and server-side simplicity. It is essential for modern, high-quality Next.js applications.
For ensuring the data you send is valid, verify it first using my Zod Validation Guide.
FAQ
Q: Can I use this for things other than lists? Yes. You can use it for toggles (Like buttons), edit forms (changing a title), or any UI state that mirrors server data.
Q: Is this stable?
Yes, useOptimistic is stable in React 19 (which Next.js App Router uses).
This guide was written by Sourav Mishra, exploring the cutting edge of React UX.