Home/Learn/Next.js/Optimistic Updates with useOptimistic

Optimistic Updates with useOptimistic

Advanced
Mutations

Show the result before the server confirms it, and let React roll it back automatically if the action fails. The rollback is the part that makes it safe.

Overview

A server action is a round trip, and a round trip is visible on a slow connection. For small reversible actions — a like, a bookmark, adding an item to a list — waiting is the wrong experience, and useOptimistic gives you the alternative with far less machinery than doing it by hand. What makes it trustworthy is that React reverts to the real state when the action settles: if the write failed, the optimistic value disappears on its own. The judgement is knowing which actions deserve this and which must not have it.

The Hook

A temporary view of state that survives only until the action settles.

Optimistic state is discarded when the action settles
'use client'
import { useOptimistic, startTransition } from 'react'

export function Todos({ todos }) {          // todos come from the server
  const [optimisticTodos, addOptimistic] = useOptimistic(
    todos,
    (state, newTodo) => [...state, { ...newTodo, pending: true }],
  )

  async function action(formData: FormData) {
    const title = formData.get('title') as string
    startTransition(() => addOptimistic({ id: crypto.randomUUID(), title }))
    await createTodo(title)                 // the server action
  }

  return (
    <>
      <form action={action}><input name="title" /><button>Add</button></form>
      <ul>
        {optimisticTodos.map((t) => (
          <li key={t.id} className={t.pending ? 'opacity-50' : ''}>{t.title}</li>
        ))}
      </ul>
    </>
  )
}

// When the action finishes and the server data re-renders, the
// optimistic value is discarded and the real list takes over. If the
// action THREW, the optimistic value simply vanishes — the rollback
// is automatic, which is the whole appeal.

Handling the Failure Visibly

Silent rollback is confusing on its own.

Show pending state; never optimistic for money
// A row that appears and then disappears with no explanation reads
// as a bug. Pair the rollback with a message:
async function action(formData) {
  startTransition(() => addOptimistic({ id: tempId, title }))
  const result = await createTodo(title)
  if (!result?.ok) toast.error('Could not add that — please try again')
}

// Mark the pending item so the user knows it is not confirmed:
{t.pending && <Spinner className="h-3 w-3" />}
// Reduced opacity plus a small spinner is enough; do not make it
// look identical to a saved row.

// Use optimistic updates for:
//   likes, bookmarks, toggles, reordering, marking as read,
//   adding a row to a list
// Do NOT use them for:
//   payments, submissions, anything with a legal or financial effect,
//   anything where showing success and then reversing is worse than
//   a one-second wait

// The test: if the rollback would embarrass you in front of the user,
// wait for the server instead.

The Alternatives

Three ways to make a write feel fast, in increasing order of cost.

Pending, optimistic, or a full client cache
// 1. Pending state only — honest, and enough for most forms.
const [state, formAction, isPending] = useActionState(save, {})
<button disabled={isPending}>{isPending ? 'Saving…' : 'Save'}</button>

// 2. Optimistic — the row appears instantly, rolls back on failure.
//    Right for small, reversible, high-frequency actions.

// 3. Optimistic plus a client cache (TanStack Query) — needed when
//    the same data appears on several screens that must all update,
//    or when you want retries and offline behaviour. More machinery;
//    only worth it when 1 and 2 genuinely do not cover the case.

// A note on transitions: useOptimistic must be updated inside a
// transition, which is why startTransition wraps the call. Outside
// one, React warns and the optimistic value may not be applied.

// And keep the source of truth on the server. The optimistic value
// is a temporary VIEW, never state you then have to reconcile — the
// moment you find yourself merging optimistic and real state by hand,
// you have rebuilt the client cache badly.

Key Points to Remember

  • 1useOptimistic shows a provisional value and discards it automatically when the action settles
  • 2The rollback is automatic on failure, which is what makes the pattern safe to use
  • 3Pair a rollback with a message and mark pending items, or a vanishing row looks like a bug
  • 4Use it for likes, toggles and reordering — never for payments or submissions
  • 5Optimistic state must be updated inside a transition, and it is a temporary view rather than a second source of truth

Interview Questions

Sign in to ask Aria
1

What happens to an optimistic update if the server action fails?

Medium
2

Which actions should never be optimistic, and why?

Medium
3

Why must useOptimistic be updated inside a transition?

Hard

Ask Aria about Optimistic Updates with useOptimistic

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…