Instant UI with Optimistic Updates
useOptimistic shows an optimistic (assumed successful) UI update while the Server Action is running. If the action fails, the UI reverts. This makes interactions feel instant even with network latency.
Todo List with Optimistic Toggle
// app/todos/todo-list.tsx
'use client';
import { useOptimistic } from 'react';
import { toggleTodo } from '@/actions/todos';
interface Todo {
id: string;
text: string;
completed: boolean;
}
export function TodoList({ todos }: { todos: Todo[] }) {
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(currentTodos, toggledId: string) =>
currentTodos.map(todo =>
todo.id === toggledId
? { ...todo, completed: !todo.completed }
: todo
)
);
async function handleToggle(id: string) {
addOptimisticTodo(id); // Update UI immediately
await toggleTodo(id); // Then update server
}
return (
<ul className="space-y-2">
{optimisticTodos.map(todo => (
<li
key={todo.id}
className="flex items-center gap-3 p-3 border rounded-lg"
>
<button
onClick={() => handleToggle(todo.id)}
className={`w-5 h-5 rounded border-2 flex items-center justify-center
${todo.completed
? 'bg-green-500 border-green-500'
: 'border-gray-300'}`}
>
{todo.completed && (
<svg className="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 12 12">
<path d="M10 3L5 8.5 2 5.5" stroke="white" strokeWidth="2" fill="none"/>
</svg>
)}
</button>
<span className={todo.completed ? 'line-through text-gray-400' : ''}>
{todo.text}
</span>
</li>
))}
</ul>
);
}
Like Button with Optimistic Count
'use client';
import { useOptimistic, useTransition } from 'react';
import { likePost } from '@/actions/posts';
export function LikeButton({
postId,
initialCount,
initialLiked,
}: {
postId: string;
initialCount: number;
initialLiked: boolean;
}) {
const [, startTransition] = useTransition();
const [optimisticState, setOptimistic] = useOptimistic(
{ count: initialCount, liked: initialLiked },
(current, newLiked: boolean) => ({
count: newLiked ? current.count + 1 : current.count - 1,
liked: newLiked,
})
);
function handleLike() {
startTransition(async () => {
setOptimistic(!optimisticState.liked);
await likePost(postId);
});
}
return (
<button onClick={handleLike} className="flex items-center gap-2">
<span>{optimisticState.liked ? '❤️' : '🤍'}</span>
<span>{optimisticState.count}</span>
</button>
);
}
Optimistic updates bring native-app responsiveness to web applications — users see immediate feedback instead of waiting for network roundtrips.