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 →