Skip to main content

tanstack-query

Comprehensive TanStack Query v5 patterns for async state management. Covers breaking changes, query key factories, data transformation, mutations, optimistic updates, authentication, testing with MSW, and anti-patterns. Use for all server state management, data fetching, and cache invalidation tasks.

Informations de source

Dépôt
MadAppGang/claude-code
Dernière activité de la source
31 janvier 2026 à 03:08
Langue détectée de SKILL.md
anglais
Étoiles
284
Forks
25

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
tanstack-query
description
Comprehensive TanStack Query v5 patterns for async state management. Covers breaking changes, query key factories, data transformation, mutations, optimistic updates, authentication, testing with MSW, and anti-patterns. Use for all server state management, data fetching, and cache invalidation tasks.
# TanStack Query v5 - Complete Guide **TanStack Query v5** (October 2023) is the async state manager for this project. It requires React 18+, features first-class Suspense support, improved TypeScript inference, and a 20% smaller bundle. This section covers production-ready patterns based on official documentation and community best practices. ### Breaking Changes in v5 **Key updates you need to know:** 1. **Single Object Signature**: All hooks now accept one configuration object: ```typescript // ✅ v5 - single object useQuery({ queryKey, queryFn, ...options }) // ❌ v4 - multiple overloads (deprecated) useQuery(queryKey, queryFn, options) ``` 2. **Renamed Options**: - `cacheTime` → `gcTime` (garbage collection time) - `keepPreviousData` → `placeholderData: keepPreviousData` - `isLoading` now means `isPending && isFetching` 3. **Callbacks Removed from useQuery**: - `onSuccess`, `onError`, `onSettled` removed from `useQuery` - Use global QueryCache callbacks instead - Prevents duplicate executions 4. **Infinite Queries Require initialPageParam**: - No default value provided - Must explicitly set `initialPageParam` (e.g., `0` or `null`) 5. **First-Class Suspense**: - New dedicated hooks: `useSuspenseQuery`, `useSuspenseInfiniteQuery` - No experimental flag needed - Data is never undefined at type level **Migration**: Use the official codemod for automatic migration: `npx @tanstack/query-codemods v5/replace-import-specifier` ### Smart Defaults Query v5 ships with production-ready defaults: ```typescript { staleTime: 0, // Data instantly stale (refetch on mount) gcTime: 5 * 60_000, // Keep unused cache for 5 minutes retry: 3, // 3 retries with exponential backoff refetchOnWindowFocus: true,// Refetch when user returns to tab refetchOnReconnect: true, // Refetch when network reconnects } ``` **Philosophy**: React Query is an **async state manager, not a data fetcher**. You provide the Promise; Query manages caching, background updates, and synchronization. ### Client Setup ```typescript // src/app/providers.tsx import { QueryClient, QueryClientProvider, QueryCache } from '@tanstack/react-query' import { toast } from './toast' // Your notification system const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 0, // Adjust per-query gcTime: 5 * 60_000, // 5 minutes (v5: formerly cacheTime) retry: (failureCount, error) => { // Don't retry on 401 (authentication errors) if (error?.response?.status === 401) return false return failureCount < 3 }, }, }, queryCache: new QueryCache({ onError: (error, query) => { // Only show toast for background errors (when data exists) if (query.state.data !== undefined) { toast.error(`Something went wrong: ${error.message}`) } }, }), }) export function AppProviders({ children }: { children: React.ReactNode }) { return ( <QueryClientProvider client={queryClient}> {children} </QueryClientProvider> ) } ``` **DevTools Setup** (auto-excluded in production): ```typescript import { ReactQueryDevtools } from '@tanstack/react-query-devtools' <QueryClientProvider client={queryClient}> {children} <ReactQueryDevtools initialIsOpen={false} /> </QueryClientProvider> ``` ### Server-Side Rendering Configuration When using TanStack Query with SSR (Next.js, Remix, TanStack Start), configure server-specific defaults: ```typescript // Server-side QueryClient configuration export function makeQueryClient() { return new QueryClient({ defaultOptions: { queries: { // Server: Don't retry on server (fail fast) retry: typeof window === 'undefined' ? 0 : 3, // Server: Data is always fresh when rendered staleTime: 60_000, // 1 minute }, }, }) } ``` **Server vs Client Defaults:** | Option | Client Default | Server Recommended | Why | |--------|---------------|-------------------|-----| | `retry` | 3 | 0 | Server should fail fast, not retry loops | | `staleTime` | 0 | 60_000+ | Server-rendered data is fresh | | `gcTime` | 5 min | Infinity | No garbage collection needed on server | | `refetchOnWindowFocus` | true | false | No window on server | | `refetchOnReconnect` | true | false | No reconnect on server | **Important:** In SPA-only apps (TanStack Router + Vite), you don't need these server defaults. They're only relevant for SSR frameworks. ### Streaming SSR (Experimental) For Next.js App Router, `@tanstack/react-query-next-experimental` enables streaming: ```bash pnpm add @tanstack/react-query-next-experimental ``` **Setup:** ```typescript // app/providers.tsx 'use client' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { ReactQueryStreamedHydration } from '@tanstack/react-query-next-experimental' function makeQueryClient() { return new QueryClient({ defaultOptions: { queries: { staleTime: 60_000 }, }, }) } let browserQueryClient: QueryClient | undefined function getQueryClient() { if (typeof window === 'undefined') { return makeQueryClient() // Server: always new } return (browserQueryClient ??= makeQueryClient()) // Browser: singleton } export function Providers({ children }: { children: React.ReactNode }) { const queryClient = getQueryClient() return ( <QueryClientProvider client={queryClient}> <ReactQueryStreamedHydration>{children}</ReactQueryStreamedHydration> </QueryClientProvider> ) } ``` **Usage in Client Components:** ```typescript 'use client' import { useSuspenseQuery } from '@tanstack/react-query' export function UserProfile({ userId }: { userId: string }) { // No prefetch needed! Data streams from server const { data: user } = useSuspenseQuery({ queryKey: ['user', userId], queryFn: () => fetchUser(userId), }) return <div>{user.name}</div> } ``` **Benefits:** - Skip manual prefetching in Server Components - Data streams to client as it resolves - Suspense boundaries show loading states naturally **Limitations:** - Next.js App Router only (experimental) - Not for TanStack Router SPAs (use route loaders instead) ### Server Components Integration **When you have React Server Components (RSC)**, how does TanStack Query fit? #### The Mental Model Think of Server Components as **another framework loader** (like route loaders): | Feature | Server Components | TanStack Query | |---------|-------------------|----------------| | Initial data fetch | Yes (server) | Yes (client prefetch) | | Client mutations | No | Yes | | Background refetch | No | Yes | | Optimistic updates | No | Yes | | Real-time updates | No | Yes | | Cache management | No | Yes | #### When TanStack Query is Still Valuable Even in RSC-heavy apps, Query remains essential for: 1. **Client-Side Mutations** ```typescript // Server Component fetches, Client handles mutations export default async function PostPage({ params }) { const post = await fetchPost(params.id) // Server fetch return <PostWithComments post={post} /> // Client mutations } 'use client' function PostWithComments({ post }) { const addComment = useMutation({ ... }) // Still need Query! // ... } ``` 2. **Background Refetching After Initial Load** ```typescript // Initial: Server Component renders with fresh data // After: Query keeps data fresh on client ``` 3. **Optimistic Updates** ```typescript // Can't do optimistic updates with Server Components alone const likeMutation = useMutation({ mutationFn: likePost, onMutate: async () => { // Optimistic update - only possible with Query }, }) ``` 4. **Real-Time Updates** ```typescript // WebSocket data, polling, etc. - client-only useQuery({ queryKey: ['notifications'], queryFn: fetchNotifications, refetchInterval: 30_000, // Real-time polling }) ``` #### Recommended Pattern ```typescript // Server Component: Initial fetch export default async function DashboardPage() { const initialData = await fetchDashboard() return ( <HydrationBoundary state={dehydrate(queryClient)}> <DashboardClient initialData={initialData} /> </HydrationBoundary> ) } // Client Component: Mutations + real-time 'use client' function DashboardClient({ initialData }) { // Query hydrates from server data, then manages client state const { data } = useQuery({ queryKey: ['dashboard'], queryFn: fetchDashboard, initialData, }) const updateWidget = useMutation({ ... }) // ... } ``` #### SPA Recommendation **For SPA-only apps (TanStack Router + Vite)**: Server Components don't apply. Use TanStack Query as your primary data layer with route loaders for prefetching. ### Query + React 19 Actions When using React 19 Actions alongside Query, keep responsibilities clear: #### Complementary Usage ```typescript // Query: Fetching and caching const { data: posts } = useQuery({ queryKey: ['posts'], queryFn: fetchPosts, }) // Action: Form submission with built-in validation async function createPostAction(formData: FormData) { 'use server' const result = await createPost(formData) return result } // After action succeeds, invalidate Query cache const [state, formAction] = useActionState(async (prev, formData) => { const result = await createPostAction(formData) if (result.success) { queryClient.invalidateQueries({ queryKey: ['posts'] }) } return result }, { success: false }) ``` #### Decision Matrix | Use Case | Recommendation | |----------|----------------| | Data fetching | Query (`useQuery`) | | List caching | Query | | Form submission | Action (`useActionState`) + Query invalidation | | Button click mutation | Query (`useMutation`) | | Optimistic update with rollback | Query (`useMutation`) | | Server-side validation | Action | | Complex multi-step mutations | Query (`useMutation`) | #### Rule of Thumb - **Actions** for form submissions with server-side validation - **Query** for everything else (fetching, caching, complex mutations) - **Both** when forms need to update Query cache after success ### Architecture: Feature-Based Colocation **Recommended pattern**: Group queries with related features, not by file type. ``` src/features/ ├── Todos/ │ ├── index.tsx # Feature entry point │ ├── queries.ts # All React Query logic (keys, functions, hooks) │ ├── types.ts # TypeScript types │ └── components/ # Feature-specific components ``` **Export only custom hooks** from query files. Keep query functions and keys private: ```typescript // features/todos/queries.ts // 1. Query Key Factory (hierarchical structure) const todoKeys = { all: ['todos'] as const, lists: () => [...todoKeys.all, 'list'] as const, list: (filters: string) => [...todoKeys.lists(), { filters }] as const, details: () => [...todoKeys.all, 'detail'] as const, detail: (id: number) => [...todoKeys.details(), id] as const, } // 2. Query Function (private) const fetchTodos = async (filters: string): Promise<Todo[]> => { const response = await axios.get('/api/todos', { params: { filters } }) return response.data } // 3. Custom Hook (public API) export const useTodosQuery = (filters: string) => { return useQuery({ queryKey: todoKeys.list(filters), queryFn: () => fetchTodos(filters), staleTime: 30_000, // Fresh for 30 seconds }) } ``` **Benefits**: - Prevents key/function mismatches - Clean public API - Encapsulation and maintainability - Easy to locate all query logic for a feature ### Query Key Factories (Essential) **Structure keys hierarchically** from generic to specific: ```typescript // ✅ Correct hierarchy ['todos'] // Invalidates everything ['todos', 'list'] // Invalidates all lists ['todos', 'list', { filters }] // Invalidates specific list ['todos', 'detail', 1] // Invalidates specific detail
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub