Introduction
You know that satisfying feeling when you like a tweet and the heart turns red instantly? That's optimistic UI — the app shows the result before the server confirms it. If the server fails, it rolls back.
Before React 19, implementing this pattern required a lot of boilerplate. Now, the useOptimistic hook makes it trivial. Here's how it works.
What is useOptimistic?
useOptimistic takes your current state and returns an "optimistic" version of it — one that updates immediately when you trigger an action, and reverts to the real state once the async operation settles.
const [optimisticState, addOptimistic] = useOptimistic(state, updateFn);
Example 1: Like button
"use client";
import { useOptimistic, useState } from "react";
import { toggleLike } from "@/actions/likes";
export function LikeButton({ postId, initialLikes }: { postId: string; initialLikes: number }) {
const [likes, setLikes] = useState(initialLikes);
const [liked, setLiked] = useState(false);
const [optimisticLikes, addOptimisticLike] = useOptimistic(
likes,
(currentLikes, newLiked: boolean) => currentLikes + (newLiked ? 1 : -1)
);
async function handleLike() {
const newLiked = !liked;
addOptimisticLike(newLiked); // Instant UI update
setLiked(newLiked);
try {
const updated = await toggleLike(postId);
setLikes(updated.likes);
} catch {
setLiked(!newLiked); // Rollback on error
}
}
return (
);
}
Example 2: Optimistic todo list
"use client";
import { useOptimistic, useState } from "react";
import { addTodo } from "@/actions/todos";
export function TodoList({ initialTodos }) {
const [todos, setTodos] = useState(initialTodos);
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(currentTodos, newTodo) => [...currentTodos, { ...newTodo, pending: true }]
);
async function handleSubmit(formData: FormData) {
const text = formData.get("text") as string;
const tempTodo = { id: Date.now(), text, completed: false };
addOptimisticTodo(tempTodo); // Show immediately
const savedTodo = await addTodo(text);
setTodos((prev) => [...prev, savedTodo]);
}
return (
{optimisticTodos.map((todo) => (
-
{todo.text}
))}
);
}
Notice the pending: true flag — you can use this to show a loading style on items that haven't been confirmed yet.
When to use useOptimistic
- Like / upvote / follow buttons
- Adding items to a list
- Marking items as complete
- Any mutation where the success rate is high and latency matters
Don't use it for destructive actions (deleting accounts, financial transactions) where showing a false state could be harmful.
Conclusion
useOptimistic is one of my favourite additions in React 19. It codifies a pattern that previously required custom state management into a clean, two-line hook. Combined with Server Actions, it makes building responsive, server-synced UIs dramatically simpler.
