Part 37: Optimistic UI Updates & Mutation Handling

Part 37 Technology Updated September 12, 2026

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 onMutate to 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!