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.

BySourav Mishra3 min read

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:

  1. User clicks "Post Comment".
  2. Wait for network request.
  3. Wait for database.
  4. Wait for revalidation.
  5. 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

  1. State Init: It takes initialMessages (real data from server).
  2. Mutation: Calling addOptimisticMessage updates the UI state immediately without waiting.
  3. Reconciliation: When sendMessage finishes 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.

Share this post

Cover image for React useOptimistic: Build SNAPPY Interfaces

You might also like

See all