⚛ TanStack Query (React Query)
On this page
useQuery Jump to heading
The v5 API uses a single object argument instead of positional arguments.
import { useQuery } from '@tanstack/react-query'
interface User {
id: string
name: string
}
const { data, error, isPending, isError } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
})
With options Jump to heading
const { data, isPending } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
enabled: !!userId, // only run when userId is truthy
staleTime: 1000 * 60 * 5, // 5 minutes
gcTime: 1000 * 60 * 30, // 30 minutes (formerly cacheTime)
})
As a custom hook Jump to heading
import { useQuery } from '@tanstack/react-query'
interface User {
id: string
name: string
email: string
}
export const useUser = (userId: string) => {
return useQuery({
queryKey: ['user', userId],
queryFn: async (): Promise<User> => {
const response = await fetch(`/api/users/${userId}`)
if (!response.ok) throw new Error('Failed to fetch user')
return response.json()
},
enabled: !!userId,
})
}
Usage in a component Jump to heading
const UserProfile = ({ userId }: { userId: string }) => {
const { data: user, isPending, isError, error } = useUser(userId)
if (isPending) return <div>Loading...</div>
if (isError) return <div>Error: {error.message}</div>
return <div>Name: {user.name}</div>
}
useMutation Jump to heading
import { useMutation, useQueryClient } from '@tanstack/react-query'
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: (newUser: { name: string }) => {
return fetch('/api/users', {
method: 'POST',
body: JSON.stringify(newUser),
})
},
onSuccess: () => {
// Invalidate and refetch
queryClient.invalidateQueries({ queryKey: ['users'] })
},
})
// Usage
mutation.mutate({ name: 'New User' })
queryOptions Jump to heading
The queryOptions helper lets you define query configuration in one place and reuse it across useQuery, useSuspenseQuery, useQueries, prefetching, and more. Great for co-locating queryKey and queryFn together.
import { queryOptions, useQuery, useQueryClient } from '@tanstack/react-query'
function userOptions(id: string) {
return queryOptions({
queryKey: ['user', id],
queryFn: () => fetchUser(id),
staleTime: 5 * 1000,
})
}
// Usage in components
const { data } = useQuery(userOptions(userId))
const { data } = useSuspenseQuery(userOptions(userId))
// Prefetching
queryClient.prefetchQuery(userOptions(userId))
// Setting data directly
queryClient.setQueryData(userOptions(userId).queryKey, newUser)
// Multiple queries
useQueries({
queries: [userOptions('1'), userOptions('2')],
})
Override options at the component level:
const { data } = useQuery({
...userOptions(userId),
select: (data) => data.name, // type inference still works
})
For infinite queries, use infiniteQueryOptions.
useQueries Jump to heading
Run multiple queries in parallel.
import { useQueries } from '@tanstack/react-query'
const results = useQueries({
queries: [
{
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
},
{
queryKey: ['posts', userId],
queryFn: () => fetchUserPosts(userId),
},
],
})
useSuspenseQuery Jump to heading
For use with React Suspense. Data is guaranteed to be defined.
import { useSuspenseQuery } from '@tanstack/react-query'
const UserProfile = ({ userId }: { userId: string }) => {
// data is guaranteed to be defined
const { data: user } = useSuspenseQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
})
return <div>Name: {user.name}</div>
}
// Wrap with Suspense
const App = () => (
<Suspense fallback={<div>Loading...</div>}>
<UserProfile userId="123" />
</Suspense>
)
QueryClient setup Jump to heading
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60, // 1 minute
retry: 1,
},
},
})
const App = () => (
<QueryClientProvider client={queryClient}>
<YourApp />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
)
Placeholder and initial data Jump to heading
const { data } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
// Show immediately while fetching
placeholderData: { id: userId, name: 'Loading...' },
// Or use previous data as placeholder
placeholderData: (previousData) => previousData,
})
← Back home