React 19 useOptimistic: Build Instant UI Without Waiting for the Server

Published: (April 20, 2026 at 08:36 PM EDT)
3 min read
Source: Dev.to

Source: Dev.to

Introduction

useOptimistic is one of the most underused hooks in React 19. Instead of relying on local state plus loading spinners, you can ship instant UI updates that feel snappy while the server request runs in the background.

Using useOptimistic

Old approach – users see lag

async function addTodo(text: string) {
  setLoading(true);
  const newTodo = await createTodo(text); // 200‑800 ms wait
  setTodos(prev => [...prev, newTodo]);
  setLoading(false);
}

With useOptimistic

const [optimisticTodos, addOptimisticTodo] = useOptimistic(
  todos,
  (currentTodos, newText: string) => [
    ...currentTodos,
    { id: crypto.randomUUID(), text: newText, pending: true }
  ]
);

async function addTodo(text: string) {
  addOptimisticTodo(text);               // Instant UI update
  const newTodo = await createTodo(text); // Background request
  setTodos(prev => [...prev, newTodo]);
}

The item appears instantly. If the request fails, the optimistic update rolls back automatically.

Core API

const [optimisticState, dispatchOptimistic] = useOptimistic(
  state,      // real state (from server/parent)
  updateFn    // (currentState, action) => nextOptimisticState
);

React automatically reverts optimisticState back to state when the async action finishes—whether it succeeds or throws. No manual rollback is required.

Example: Todo List

function TodoList({ todos }: { todos: Todo[] }) {
  const [optimisticTodos, addOptimisticTodo] = useOptimistic(
    todos,
    (state, text: string) => [
      ...state,
      { id: crypto.randomUUID(), text, completed: false, pending: true }
    ]
  );

  async function formAction(formData: FormData) {
    addOptimisticTodo(formData.get("todo") as string);
    await saveTodo(formData.get("todo") as string);
  }

  return (
    <>
      {optimisticTodos.map(todo => (
        <div key={todo.id}>
          {todo.text}
        </div>
      ))}

      <form onSubmit={e => {
        e.preventDefault();
        const data = new FormData(e.currentTarget);
        formAction(data);
      }}>
        <input name="todo" />
        <button type="submit">Add</button>
      </form>
    </>
  );
}

Example: Like Toggle

const [optimistic, dispatch] = useOptimistic(
  { liked, count },
  (current) => ({
    liked: !current.liked,
    count: current.liked ? current.count - 1 : current.count + 1,
  })
);

async function handleLike() {
  dispatch(null);               // Instant toggle
  const result = await toggleLike(postId);
  setLike(result);
}

Handling Failures

useOptimistic rolls back only when the async action throws. Silent failures will leave the optimistic state stuck.

Bad pattern – no throw

async function save(data) {
  const json = await fetch(...).then(r => r.json());
  if (!json.success) return json.error; // returns, doesn't throw
}

Good pattern – throw on error

async function save(data) {
  const res = await fetch(...);
  if (!res.ok) throw new Error(`Failed: ${res.status}`);
  return res.json();
}

Example with error handling

async function submitComment(formData: FormData) {
  const text = formData.get("comment") as string;
  setError(null);
  addOptimisticComment(text);

  try {
    const comment = await postComment(postId, text);
    setComments(prev => [...prev, comment]);
  } catch {
    setError("Failed to post. Try again.");
    // useOptimistic auto-reverts the optimistic entry
  }
}

Common Pitfalls

  • Large server changes – If the server response replaces the optimistic placeholder with significantly different data, the UI may flash.
  • High‑conflict scenarios – Collaborative editing, inventory counts, or financial writes can cause frequent rollbacks.
  • Irreversible actions – Deletions, charges, or sent emails should be confirmed before applying an optimistic update.

When to Use useOptimistic

  • Adds, likes, toggles, reorders, soft deletes.
  • Any CRUD operation where the perceived latency (the time until the UI shows a result) is the primary user concern.

useOptimistic doesn’t make the server faster; it makes the app feel faster by showing results before the server responds.

Resources

  • React 19 + Next.js Server Actions + optimistic UI – The AI SaaS Starter Kit ships with everything pre‑wired, saving you from a lengthy setup.
0 views
Back to Blog

Related posts

Read more »