TanStack Query mit Claude Code: Server State Management 2026

TanStack Query (ehemals React Query) ist der De-facto-Standard für Server State in React — automatisches Caching, Background-Refetching, Optimistic Updates. Claude Code kennt alle Patterns: von einfachen useQuery-Calls bis zu komplexen Infinite Scroll und Optimistic Mutations.

Setup und QueryClient-Konfiguration

SetupQueryClient für Production konfigurieren

# Prompt: "Richte TanStack Query v5 ein mit sinnvollen Production-Defaults" # Installation npm install @tanstack/react-query @tanstack/react-query-devtools # main.tsx — QueryClient mit Production-Defaults import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { ReactQueryDevtools } from '@tanstack/react-query-devtools' const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 60 * 1000, // 1 Min frisch — kein Re-fetch bei Mount gcTime: 5 * 60 * 1000, // 5 Min Cache-Haltezeit (ehem. cacheTime) retry: 2, // 2x retry bei Netzwerkfehler refetchOnWindowFocus: false, // Kein Re-fetch bei Tab-Wechsel throwOnError: false // Error Boundaries opt-in }, mutations: { retry: 0 // Mutations NICHT wiederholen } } }) export default function App() { return ( <QueryClientProvider client={queryClient}> <Router /> {import.meta.env.DEV && <ReactQueryDevtools />} </QueryClientProvider> ) }
v5-Breaking Change: cacheTime heißt jetzt gcTime. Claude Code kennt v5 und generiert immer aktuellen Code — kein manuelles Nachschauen in der Changelog nötig.

useQuery: Daten laden und cachen

useQueryTypisierte Queries mit Query Keys

# Prompt: "useQuery für User-Profil, typisiert, mit Error-Handling und Loading-State" // api/users.ts — API-Layer interface User { id: string name: string email: string plan: 'free' | 'pro' | 'enterprise' } async function fetchUser(userId: string): Promise<User> { const res = await fetch(`/api/users/${userId}`) if (!res.ok) throw new Error(`HTTP ${res.status}`) return res.json() } // Query Keys als Konstanten (SSOT) export const userKeys = { all: ['users'] as const, detail: (id: string) => ['users', id] as const, list: (filters: Record<string, unknown>) => ['users', 'list', filters] as const } // hooks/useUser.ts export function useUser(userId: string) { return useQuery({ queryKey: userKeys.detail(userId), queryFn: () => fetchUser(userId), enabled: Boolean(userId), // Nur wenn userId vorhanden select: (data) => ({ // Transformieren ohne extra Render ...data, isPro: data.plan !== 'free' }) }) } // UserProfile.tsx — Nutzung function UserProfile({ userId }: { userId: string }) { const { data: user, isPending, isError, error } = useUser(userId) if (isPending) return <Skeleton /> if (isError) return <Alert>{error.message}</Alert> return <div>{user.name} — {user.isPro ? 'Pro' : 'Free'}</div> }

useMutation: Daten schreiben

MutationCreate, Update, Delete mit Cache-Invalidierung

# Prompt: "useMutation für User-Update mit automatischer Cache-Invalidierung" export function useUpdateUser() { const queryClient = useQueryClient() return useMutation({ mutationFn: ({ id, data }: { id: string; data: Partial<User> }) => fetch(`/api/users/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }).then(r => r.json()), // Cache nach Mutation aktualisieren onSuccess: (updatedUser, { id }) => { // Spezifischen User direkt aktualisieren queryClient.setQueryData(userKeys.detail(id), updatedUser) // User-Listen invalidieren (werden neu geladen) queryClient.invalidateQueries({ queryKey: userKeys.all }) }, onError: (error) => { toast.error(`Update fehlgeschlagen: ${error.message}`) } }) } // Nutzung in Komponente const updateUser = useUpdateUser() const handleSubmit = (data: Partial<User>) => { updateUser.mutate({ id: userId, data }, { onSuccess: () => toast.success('Profil gespeichert') }) }

Optimistic Updates: Sofortiges UI-Feedback

OptimisticUI sofort updaten, bei Fehler zurückrollen

