Search⌘ K
AI Features

Optimistic UI with useOptimistic

Explore how to use React 19's useOptimistic Hook to create an Optimistic UI in Next.js applications that updates instantly before server confirmation. Understand the difference between pessimistic and optimistic UI patterns, how to manage temporary state, and handle automatic rollback on server errors. Learn when and how to apply optimistic updates for better user experience in high-success, low-risk interactions.

In our last lesson, we used useActionState to create responsive forms with loading indicators. This is a clear improvement over a full page reload, but there is a trade-off: the user still has to wait for the server roundtrip before they see their new item appear. Even with a spinner, this delay can feel sluggish.

We can do better. In this lesson, we’ll use React 19’s useOptimistic Hook to update the UI instantly.

Running example: A pessimistic UI

Let’s start with a simple “pessimistic” UI. A pessimistic UI waits for the server to confirm an action was successful before updating what the user sees.

Imagine a message board where users can post messages. When a user submits the form, our app does the following:

  1. It disables the form and shows a loading spinner (using the isPending state from useTransition).

  2. It sends the data to the server action.

  3. It waits for the server to process it and save it to the database.

  4. It receives the successful response.

  5. It rerenders the page with the new message in the list.

This is a safe and reliable pattern, but even on a fast connection, that round-trip takes time. For the user, it feels like a lag.

Here’s the code for a basic message board. First, we define a data structure as the mock database.

// app/messages/data.js
export const messages = [
{ id: 1, text: 'Hello, world!' },
{ id: 2, text: 'This is the second message.' },
];
The data structure and mock database

Next, we create a Server Action to add a new message and revalidate the path. We’ll add a deliberate one-second delay in our Server Action to simulate real-world network latency.

// app/messages/actions.js
'use server';
import { revalidatePath } from 'next/cache';
import { messages } from './data';
export async function addMessage(messageText) {
// Simulate a 1-second network delay
await new Promise((res) => setTimeout(res, 1000));
const newMessage = {
id: messages.length + 1,
text: messageText,
};
messages.push(newMessage);
revalidatePath('/messages');
}
The Server Action with a simulated delay

Explanation:

  • Line 3: The 'use server' directive marks this module as containing server-side functions.

  • Line 10: We simulate network latency.

  • Lines 12–16: We create a new ...