Instant UI Updates with React useOptimistic Hook
Problem
When a user clicks a “like” button, the UI freezes until the server responds. Even a 500ms delay makes users wonder if their click registered. A loading spinner feels excessive for such a small interaction.
Solution
React 19’s useOptimistic hook lets you update the UI immediately and automatically rolls back if the server request fails.
import { useOptimistic } from 'react';
function LikeButton({ postId, initialCount }: {
postId: string;
initialCount: number;
}) {
const [optimisticCount, setOptimisticCount] = useOptimistic(
initialCount,
(current, increment: number) => current + increment
);
async function handleLike() {
setOptimisticCount(1); // Update UI instantly
await likePost(postId); // Server request in the background
}
return (
<button onClick={handleLike}>
{optimisticCount}
</button>
);
}
A more practical example — applying it to a comment list:
function CommentList({ comments }: { comments: Comment[] }) {
const [optimisticComments, addOptimisticComment] = useOptimistic(
comments,
(state, newComment: Comment) => [...state, newComment]
);
async function handleSubmit(text: string) {
const tempComment = {
id: crypto.randomUUID(),
text,
pending: true,
};
addOptimisticComment(tempComment); // Add to list immediately
await postComment(text); // Save to server
}
return (
<ul>
{optimisticComments.map((c) => (
<li key={c.id} style={{ opacity: c.pending ? 0.6 : 1 }}>
{c.text}
</li>
))}
</ul>
);
}
Key Points
useOptimisticshows the optimistic state only while the async action is in progress, then reverts to the actual state on completion- Failed server requests automatically roll back to the original state
- Adding a
pendingflag for visual feedback (like reduced opacity) further improves the user experience