TanStack QueryOptimistic UpdatesTypeScriptUX

TanStack Query Optimistic Updates: Stop Making Users Wait for Server Responses

Stop waiting for server responses. TanStack Query optimistic updates give your users instant feedback — with the exact code that handles rollback when the server disagrees.

Sam Rivera
Sam Rivera
June 12, 202610 min read
Also available in:Deutsch · 中文

TL;DR: Optimistic updates make your SaaS app feel instant by updating the UI before the server responds. TanStack Query's onMutate callback gives you a clean pattern: snapshot the current cache, apply the optimistic change, and roll back on error. This guide covers three patterns — single item updates, list mutations, and paginated lists — with full TypeScript examples.


Introduction

Users perceive speed as a feature. A 300ms server call feels sluggish. Optimistic updates eliminate this perception by showing the result immediately and reconciling with the server in the background.

TanStack Query offers first-class support for this pattern through the useMutation hook's lifecycle callbacks.


Basic Optimistic Update

tsx
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { updateProductFn } from '../server/products'

export function useOptimisticUpdate() {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: updateProductFn,
    onMutate: async (newProduct) => {
      await queryClient.cancelQueries({ queryKey: ['product', newProduct.id] })
      const previous = queryClient.getQueryData(['product', newProduct.id])
      queryClient.setQueryData(['product', newProduct.id], newProduct)
      return { previous }
    },
    onError: (err, newProduct, context) => {
      queryClient.setQueryData(['product', newProduct.id], context?.previous)
    },
    onSettled: () => {
      queryClient.invalidateQueries({ queryKey: ['products'] })
    },
  })
}

List Mutation with Optimistic Update

For list queries, you need to manipulate the cached array:

tsx
useMutation({
  mutationFn: createProductFn,
  onMutate: async (newProduct) => {
    await queryClient.cancelQueries({ queryKey: ['products'] })
    const previous = queryClient.getQueryData(['products'])

    queryClient.setQueryData(['products'], (old: any) => ({
      pages: old.pages.map((page: any, i: number) =>
        i === 0 ? { ...page, items: [newProduct, ...page.items] } : page
      ),
      pageParams: old.pageParams,
    }))

    return { previous }
  },
  onError: (err, _, context) => {
    queryClient.setQueryData(['products'], context?.previous)
  },
})

Rollback Decision Matrix

Failure TypeUX ResponseTechnical Action
Network errorRevert + toastonError rollback
Validation errorShow field errorKeep optimistic + mark invalid
Authorization errorRevert + redirectonError + router redirect
Conflict (stale data)Revert + refetchonSettled invalidation

Conclusion

Optimistic updates dramatically improve perceived performance. TanStack Query's mutation callbacks provide a clean, testable pattern for implementing them without the complexity of manual state management.

Pair optimistic updates with proper Cache Invalidation Strategies for a complete data synchronization solution. Learn how server functions integrate with TanStack Query in Server Functions Best Practices.