TanStack Query
TanStack Query cheat sheet with useQuery, useMutation, caching strategies, optimistic updates, and server state management examples.
Other React Sheets
Sign in to mark items as known and track your progress.
Sign inSetup & Configuration
Installing and configuring TanStack Query
Installation & Basic Setup
Setting up TanStack Query in your React application
# Installation
npm install @tanstack/react-query
npm install @tanstack/react-query-devtools
// App.tsx - Setup QueryClient & Provider
import {
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
const queryClient = new QueryClient()
function App() {
return (
<QueryClientProvider client={queryClient}>
{/* Your app components */}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
)
}Queries
Fetching and caching server data
Basic Query
Using useQuery for data fetching
import { useQuery } from '@tanstack/react-query'
// Basic query
function Profile() {
const { data, error, isLoading } = useQuery({
queryKey: ['profile'],
queryFn: fetchProfile
})
if (isLoading) return 'Loading...'
if (error) return 'An error occurred: ' + error.message
return <div>{data.name}</div>
}
// Query with parameters
function User({ userId }) {
const { data, error, isLoading } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
enabled: !!userId // Only run if userId exists
})
return <div>{data?.name}</div>
}Dependent Queries
Queries that depend on other queries
// Sequential dependent queries
function UserProjects({ userId }) {
// First query
const userQuery = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId)
})
// Second query depends on first
const projectsQuery = useQuery({
queryKey: ['projects', userQuery.data?.id],
queryFn: () => fetchProjects(userQuery.data.id),
enabled: !!userQuery.data?.id // Only run when user data is available
})
return (
<div>
<h1>{userQuery.data?.name}</h1>
<ul>
{projectsQuery.data?.map(project => (
<li key={project.id}>{project.name}</li>
))}
</ul>
</div>
)
}Parallel Queries
Running multiple queries simultaneously
import { useQueries } from '@tanstack/react-query'
// Multiple parallel queries with useQueries
function Dashboard({ userIds }) {
const userQueries = useQueries({
queries: userIds.map(id => ({
queryKey: ['user', id],
queryFn: () => fetchUser(id),
staleTime: 1000 * 60 * 5
}))
})
const isLoading = userQueries.some(query => query.isLoading)
const isError = userQueries.some(query => query.isError)
if (isLoading) return 'Loading users...'
if (isError) return 'Error loading users'
return (
<div>
{userQueries.map((query, index) => (
<div key={userIds[index]}>
{query.data?.name}
</div>
))}
</div>
)
}Mutations
Creating, updating, and deleting data
Basic Mutations
Using useMutation for data modifications
import { useMutation, useQueryClient } from '@tanstack/react-query'
function CreatePost() {
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: createPost,
onSuccess: () => {
// Invalidate and refetch
queryClient.invalidateQueries({ queryKey: ['posts'] })
},
})
return (
<form
onSubmit={(e) => {
e.preventDefault()
const formData = new FormData(e.target)
mutation.mutate({
title: formData.get('title'),
content: formData.get('content')
})
}}
>
<input name="title" />
<textarea name="content" />
<button type="submit">
{mutation.isPending ? 'Creating...' : 'Create Post'}
</button>
{mutation.isError && (
<div>Error: {mutation.error.message}</div>
)}
</form>
)
}Update & Delete Mutations
Implementing update and delete operations
// Update mutation
function EditPost({ post }) {
const queryClient = useQueryClient()
const updateMutation = useMutation({
mutationFn: ({ id, ...data }) => updatePost(id, data),
onSuccess: (data) => {
// Update specific item in cache
queryClient.setQueryData(['post', data.id], data)
// Invalidate list
queryClient.invalidateQueries({ queryKey: ['posts'] })
}
})
const deleteMutation = useMutation({
mutationFn: deletePost,
onSuccess: (_, postId) => {
// Remove from cache
queryClient.removeQueries({ queryKey: ['post', postId] })
queryClient.invalidateQueries({ queryKey: ['posts'] })
}
})
return (
<div>
<button onClick={() => updateMutation.mutate({
id: post.id,
title: 'Updated Title'
})}>
Update
</button>
<button onClick={() => deleteMutation.mutate(post.id)}>
Delete
</button>
</div>
)
}Infinite Queries
Implementing pagination and infinite scroll
Infinite Scroll
Loading data progressively with useInfiniteQuery
import { useInfiniteQuery } from '@tanstack/react-query'
function InfiniteList() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
isLoading,
isError
} = useInfiniteQuery({
queryKey: ['posts'],
queryFn: ({ pageParam }) => fetchPosts(pageParam),
initialPageParam: 0,
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
})
if (isLoading) return 'Loading...'
if (isError) return 'Error!'
return (
<div>
{data.pages.map((page, i) => (
<div key={i}>
{page.posts.map(post => (
<div key={post.id}>{post.title}</div>
))}
</div>
))}
<button
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
{isFetchingNextPage
? 'Loading more...'
: hasNextPage
? 'Load More'
: 'Nothing more to load'}
</button>
</div>
)
}Cache Management
Managing and manipulating the query cache
Cache Operations
Working with the QueryClient cache
import { useQueryClient } from '@tanstack/react-query'
function CacheControls() {
const queryClient = useQueryClient()
// Invalidate queries
const handleInvalidate = () => {
// Invalidate all queries
queryClient.invalidateQueries()
// Invalidate specific queries
queryClient.invalidateQueries({ queryKey: ['posts'] })
// Invalidate with exact match
queryClient.invalidateQueries({
queryKey: ['post', 1],
exact: true
})
}
// Prefetch data
const handlePrefetch = () => {
queryClient.prefetchQuery({
queryKey: ['post', 2],
queryFn: () => fetchPost(2),
staleTime: 1000 * 60 * 5
})
}
// Direct cache manipulation
const handleCacheUpdate = () => {
// Set data directly
queryClient.setQueryData(['user'], { name: 'John' })
// Get cached data
const cachedUser = queryClient.getQueryData(['user'])
// Remove from cache
queryClient.removeQueries({ queryKey: ['old-data'] })
}
return (
<div>
<button onClick={handleInvalidate}>Invalidate</button>
<button onClick={handlePrefetch}>Prefetch</button>
<button onClick={handleCacheUpdate}>Update Cache</button>
</div>
)
}Advanced Patterns
Advanced TanStack Query patterns and techniques
Suspense & Error Boundaries
Using React Suspense with TanStack Query
import { useSuspenseQuery } from '@tanstack/react-query'
import { Suspense } from 'react'
import { ErrorBoundary } from 'react-error-boundary'
// Using suspense query
function Profile({ userId }) {
// This will suspend
const { data } = useSuspenseQuery({
queryKey: ['profile', userId],
queryFn: () => fetchProfile(userId)
})
// Data is guaranteed to be defined
return <div>{data.name}</div>
}
// App with Suspense and Error Boundary
function App() {
return (
<ErrorBoundary fallback={<div>Something went wrong</div>}>
<Suspense fallback={<div>Loading...</div>}>
<Profile userId={1} />
</Suspense>
</ErrorBoundary>
)
}Custom Hooks & Patterns
Building reusable query hooks and patterns
// Custom hook for user data
function useUser(userId: string) {
return useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
enabled: !!userId,
staleTime: 1000 * 60 * 5,
})
}
// Custom hook with mutations
function useUpdateUser() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: updateUser,
onSuccess: (data) => {
queryClient.setQueryData(['user', data.id], data)
queryClient.invalidateQueries({ queryKey: ['users'] })
}
})
}
// Usage
function Profile({ userId }) {
const { data: user, isLoading } = useUser(userId)
const updateUser = useUpdateUser()
if (isLoading) return 'Loading...'
return (
<div>
<h1>{user.name}</h1>
<button onClick={() => updateUser.mutate({ id: userId, name: 'New Name' })}>
Update
</button>
</div>
)
}