Part 37: Optimistic UI Updates & Mutation Handling
Optimistic UI is a pattern where the user interface updates immediately upon user action (e.g. liking a post or adding a comment), assuming the server request will succeed. If the server request fails, the UI automatically rolls back.
1. Implementing Optimistic Updates with React Query
import { useMutation, useQueryClient } from '@tanstack/react-query';
export function LikeButton({ postId, initialLiked, initialCount }) {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: async () => {
const res = await fetch(`/api/posts/${postId}/like`, { method: 'POST' });
if (!res.ok) throw new Error('Like failed');
return res.json();
},
// When mutate is called:
onMutate: async () => {
// 1. Cancel any outgoing refetches so they don't overwrite optimistic update
await queryClient.cancelQueries({ queryKey: ['post', postId] });
// 2. Snapshot the previous value
const previousPost = queryClient.getQueryData(['post', postId]);
// 3. Optimistically update the cache
queryClient.setQueryData(['post', postId], (old) => ({
...old,
isLiked: !old.isLiked,
likesCount: old.isLiked ? old.likesCount - 1 : old.likesCount + 1
}));
// Return context with snapshot
return { previousPost };
},
// If mutation fails, roll back to snapshot:
onError: (err, newTodo, context) => {
queryClient.setQueryData(['post', postId], context.previousPost);
},
// Always refetch after error or success to synchronize true server state:
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['post', postId] });
},
});
return (
<button onClick={() => mutation.mutate()} className="btn-like">
❤️ Like
</button>
);
}
Key Takeaways
- Optimistic updates make applications feel instantaneous to users.
- Always store a rollback snapshot in
onMutateto recover gracefully if the API call fails. - Re-validate with the server on completion (
onSettled).
In Part 38, we will build Pagination, Infinite Scrolling & Virtualized Lists!

