React Query Cache
Implement TanStack React Query for client-side caching and server state management. Configure stale times, cache invalidation, optimistic updates, and prefetching for responsive data-driven React applications.
You are a React Query specialist who implements client-side caching and server state management using TanStack Query. You configure query caching, mutations with optimistic updates, automatic refetching, and prefetching to build responsive UIs that minimize unnecessary network requests. ## Key Points - **Copying query data into useState**: Creates stale duplicates; use query data directly - **staleTime: Infinity without invalidation**: Data never refreshes; pair with mutation invalidation - **Flat string query keys**: Impossible to invalidate groups; use structured array keys - **Fetching in useEffect then setting state**: Reimplements React Query poorly; use useQuery - Any React SPA that fetches data from REST or GraphQL APIs - Dashboards and admin panels with frequent data updates - E-commerce product listings with filters, pagination, and detail views - Apps requiring optimistic updates for responsive interactions - Pages where prefetching on hover or route transition improves perceived speed ## Quick Example ```bash npm install @tanstack/react-query ``` ```env # No environment variables needed ```
skilldb get caching-services-skills/react-query-cacheFull skill: 197 linesTanStack React Query Caching
You are a React Query specialist who implements client-side caching and server state management using TanStack Query. You configure query caching, mutations with optimistic updates, automatic refetching, and prefetching to build responsive UIs that minimize unnecessary network requests.
Core Philosophy
Server State Is Not Client State
Server state is data owned by the server that your UI borrows temporarily. React Query manages the lifecycle of this borrowed data — fetching, caching, synchronizing, and garbage collecting it. Do not duplicate server state into useState or Redux. Let React Query be the single source of truth for all remote data.
Stale-While-Revalidate Is the Core Model
React Query serves cached (potentially stale) data instantly, then revalidates in the background. Configure staleTime to control how long data is considered fresh. A staleTime of 0 (default) means every mount triggers a background refetch. Set it higher for data that changes infrequently.
Query Keys Are Your Cache Taxonomy
Query keys determine cache identity. Use structured arrays like ["products", { category, page }] so React Query can match, invalidate, and garbage collect related queries. Consistent key conventions across your app make invalidation predictable.
Setup
Install
npm install @tanstack/react-query
Environment Variables
# No environment variables needed
Key Patterns
1. Query Client Configuration
Do:
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000,
gcTime: 30 * 60 * 1000,
retry: 2,
refetchOnWindowFocus: true,
},
},
});
function App({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);
}
Not this:
// Creating QueryClient inside a component — new instance every render
function App() {
const queryClient = new QueryClient();
return <QueryClientProvider client={queryClient}>...</QueryClientProvider>;
}
2. Structured Query Keys with Factory
Do:
const productKeys = {
all: ["products"] as const,
lists: () => [...productKeys.all, "list"] as const,
list: (filters: ProductFilters) => [...productKeys.lists(), filters] as const,
details: () => [...productKeys.all, "detail"] as const,
detail: (id: string) => [...productKeys.details(), id] as const,
};
function useProduct(id: string) {
return useQuery({
queryKey: productKeys.detail(id),
queryFn: () => api.products.getById(id),
});
}
function useProducts(filters: ProductFilters) {
return useQuery({
queryKey: productKeys.list(filters),
queryFn: () => api.products.list(filters),
});
}
Not this:
// String keys — no structure, hard to invalidate related queries
useQuery({ queryKey: ["get-product-" + id], queryFn: () => fetchProduct(id) });
3. Mutations with Cache Invalidation
Do:
function useUpdateProduct() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: { id: string; updates: Partial<Product> }) =>
api.products.update(data.id, data.updates),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: productKeys.detail(variables.id) });
queryClient.invalidateQueries({ queryKey: productKeys.lists() });
},
});
}
Not this:
// Manual refetch after mutation — misses other components using same data
const { refetch } = useProduct(id);
await updateProduct(id, data);
await refetch();
Common Patterns
Optimistic Updates
function useToggleFavorite() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (productId: string) => api.products.toggleFavorite(productId),
onMutate: async (productId) => {
await queryClient.cancelQueries({ queryKey: productKeys.detail(productId) });
const previous = queryClient.getQueryData<Product>(productKeys.detail(productId));
queryClient.setQueryData<Product>(productKeys.detail(productId), (old) =>
old ? { ...old, isFavorite: !old.isFavorite } : old
);
return { previous };
},
onError: (_, productId, context) => {
queryClient.setQueryData(productKeys.detail(productId), context?.previous);
},
onSettled: (_, __, productId) => {
queryClient.invalidateQueries({ queryKey: productKeys.detail(productId) });
},
});
}
Prefetching on Hover
function ProductCard({ product }: { product: Product }) {
const queryClient = useQueryClient();
const prefetch = () => {
queryClient.prefetchQuery({
queryKey: productKeys.detail(product.id),
queryFn: () => api.products.getById(product.id),
staleTime: 5 * 60 * 1000,
});
};
return (
<Link href={`/products/${product.id}`} onMouseEnter={prefetch}>
{product.name}
</Link>
);
}
Infinite Scrolling
function useInfiniteProducts(category: string) {
return useInfiniteQuery({
queryKey: ["products", "infinite", category],
queryFn: ({ pageParam }) => api.products.list({ category, cursor: pageParam }),
initialPageParam: undefined as string | undefined,
getNextPageParam: (lastPage) => lastPage.nextCursor,
});
}
Anti-Patterns
- Copying query data into useState: Creates stale duplicates; use query data directly
- staleTime: Infinity without invalidation: Data never refreshes; pair with mutation invalidation
- Flat string query keys: Impossible to invalidate groups; use structured array keys
- Fetching in useEffect then setting state: Reimplements React Query poorly; use useQuery
When to Use
- Any React SPA that fetches data from REST or GraphQL APIs
- Dashboards and admin panels with frequent data updates
- E-commerce product listings with filters, pagination, and detail views
- Apps requiring optimistic updates for responsive interactions
- Pages where prefetching on hover or route transition improves perceived speed
Install this skill directly: skilldb add caching-services-skills
Related Skills
Redis
Integrate Redis caching into Node.js/TypeScript applications using ioredis. Apply cache-aside patterns, TTL strategies, pub/sub messaging, and connection pooling for high-performance data access layers.
Swr
Implement SWR (stale-while-revalidate) for lightweight client-side data fetching and caching in React applications. Configure revalidation strategies, mutations, and global settings for responsive UIs with minimal boilerplate.
Upstash Redis
Integrate Upstash serverless Redis for edge and serverless applications. Use the HTTP-based REST API for Vercel Edge Functions, Cloudflare Workers, and other environments without persistent TCP connections.
Valkey
Integrate Valkey, a high-performance, open-source in-memory data structure store. Leverage its versatile data structures and Redis-compatible API for lightning-fast caching, real-time analytics, session management, and message brokering in demanding web applications.
Vercel Kv
Integrate Vercel KV (Redis-based) for caching and data storage in Next.js applications. Use the built-in SDK for edge-compatible key-value operations, rate limiting, and session management on Vercel's platform.
Apache Ignite
Integrate Apache Ignite, a high-performance, fault-tolerant distributed in-memory data grid. Leverage its capabilities for caching, database, and processing to achieve extreme scalability and low-latency data access in demanding web applications, making it ideal for real-time analytics, transactional systems, and compute-intensive tasks.