Optimistic UI with `useOptimistic` in Next.js

How to use the useOptimistic hook to show instant UI while a Server Action runs, and how it reverts when the action fails.

7 min read

useOptimistic is a React hook that shows a temporary value while a Server Action is in flight, then snaps back to the real value when the action settles. It is how Next.js apps make likes, follows, and list changes feel instant instead of waiting on the server. It does not fetch anything itself; it only changes what renders while your action runs.

Here is a like button in a Client Component file such as app/like-button.tsx.

App.tsxApp.tsx
'use client'
import { useOptimistic } from 'react'
import { toggleLike } from './actions'
export function LikeButton({ liked }: { liked: boolean }) {
  const [optimisticLiked, setOptimisticLiked] = useOptimistic(liked)
  async function likeAction() {
    setOptimisticLiked(!optimisticLiked)
    await toggleLike(!optimisticLiked)
  }
  return <form action={likeAction}><button>{optimisticLiked ? 'Unlike' : 'Like'}</button></form>
}

The moment the visitor clicks, the button label flips to Unlike, even though the server has not answered yet. When toggleLike resolves, the parent updates the real liked prop and the optimistic value converges on it. The component needs use client because the hook runs in the browser.

How the temporary state works

The optimistic value only exists while a transition is pending. Before the click it equals liked, during the action it equals the value you set, and after the action it equals liked again. The optimistic value and the real value converge in a single render when the transition completes, so there is no extra render to clear it.

If the action fails, the transition still ends, and React renders the real liked value. Because the parent only updates liked on success, a failure makes the button silently flip back to Like. That rollback is automatic, but you should still catch the error to show a message.

The setter runs inside a transition

The setter from useOptimistic must be called inside a transition or an action prop. A form action prop already runs inside a transition, which is why likeAction can call setOptimisticLiked directly with no extra wrapping.

Calling the setter outside an action triggers a React warning, and the optimistic value appears only briefly before snapping back. The form's action prop is what keeps the temporary value alive for the whole request.

For a plain click handler that is not a form, wrap the setter and the action call in startTransition. See calling Server Actions from event handlers and useTransition for that pattern.

Reducer for lists and multiple values

useOptimistic takes an optional second argument, a reducer. Without one, the setter takes the next value directly, which is all a toggle needs. With one, the setter takes a description of the change and the reducer decides how to apply it.

Second argumentSetter receivesUse it when
OmittedThe next valueThe change is a toggle or an increment
A reducerThe change to applyYou add or remove items from a list

The reducer receives the current list and the change, and returns the next list. That matters when the base list changes while your action is pending, such as another visitor adding a row at the same time.

A comments list, for example, can optimistically append the new comment with a pending flag, then swap it for the saved record when the server responds.

The reducer runs against the current state each time, so the append lands on top of whatever the latest list holds. That is what you want when the base data can change underneath a pending action.

See useActionState for pending state and server errors when you also want the server's return value and error handling, and how to mutate data with a Server Action for the action that actually writes the change.

When the action fails

If the Server Action throws, React ends the transition and renders the real value again. The optimistic change disappears, so a failed delete brings the row back and a failed like unlikes the post.

That rollback is what makes optimistic UI safe. The worst case is a brief flicker back to the old state rather than a UI that claims a change the server never accepted.

It is not a complete failure story though. The rollback alone does not tell the visitor why anything failed, so catch the error around the action call and show a message next to the control they used.

Rune AI

Rune AI

Key Insights

  • useOptimistic shows a temporary value while an action is in flight.
  • Call the setter inside a transition or a form action prop.
  • The optimistic value reverts to the real value when the action settles.
  • Omit the reducer for simple toggles and pass one for lists.
  • The real value must update on success, or the UI snaps back.
RunePowered by Rune AI

Frequently Asked Questions

Does useOptimistic work with Server Actions?

Yes. Wrap a Server Action call in a transition, update the optimistic value first, then await the action. The UI shows the optimistic value until the server confirms.

What happens when the action fails?

The optimistic value is temporary. If the action throws, the transition ends and the UI renders the real value again, which rolls back the optimistic change.

Do I always need startTransition?

No. A form action prop already runs inside a transition, so the setter works there directly. For plain click handlers, wrap the call in startTransition.

Conclusion

useOptimistic renders a temporary value while a Server Action runs, then converges on the real value. Call the setter inside a transition, update the optimistic state before awaiting the action, and let React roll back automatically on failure.