Production-ready data fetching with TanStack Query including caching, optimistic updates, and infinite queries
# TanStack Query Advanced Patterns for Google Antigravity
Master data fetching with TanStack Query using Google Antigravity's Gemini 3 engine. This guide covers caching strategies, optimistic updates, infinite queries, and prefetching patterns.
## Query Client Configuration
```typescript
// lib/query-client.ts
import { QueryClient, QueryCache, MutationCache } from '@tanstack/react-query';
import { toast } from 'sonner';
export function createQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1 minute
gcTime: 5 * 60 * 1000, // 5 minutes (formerly cacheTime)
retry: (failureCount, error: any) => {
if (error?.status === 404 || error?.status === 401) {
return false;
}
return failureCount < 3;
},
refetchOnWindowFocus: false,
},
mutations: {
retry: false,
},
},
queryCache: new QueryCache({
onError: (error: any, query) => {
if (query.state.data !== undefined) {
toast.error(`Background refresh failed: ${error.message}`);
}
},
}),
mutationCache: new MutationCache({
onError: (error: any) => {
toast.error(`Operation failed: ${error.message}`);
},
}),
});
}
```
## Query Keys Factory
```typescript
// lib/query-keys.ts
export const queryKeys = {
products: {
all: ['products'] as const,
lists: () => [...queryKeys.products.all, 'list'] as const,
list: (filters: ProductFilters) =>
[...queryKeys.products.lists(), filters] as const,
details: () => [...queryKeys.products.all, 'detail'] as const,
detail: (id: string) => [...queryKeys.products.details(), id] as const,
},
users: {
all: ['users'] as const,
current: () => [...queryKeys.users.all, 'current'] as const,
profile: (id: string) => [...queryKeys.users.all, 'profile', id] as const,
},
orders: {
all: ['orders'] as const,
lists: () => [...queryKeys.orders.all, 'list'] as const,
list: (filters: OrderFilters) =>
[...queryKeys.orders.lists(), filters] as const,
detail: (id: string) => [...queryKeys.orders.all, 'detail', id] as const,
},
} as const;
```
## Custom Query Hooks
```typescript
// hooks/use-products.ts
import {
useQuery,
useMutation,
useQueryClient,
useInfiniteQuery,
} from '@tanstack/react-query';
import { queryKeys } from '@/lib/query-keys';
import { api } from '@/lib/api';
interface ProductFilters {
categoryId?: string;
search?: string;
sortBy?: string;
}
export function useProducts(filters: ProductFilters = {}) {
return useQuery({
queryKey: queryKeys.products.list(filters),
queryFn: () => api.products.list(filters),
placeholderData: (previousData) => previousData,
});
}
export function useInfiniteProducts(filters: ProductFilters = {}) {
return useInfiniteQuery({
queryKey: [...queryKeys.products.list(filters), 'infinite'],
queryFn: ({ pageParam = 1 }) =>
api.products.list({ ...filters, page: pageParam }),
getNextPageParam: (lastPage) =>
lastPage.pagination.page < lastPage.pagination.pages
? lastPage.pagination.page + 1
: undefined,
initialPageParam: 1,
});
}
export function useProduct(id: string) {
const queryClient = useQueryClient();
return useQuery({
queryKey: queryKeys.products.detail(id),
queryFn: () => api.products.getById(id),
initialData: () => {
// Try to find product in list cache
const lists = queryClient.getQueriesData<ProductListResponse>({
queryKey: queryKeys.products.lists(),
});
for (const [, data] of lists) {
const product = data?.items.find((p) => p.id === id);
if (product) return product;
}
return undefined;
},
});
}
export function useCreateProduct() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: api.products.create,
onSuccess: (newProduct) => {
queryClient.setQueryData(
queryKeys.products.detail(newProduct.id),
newProduct
);
queryClient.invalidateQueries({ queryKey: queryKeys.products.lists() });
},
});
}
export function useUpdateProduct() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, data }: { id: string; data: Partial<Product> }) =>
api.products.update(id, data),
onMutate: async ({ id, data }) => {
await queryClient.cancelQueries({ queryKey: queryKeys.products.detail(id) });
const previousProduct = queryClient.getQueryData<Product>(
queryKeys.products.detail(id)
);
queryClient.setQueryData<Product>(
queryKeys.products.detail(id),
(old) => (old ? { ...old, ...data } : old)
);
return { previousProduct };
},
onError: (err, { id }, context) => {
if (context?.previousProduct) {
queryClient.setQueryData(
queryKeys.products.detail(id),
context.previousProduct
);
}
},
onSettled: (_, __, { id }) => {
queryClient.invalidateQueries({ queryKey: queryKeys.products.detail(id) });
queryClient.invalidateQueries({ queryKey: queryKeys.products.lists() });
},
});
}
```
## Optimistic Updates Pattern
```typescript
// hooks/use-cart.ts
import { useMutation, useQueryClient } from '@tanstack/react-query';
interface CartItem {
id: string;
productId: string;
quantity: number;
product: Product;
}
export function useAddToCart() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (item: { productId: string; quantity: number }) =>
api.cart.addItem(item),
onMutate: async (newItem) => {
await queryClient.cancelQueries({ queryKey: ['cart'] });
const previousCart = queryClient.getQueryData<CartItem[]>(['cart']);
const product = queryClient.getQueryData<Product>(
queryKeys.products.detail(newItem.productId)
);
if (product) {
const optimisticItem: CartItem = {
id: `temp-${Date.now()}``,
productId: newItem.productId,
quantity: newItem.quantity,
product,
};
queryClient.setQueryData<CartItem[]>(['cart'], (old = []) => {
const existing = old.find((i) => i.productId === newItem.productId);
if (existing) {
return old.map((i) =>
i.productId === newItem.productId
? { ...i, quantity: i.quantity + newItem.quantity }
: i
);
}
return [...old, optimisticItem];
});
}
return { previousCart };
},
onError: (err, newItem, context) => {
if (context?.previousCart) {
queryClient.setQueryData(['cart'], context.previousCart);
}
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['cart'] });
},
});
}
```
## Prefetching Strategies
```typescript
// components/ProductList.tsx
import { useQueryClient } from '@tanstack/react-query';
import { queryKeys } from '@/lib/query-keys';
import Link from 'next/link';
export function ProductList({ products }: { products: Product[] }) {
const queryClient = useQueryClient();
const prefetchProduct = (id: string) => {
queryClient.prefetchQuery({
queryKey: queryKeys.products.detail(id),
queryFn: () => api.products.getById(id),
staleTime: 60 * 1000,
});
};
return (
<div className="grid gap-4">
{products.map((product) => (
<Link
key={product.id}
href={`/products/${product.id}`}
onMouseEnter={() => prefetchProduct(product.id)}
onFocus={() => prefetchProduct(product.id)}
>
<ProductCard product={product} />
</Link>
))}
</div>
);
}
// Server-side prefetching in Next.js
// app/products/[id]/page.tsx
import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query';
export default async function ProductPage({ params }: { params: { id: string } }) {
const queryClient = new QueryClient();
await queryClient.prefetchQuery({
queryKey: queryKeys.products.detail(params.id),
queryFn: () => api.products.getById(params.id),
});
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<ProductDetails id={params.id} />
</HydrationBoundary>
);
}
```
## Best Practices
Google Antigravity's Gemini 3 engine recommends these TanStack Query patterns: Use query key factories for type-safe and consistent cache keys. Implement optimistic updates for immediate user feedback. Leverage placeholderData for smoother transitions between cached and fresh data. Prefetch data on hover for instant navigation. Configure proper staleTime and gcTime for optimal caching.This TanStack Query prompt is ideal for developers working on:
By using this prompt, you can save hours of manual coding and ensure best practices are followed from the start. It's particularly valuable for teams looking to maintain consistency across their tanstack query implementations.
Yes! All prompts on Antigravity AI Directory are free to use for both personal and commercial projects. No attribution required, though it's always appreciated.
This prompt works excellently with Claude, ChatGPT, Cursor, GitHub Copilot, and other modern AI coding assistants. For best results, use models with large context windows.
You can modify the prompt by adding specific requirements, constraints, or preferences. For TanStack Query projects, consider mentioning your framework version, coding style, and any specific libraries you're using.