# Prompt: "Optimistic Update für Todo-Toggle — sofortiges UI-Feedback" export function useToggleTodo() { const queryClient = useQueryClient() return useMutation({ mutationFn: (todoId: string) => fetch(`/api/todos/${todoId}/toggle`, { method: 'POST' }), // 1. Optimistisch updaten BEVOR API antwortet onMutate: async (todoId) => { // Laufende Queries canceln (verhindert Überschreiben) await queryClient.cancelQueries({ queryKey: ['todos'] }) // Snapshot für Rollback sichern const prev = queryClient.getQueryData(['todos']) // Optimistisch updaten queryClient.setQueryData(['todos'], (old: Todo[]) => old.map(t => t.id === todoId ? { ...t, done: !t.done } : t) ) return { prev } // Kontext für onError }, // 2. Bei Fehler: zurückrollen onError: (_err, _todoId, context) => { queryClient.setQueryData(['todos'], context?.prev) }, // 3. Nach Erfolg oder Fehler: Daten neu laden onSettled: () => { queryClient.invalidateQueries({ queryKey: ['todos'] }) } }) }
Optimistic Pattern Tipp: "Implementiere Optimistic Updates für [Feature] mit automatischem Rollback bei API-Fehler." Claude Code generiert den vollständigen onMutate/onError/onSettled-Zyklus inklusive TypeScript-Typisierung.

Infinite Queries: Pagination und Infinite Scroll

InfinitePagination und Infinite Scroll

# Prompt: "useInfiniteQuery für paginierten Feed mit 'Mehr laden'-Button" export function useInfinitePosts(filter: string) { return useInfiniteQuery({ queryKey: ['posts', filter], queryFn: ({ pageParam }) => fetch(`/api/posts?cursor=${pageParam}&filter=${filter}`) .then(r => r.json()), initialPageParam: undefined as string | undefined, getNextPageParam: (lastPage) => lastPage.nextCursor, // undefined = kein weiterer Seite (hasNextPage = false) select: (data) => ({ pages: data.pages, posts: data.pages.flatMap(p => p.items) // Flat array }) }) } // Feed-Komponente function PostFeed() { const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfinitePosts('recent') // Intersection Observer für Auto-Load const { ref } = useIntersectionObserver({ onChange: (inView) => { if (inView && hasNextPage) fetchNextPage() } }) return ( <div> {data?.posts.map(post => <PostCard key={post.id} post={post} />)} <div ref={ref}> {isFetchingNextPage && <Spinner />} </div> </div> ) }

Prefetching und Hydration (Next.js)

# Prompt: "TanStack Query mit Next.js App Router — SSR + Client Hydration" // app/users/[id]/page.tsx — Server Component import { HydrationBoundary, QueryClient, dehydrate } from '@tanstack/react-query' export default async function UserPage({ params }: { params: { id: string } }) { const queryClient = new QueryClient() // Auf Server prefetchen await queryClient.prefetchQuery({ queryKey: userKeys.detail(params.id), queryFn: () => fetchUser(params.id) }) return ( <HydrationBoundary state={dehydrate(queryClient)}> <UserProfile userId={params.id} /> </HydrationBoundary> ) } // → Client bekommt bereits befüllten Cache — kein Ladezustand sichtbar! # Dependent Queries (sequenziell): const { data: user } = useUser(userId) const { data: orders } = useQuery({ queryKey: ['orders', user?.id], queryFn: () => fetchOrders(user!.id), enabled: Boolean(user?.id) // Erst wenn User geladen })

Wann TanStack Query, wann Zustand?

EntscheidungServer State vs. Client State

# TanStack Query = SERVER STATE (vom Backend) # ✅ User-Profil, Posts, Produkte, Bestellungen # ✅ Alles was gecacht, synchronisiert, invalidiert werden muss # ✅ Daten die "veralten" können # Zustand = CLIENT STATE (nur Browser) # ✅ UI-State (Modal offen/zu, aktiver Tab) # ✅ Formular-Draft (nicht gespeichert) # ✅ User-Preferences (dark mode, Sprache) # FALSCH: Server-Daten in Zustand speichern # → Manuelles Cache-Management, veraltete Daten, Inkonsistenz # RICHTIG: TanStack Query + Zustand kombinieren # → TanStack Query = Backend-Daten # → Zustand = UI-State + lokale User-Preferences # Claude Code Prompt: # "Analysiere diesen Code: wo wird Server State in useState gehalten? # Migriere zu TanStack Query."
Häufiger Fehler: Server-Daten in useState oder Zustand speichern und manuell synchronisieren. TanStack Query löst das automatisch — kein manuelles Loading-State, kein manuelles Re-fetch, kein "Stale Data"-Problem.

Data-Fetching-Modul im Kurs

Im Claude Code Mastery Kurs: vollständiges TanStack Query-Modul mit useQuery, useMutation, Optimistic Updates, Infinite Scroll, SSR-Hydration und Integration mit Next.js App Router — für professionelles Server State Management.

14 Tage kostenlos testen →