TanStack Query Cheat Sheet
Quick reference for TanStack Query (React Query): queries, mutations, caching, invalidation, and optimistic updates. Master server state management.
Setup & Basic Queries
| npm install @tanstack/react-query | Install TanStack Query for React |
| <QueryClientProvider client={new QueryClient()}><App /></QueryClientProvider> | Wrap app with QueryClientProvider |
| const { data, isLoading, error } = useQuery({ queryKey: ["todos"], queryFn: fetchTodos }) | Basic query with key and fetch function |
| const { data } = useQuery({ queryKey: ["todo", id], queryFn: () => fetchTodo(id) }) | Query with dynamic key (auto-refetches when id changes) |
| const { data } = useQuery({ queryKey: ["todos"], queryFn: fetchTodos, enabled: !!userId }) | Conditional query — only runs when enabled is true |
Query Options
| staleTime: 5 * 60 * 1000 | Data stays fresh for 5 minutes (no refetch) |
| gcTime: 30 * 60 * 1000 | Garbage collect unused cache after 30 minutes (formerly cacheTime) |
| refetchInterval: 10000 | Auto-refetch every 10 seconds (polling) |
| retry: 3, retryDelay: (attempt) => attempt * 1000 | Retry failed queries 3 times with backoff |
| select: (data) => data.filter(t => t.done) | Transform/select a subset of query data |
Mutations
| const mutation = useMutation({ mutationFn: createTodo }) | Define a mutation with a mutation function |
| mutation.mutate({ title: "New" }) | Trigger the mutation with variables |
| mutation.mutateAsync({ title: "New" }).then(data => ...) | Trigger mutation and get a Promise back |
| onSuccess: (data, vars, ctx) => queryClient.invalidateQueries({ queryKey: ["todos"] }) | Invalidate queries on successful mutation |
| onError: (err, vars, ctx) => { /* rollback */ }, onSettled: () => { /* always runs */ } | Handle mutation error and settled callbacks |
Cache Management
| queryClient.invalidateQueries({ queryKey: ["todos"] }) | Mark queries as stale and trigger refetch |
| queryClient.setQueryData(["todo", id], updatedTodo) | Directly update cached data (optimistic update) |
| queryClient.getQueryData(["todo", id]) | Read cached data without triggering a fetch |
| queryClient.prefetchQuery({ queryKey: ["todo", id], queryFn: fetchTodo }) | Prefetch data before it is needed (e.g., on hover) |
| queryClient.cancelQueries({ queryKey: ["todos"] }) | Cancel in-flight queries (useful before optimistic update) |
Advanced Patterns
| useInfiniteQuery({ queryKey: ["items"], queryFn: ({ pageParam }) => fetch(pageParam), getNextPageParam: (last) => last.next }) | Infinite scroll / load more with pagination |
| useSuspenseQuery({ queryKey: ["todos"], queryFn: fetchTodos }) | Query with React Suspense support (throws promise) |
| placeholderData: (prev) => prev | Keep previous data while refetching (keepPreviousData pattern) |
| queryClient.setMutationDefaults(["addTodo"], { mutationFn: createTodo, onMutate: async (todo) => { ... } }) | Set default mutation options for offline-first optimistic updates |
| useIsFetching({ queryKey: ["todos"] }) | Get number of active fetches (useful for global loading indicators) |
Step-by-Step Guide
Read Guide →