Skip to main content
Technology & EngineeringCaching Services197 lines

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.

Quick Summary25 lines
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 lines
Paste into your CLAUDE.md or agent config

TanStack 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

Get CLI access →

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.

Caching Services165L

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.

Caching Services206L

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.

Caching Services167L

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.

Caching Services207L

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.

Caching Services175L

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.

Caching Services257L