typescript 73 lines · 2 tabs

Optimistic updates with React Query mutations

Maya Patel Jan 2026
2 tabs
import { useMutation, useQueryClient } from '@tanstack/react-query'
import api from '@/services/api'
import { Post, PostId } from '@/types'

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

  return useMutation({
    mutationFn: async (postId: PostId) => {
      const { data } = await api.post(`/posts/${postId}/like`)
      return data
    },

    onMutate: async (postId: PostId) => {
      // Cancel outgoing refetches
      await queryClient.cancelQueries({ queryKey: ['posts'] })

      // Snapshot previous value
      const previousPosts = queryClient.getQueryData<Post[]>(['posts'])

      // Optimistically update cache
      queryClient.setQueryData<Post[]>(['posts'], (old) =>
        old?.map((post) =>
          post.id === postId
            ? { ...post, likes_count: post.likes_count + 1, liked_by_user: true }
            : post
        )
      )

      // Return snapshot for rollback
      return { previousPosts }
    },

    onError: (err, postId, context) => {
      // Rollback on error
      if (context?.previousPosts) {
        queryClient.setQueryData(['posts'], context.previousPosts)
      }
    },

    onSettled: () => {
      // Always refetch after error or success
      queryClient.invalidateQueries({ queryKey: ['posts'] })
    },
  })
}
2 files · typescript Explain with highlit

Optimistic updates make UIs feel instant by immediately showing the expected result before the server confirms. When a user likes a post, I update the local cache immediately, submit the request in background, and rollback if it fails. React Query's mutation callbacks (onMutate, onError, onSettled) orchestrate this flow. The onMutate function snapshots the current cache, updates it optimistically, and returns the snapshot for potential rollback. If the mutation fails, onError restores the snapshot. The onSettled callback runs regardless of success/failure to ensure cache consistency. This pattern dramatically improves perceived performance for actions like likes, follows, or simple edits where failures are rare.


Related snips

typescript
export interface RetryOptions {
  retries: number;
  baseMs: number;
  maxMs: number;
  signal?: AbortSignal;
  onRetry?: (attempt: number, delay: number, err: unknown) => void;

Exponential backoff with jitter for retries

typescript reliability retry
by codesnips 2 tabs
typescript
import axios, { AxiosError } from 'axios'
import { v4 as uuidv4 } from 'uuid'

const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000/api/v1',
  timeout: 15000,

Axios API client with interceptors

react axios api
by Maya Patel 1 tab
typescript
import { create } from 'zustand'
import { persist } from 'zustand/middleware'

interface FilterState {
  search: string
  category: string | null

Zustand for lightweight state management

react zustand state-management
by Maya Patel 2 tabs
typescript
import React from "react";

type FallbackProps = {
  error: Error;
  reset: () => void;
};

React Error Boundary + error reporting hook

react frontend error-boundary
by codesnips 3 tabs
javascript
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
  openAnalyzer: true,
});

/** @type {import('next').NextConfig} */

Next.js bundle analyzer for targeted performance work

nextjs performance tooling
by codesnips 4 tabs
typescript
import { Navigate, useLocation } from 'react-router-dom'
import { useAuth } from '@/contexts/AuthContext'

interface ProtectedRouteProps {
  children: React.ReactNode
}

React Router with protected routes

react react-router routing
by Maya Patel 2 tabs

Share this code

Here's the card — post it anywhere.

Optimistic updates with React Query mutations — share card
Link copied