- Next.js
- Server Actions & Form Mutations
- Optimistic UI Updates with useOptimistic
Optimistic UI Updates with useOptimistic
useOptimistic is a React 19 hook that renders predicted mutation outcomes immediately in the UI before server confirmation. If the server action succeeds, the optimistic state is replaced seamlessly by the fresh server state. If the action fails or rejects, React automatically rolls back the UI to the prior confirmed state without manual compensation code.
Learning Objectives
Implement zero-latency UI interactions using React 19 useOptimistic.
Design rollback handling when asynchronous Server Actions fail or throw errors.
Combine useOptimistic with useTransition and Server Actions for smooth UX.
Prevent race conditions when multiple optimistic updates occur rapidly.
Prerequisites & Background Knowledge
Why Interviewers Care
High-impact senior engineering topic. Evaluates candidate mastery of polished, perceived-performance UI architectures (e.g. social likes, chat messages, todo items).
The Challenge
Demonstrate how to implement an optimistic "Like" counter using React 19 useOptimistic and a Next.js Server Action. Ensure immediate UI feedback and seamless rollback on network failure.
Code Architecture & Implementation
1'use client';23import { useOptimistic, useTransition } from 'react';4import { toggleLikeAction } from '@/app/actions/likes';56interface LikeButtonProps {7 postId: string;8 initialLikes: number;9 initialLiked: boolean;10}1112export function LikeButton({ postId, initialLikes, initialLiked }: LikeButtonProps) {13 const [isPending, startTransition] = useTransition();1415 // useOptimistic takes confirmed state and an updater function16 const [optimisticState, setOptimisticState] = useOptimistic(17 { likes: initialLikes, isLiked: initialLiked },18 (state, update: { isLiked: boolean }) => ({19 likes: update.isLiked ? state.likes + 1 : state.likes - 1,20 isLiked: update.isLiked,21 })22 );2324 const handleToggle = () => {25 const nextLiked = !optimisticState.isLiked;2627 startTransition(async () => {28 // 1. Immediately apply optimistic UI change29 setOptimisticState({ isLiked: nextLiked });3031 try {32 // 2. Dispatch Server Action33 await toggleLikeAction(postId, nextLiked);34 } catch (error) {35 // 3. If action throws, React automatically rolls back optimisticState36 // to match confirmed props!37 console.error('Failed to toggle like:', error);38 }39 });40 };4142 return (43 <button44 onClick={handleToggle}45 disabled={isPending}46 className={`flex items-center gap-2 px-3 py-1 rounded border ${47 optimisticState.isLiked ? 'bg-red-50 text-red-600' : 'bg-gray-50'48 }`}49 >50 <span>{optimisticState.isLiked ? '❤️' : '🤍'}</span>51 <span>{optimisticState.likes}</span>52 </button>53 );54}
Architectural Explanation
Prior to React 19, developers had to manually maintain duplicate state variables (isOptimistic, previousValue), handle try/catch blocks, and write tedious rollback logic. useOptimistic streamlines this: you supply the current authoritative state and a reducer-like updater. When setOptimisticState is called inside a transition, React updates the UI synchronously. Once the async server transition completes, React replaces the optimistic value with the fresh server result. If an error is caught and the transition terminates, React drops the optimistic branch and reverts to the original state.
Common Mistakes & Anti-Patterns
Calling setOptimisticState outside of `startTransition` or a form action, resulting in React console warnings.
Mutating state objects directly inside the optimistic updater instead of returning a new state object.
Not revalidating server cache (`revalidatePath`), causing the UI to revert to stale data once the transition finishes.
Interview Follow-Up Traps
The interviewer asks: "What happens if the user clicks 5 times rapidly?" In React 19, transitions are queued; each optimistic update layers on top of the current state, and the server processes them in sequence.
Frequently Asked Questions
Can useOptimistic be used with React 19 useActionState?
Yes! They integrate cleanly: useActionState manages confirmed action status while useOptimistic provides immediate visual feedback.
What happens if the server action takes 5 seconds to respond?
The optimistic state remains visible to the user throughout the 5 seconds until the promise resolves or rejects.
