Lesson 22 +10 XP

Optimistic UI Updates with useOptimistic

Optimistic UI Updates with useOptimistic

Optimistic UI is a pattern where the user interface updates immediately on user input, assuming the background server request will succeed. If the server request fails, the UI rolls back gracefully to its previous state.

Why Use Optimistic UI?

Waiting for a server roundtrip to display an added comment or like button count creates perceived lag. React's useOptimistic hook makes your UI feel instantaneous.

useOptimistic Syntax

const [optimisticState, addOptimistic] = useOptimistic(
  currentState,
  // Update function: returns the expected state while action is pending
  (state, newOptimisticValue) => {
    return [...state, newOptimisticValue];
  }
);

Complete Optimistic Todo Example

// components/OptimisticTodoList.tsx
'use client';

import { useOptimistic, useRef } from 'react';
import { addTodoAction } from '@/app/actions';

type Todo = { id: string; title: string; pending?: boolean };

export default function OptimisticTodoList({ initialTodos }: { initialTodos: Todo[] }) {
  const formRef = useRef<HTMLFormElement>(null);

  const [optimisticTodos, addOptimisticTodo] = useOptimistic(
    initialTodos,
    (state: Todo[], newTitle: string) => [
      ...state,
      { id: Math.random().toString(), title: newTitle, pending: true },
    ]
  );

  async function handleFormSubmit(formData: FormData) {
    const title = formData.get('title') as string;
    formRef.current?.reset();

    // 1. Instantly update UI optimistically!
    addOptimisticTodo(title);

    // 2. Perform actual Server Action in background
    await addTodoAction(title);
  }

  return (
    <div className="p-6 max-w-md">
      <form ref={formRef} action={handleFormSubmit} className="flex gap-2 mb-4">
        <input name="title" className="border p-2 flex-1 rounded" required />
        <button type="submit" className="bg-green-600 text-white px-4 py-2 rounded">
          Add
        </button>
      </form>

      <ul className="space-y-2">
        {optimisticTodos.map((todo) => (
          <li
            key={todo.id}
            className={`p-3 rounded border ${
              todo.pending ? 'opacity-50 bg-yellow-50' : 'bg-white'
            }`}
          >
            {todo.title} {todo.pending && '(Saving...)'}
          </li>
        ))}
      </ul>
    </div>
  );
}

Optimistic UI Lifecycle

1. User clicks submit -> Component calls addOptimisticTodo(title).
2. React immediately re-renders component with optimistic item (pending indicator).
3. Background Server Action executes on server (addTodoAction).
4. Server Action completes & revalidates page -> True state replaces optimistic state automatically!

TL;DR

  • useOptimistic provides instant visual feedback before server confirmation.
  • Optimistic updates roll back automatically if the Server Action throws an error.
  • Use a pending flag in optimistic items to visually distinguish unsaved state.
  • Combine useOptimistic with revalidatePath for seamless state reconciliation.