Query result states: isPending, isError, isSuccess
The result object from useQuery contains state indicators. A query can only be in one of the following states at any given moment: isPending (or status === 'pending') means the query has no data yet; isError (or status === 'error') means the query encountered an error; isSuccess (or status === 'success') means the query was successful and data is available.
Query result additional properties
Beyond the primary states, the query result provides additional information: error property is available when the query is in an isError state; data property is available when the query is in an isSuccess state; isFetching is true in any state if the query is fetching at any time, including during background refetching.
Recommended pattern for checking query states
For most queries, it is usually sufficient to check for the isPending state first, then the isError state, then finally assume that the data is available and render the successful state. TypeScript will also narrow the type of data correctly if you've checked for pending and error before accessing it.
fetchStatus property values
The query result includes a fetchStatus property with the following options: fetchStatus === 'fetching' means the query is currently fetching; fetchStatus === 'paused' means the query wanted to fetch but it is paused (related to Network Mode); fetchStatus === 'idle' means the query is not doing anything at the moment.
Difference between status and fetchStatus
The status gives information about the data: do we have any or not? The fetchStatus gives information about the queryFn: is it running or not? Background refetches and stale-while-revalidate logic make all combinations for status and fetchStatus possible. For example, a query in success status will usually be in idle fetchStatus, but it could also be in fetching if a background refetch is happening. A query that mounts with no data will usually be in pending status and fetching fetchStatus, but could also be paused if there is no network connection. A query can be in pending state without actually fetching data.
useQuery basic example
Example showing basic useQuery usage:
```tsx
import { useQuery } from '@tanstack/react-query'
function App() {
const info = useQuery({ queryKey: ['todos'], queryFn: fetchTodoList })
}
```
This shows the minimum required parameters: queryKey as a unique identifier and queryFn as a function that returns a promise.
useQuery with status string checks example
Example showing how to use status string to handle query states:
```tsx
function Todos() {
const { status, data, error } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodoList,
})
if (status === 'pending') {
return <span>Loading...</span>
}
if (status === 'error') {
return <span>Error: {error.message}</span>
}
// also status === 'success', but "else" logic works, too
return (
<ul>
{data.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
)
}
```
When to use mutations instead of queries
If your method modifies data on the server, use Mutations instead of queries.
Prefetch in component example with useQuery
Example of prefetching in a parent component to flatten request waterfalls:
```tsx
function Article({ id }) {
const { data: articleData, isPending } = useQuery({
queryKey: ['article', id],
queryFn: getArticleById,
})
// Prefetch
useQuery({
queryKey: ['article-comments', id],
queryFn: getArticleCommentsById,
notifyOnChangeProps: [],
})
if (isPending) {
return 'Loading article...'
}
return (
<>
<ArticleHeader articleData={articleData} />
<ArticleBody articleData={articleData} />
<Comments id={id} />
</>
)
}
```
prefetchQuery basic usage
prefetchQuery populates the cache with data ahead of time. Use it by calling queryClient.prefetchQuery() with queryKey and queryFn parameters. The results are cached like a normal query.
prefetchQuery uses default staleTime from queryClient
By default, prefetchQuery uses the default staleTime configured for the queryClient to determine whether existing cache data is fresh. You can override this by passing a specific staleTime value to the prefetchQuery call, but this only applies to the prefetch itself, not to subsequent useQuery calls for the same key.
ensureQueryData ignores staleTime
The ensureQueryData function can be used instead of prefetchQuery if you want to ignore staleTime and always return data if it's available in the cache.
prefetchQuery with higher staleTime for server-side prefetching
When prefetching on the server, it's recommended to set a default staleTime higher than 0 for that queryClient to avoid having to pass a specific staleTime to each prefetch call.
prefetchQuery returns Promise<void>
prefetchQuery and prefetchInfiniteQuery return Promise<void> and thus never return query data. If you need query data returned, use fetchQuery or fetchInfiniteQuery instead.
prefetchQuery does not throw errors
The prefetch functions never throw errors because they usually try to fetch again in a useQuery which provides a graceful fallback. If you need to catch errors, use fetchQuery or fetchInfiniteQuery instead.
Prefetch in event handlers
A straightforward form of prefetching is doing it when the user interacts with something. Use queryClient.prefetchQuery in onMouseEnter or onFocus event handlers. Set staleTime to ensure prefetch fires when data is older than the specified time.
Prefetch in event handlers example
Example of prefetching in event handlers:
```tsx
function ShowDetailsButton() {
const queryClient = useQueryClient()
const prefetch = () => {
queryClient.prefetchQuery({
queryKey: ['details'],
queryFn: getDetailsData,
staleTime: 60000,
})
}
return (
<button onMouseEnter={prefetch} onFocus={prefetch} onClick={...}>
Show Details
</button>
)
}
```
Prefetch in components to avoid request waterfall
Use prefetching during component lifecycle when you know a child or descendant will need data but can't render until another query finishes. This can be done using a useQuery call and ignoring the result, with notifyOnChangeProps: [] as an optional optimization to avoid rerenders.
usePrefetchQuery hook for prefetching with Suspense
When prefetching together with Suspense, you cannot use useSuspenseQueries to prefetch (it would block rendering) or useQuery for prefetch (it wouldn't start until after suspenseful query resolved). Instead, use usePrefetchQuery or usePrefetchInfiniteQuery hooks.
Prefetch with Suspense using usePrefetchQuery
Example of prefetching with Suspense using usePrefetchQuery hook:
```tsx
function ArticleLayout({ id }) {
usePrefetchQuery({
queryKey: ['article-comments', id],
queryFn: getArticleCommentsById,
})
return (
<Suspense fallback="Loading article">
<Article id={id} />
</Suspense>
)
}
function Article({ id }) {
const { data: articleData, isPending } = useSuspenseQuery({
queryKey: ['article', id],
queryFn: getArticleById,
})
...
}
```
Prefetch inside query function
Prefetch can be done inside the query function using queryClient.prefetchQuery(). This makes sense when you know that every time a query is fetched, another query will also likely be needed.
Prefetch inside query function example
Example of prefetching inside a query function:
```tsx
const queryClient = useQueryClient()
const { data: articleData, isPending } = useQuery({
queryKey: ['article', id],
queryFn: (...args) => {
queryClient.prefetchQuery({
queryKey: ['article-comments', id],
queryFn: getArticleCommentsById,
})
return getArticleById(...args)
},
})
```
Prefetch in effect
Prefetching in an effect works, but note that if you are using useSuspenseQuery in the same component, the effect won't run until after the query finishes, which might not be what you want.
Prefetch in effect example
Example of prefetching in a useEffect:
```tsx
const queryClient = useQueryClient()
useEffect(() => {
queryClient.prefetchQuery({
queryKey: ['article-comments', id],
queryFn: getArticleCommentsById,
})
}, [queryClient, id])
```
Conditional prefetching based on other query results
Prefetch conditionally based on the result of another fetch by doing prefetching inside the query function. This allows you to inspect the fetched data and decide what else needs to be prefetched.
Conditional prefetching example with code splitting
Example of conditional prefetching based on feed item type:
```tsx
function Feed() {
const queryClient = useQueryClient()
const { data, isPending } = useQuery({
queryKey: ['feed'],
queryFn: async (...args) => {
const feed = await getFeed(...args)
for (const feedItem of feed) {
if (feedItem.type === 'GRAPH') {
queryClient.prefetchQuery({
queryKey: ['graph', feedItem.id],
queryFn: getGraphDataById,
})
}
}
return feed
}
})
...
}
```
Router integration for prefetching
Integrate prefetching at the router level to avoid request waterfalls. Explicitly declare for each route what data is needed, ahead of time. This allows you to either block rendering until all data is present, or start a prefetch without awaiting the result, or mix both approaches.
TanStack Router integration example with prefetching
Example of integrating TanStack Router with prefetching:
```tsx
const queryClient = new QueryClient()
const routerContext = new RouterContext()
const rootRoute = routerContext.createRootRoute({
component: () => { ... }
})
const articleRoute = new Route({
getParentRoute: () => rootRoute,
path: 'article',
beforeLoad: () => {
return {
articleQueryOptions: { queryKey: ['article'], queryFn: fetchArticle },
commentsQueryOptions: { queryKey: ['comments'], queryFn: fetchComments },
}
},
loader: async ({
context: { queryClient },
routeContext: { articleQueryOptions, commentsQueryOptions },
}) => {
// Fetch comments asap, but don't block
queryClient.prefetchQuery(commentsQueryOptions)
// Don't render the route at all until article has been fetched
await queryClient.prefetchQuery(articleQueryOptions)
},
component: ({ useRouteContext }) => {
const { articleQueryOptions, commentsQueryOptions } = useRouteContext()
const articleQuery = useQuery(articleQueryOptions)
const commentsQuery = useQuery(commentsQueryOptions)
return (
...
)
},
errorComponent: () => 'Oh crap!',
})
```
Manually prime a query with setQueryData
If you already have data for a query synchronously available, use the Query Client's setQueryData method to directly add or update a query's cached result by key instead of prefetching.
setQueryData example
Example of manually priming a query with setQueryData:
```tsx
queryClient.setQueryData(['todos'], todos)
```
Prefetching patterns overview
There are four main prefetching patterns: in event handlers, in components, via router integration, and during server rendering. Server rendering is covered separately in the Server Rendering & Hydration guide.
Prefetching avoids request waterfalls
One specific use of prefetching is to avoid Request Waterfalls. See the Performance & Request Waterfalls guide for in-depth background and explanation.
prefetchQuery garbage collection
If no instances of useQuery appear for a prefetched query, it will be deleted and garbage collected after the time specified in gcTime.
Using AbortSignal with axios versions before v0.22.0
For axios versions lower than v0.22.0, create a CancelToken source and pass its token to the request. Listen to the abort event on the signal and call source.cancel() when abort is triggered.
Example: axios v0.22.0+ with query cancellation
import axios from 'axios'
const query = useQuery({
queryKey: ['todos'],
queryFn: ({ signal }) =>
axios.get('/todos', {
// Pass the signal to `axios`
signal,
}),
})
Example: cancel options with silent suppression
await queryClient.cancelQueries({ queryKey: ['posts'] }, { silent: true })
Query cancellation does not work with Suspense hooks
Cancellation does not work when working with Suspense hooks: useSuspenseQuery, useSuspenseQueries, and useSuspenseInfiniteQuery.
Cancel options for queryClient.cancelQueries
Cancel options control the behavior of query cancellation operations. The silent option (boolean, defaults to false) suppresses propagation of CancelledError to observers and related notifications when true. The revert option (boolean, defaults to true) restores the query's state from immediately before the in-flight fetch, sets fetchStatus back to idle, and only throws if there was no prior data when true.
Example: manual query cancellation with cancel button
const query = useQuery({
queryKey: ['todos'],
queryFn: async ({ signal }) => {
const resp = await fetch('/todos', { signal })
return resp.json()
},
})
const queryClient = useQueryClient()
return (
<button
onClick={(e) => {
e.preventDefault()
queryClient.cancelQueries({ queryKey: ['todos'] })
}}
>
Cancel
</button>
)
Manual query cancellation with queryClient.cancelQueries
To manually cancel a query, call queryClient.cancelQueries({ queryKey }). This will cancel the query and revert it back to its previous state. If you have consumed the signal passed to the query function, TanStack Query will additionally also cancel the Promise.
Example: XMLHttpRequest with query cancellation
const query = useQuery({
queryKey: ['todos'],
queryFn: ({ signal }) => {
return new Promise((resolve, reject) => {
var oReq = new XMLHttpRequest()
oReq.addEventListener('load', () => {
resolve(JSON.parse(oReq.responseText))
})
signal?.addEventListener('abort', () => {
oReq.abort()
reject()
})
oReq.open('GET', '/todos')
oReq.send()
})
},
})
Using AbortSignal with XMLHttpRequest
To use cancellation with XMLHttpRequest, create a Promise that handles the XMLHttpRequest. Listen to the abort event on the signal and call oReq.abort() when the abort event fires.
AbortController API runtime environment support
The AbortController API is available in most runtime environments. If your runtime environment does not support it, you will need to provide a polyfill. Several polyfill options are available on npm.
AbortSignal passed to query function for cancellation
TanStack Query provides each query function with an AbortSignal instance. When a query becomes out-of-date or inactive, this signal will become aborted. All queries are cancellable, and you can respond to the cancellation inside your query function.
Default behavior when query unmounts before promise resolves
By default, queries that unmount or become unused before their promises are resolved are not cancelled. After the promise resolves, the resulting data will be available in the cache. This is helpful if a component unmounts before a query finishes, and if you mount the component again and the query has not been garbage collected yet, data will be available. However, if you consume the AbortSignal, the Promise will be cancelled and the Query must be cancelled, reverting its state to its previous state.
Using AbortSignal with fetch API
To use cancellation with fetch, pass the signal parameter from the query function to the fetch call's signal option. You can pass it to one fetch call or several fetch calls.
Example: fetch with query cancellation
const query = useQuery({
queryKey: ['todos'],
queryFn: async ({ signal }) => {
const todosResponse = await fetch('/todos', {
// Pass the signal to one fetch
signal,
})
const todos = await todosResponse.json()
const todoDetails = todos.map(async ({ details }) => {
const response = await fetch(details, {
// Or pass it to several
signal,
})
return response.json()
})
return Promise.all(todoDetails)
},
})
Using AbortSignal with axios v0.22.0 or later
For axios v0.22.0 and later, pass the signal parameter from the query function directly to the signal option in the axios.get call.
Example: axios < v0.22.0 with query cancellation
import axios from 'axios'
const query = useQuery({
queryKey: ['todos'],
queryFn: ({ signal }) => {
// Create a new CancelToken source for this request
const CancelToken = axios.CancelToken
const source = CancelToken.source()
const promise = axios.get('/todos', {
// Pass the source token to your request
cancelToken: source.token,
})
// Cancel the request if TanStack Query signals to abort
signal?.addEventListener('abort', () => {
source.cancel('Query was cancelled by TanStack Query')
})
return promise
},
})
Valid query function configurations
The following are valid query function configurations:
```tsx
useQuery({ queryKey: ['todos'], queryFn: fetchAllTodos })
useQuery({ queryKey: ['todos', todoId], queryFn: () => fetchTodoById(todoId) })
useQuery({
queryKey: ['todos', todoId],
queryFn: async () => {
const data = await fetchTodoById(todoId)
return data
},
})
useQuery({
queryKey: ['todos', todoId],
queryFn: ({ queryKey }) => fetchTodoById(queryKey[1]),
})
```
Query function must return a promise
A query function can be any function that returns a promise. The promise should either resolve the data or throw an error.
Query resolved value cannot be undefined
On success, the resolved value may be anything except undefined. Queries that resolve to undefined will be treated as failed. To store "nothing" as a successful result in the query cache, resolve null instead.
Query error handling must throw or reject
For TanStack Query to determine a query has errored, the query function must throw or return a rejected Promise. Any error that is thrown in the query function will be persisted on the error state of the query.
Throwing errors in query functions
Errors can be thrown directly or by returning a rejected Promise. Example:
```tsx
const { error } = useQuery({
queryKey: ['todos', todoId],
queryFn: async () => {
if (somethingGoesWrong) {
throw new Error('Oh no!')
}
if (somethingElseGoesWrong) {
return Promise.reject(new Error('Oh no!'))
}
return data
},
})
```
Fetch API error handling in query functions
Unlike axios or graphql-request, the fetch API does not throw errors by default for unsuccessful HTTP calls. You must manually check the response and throw errors. Example:
```tsx
useQuery({
queryKey: ['todos', todoId],
queryFn: async () => {
const response = await fetch('/todos/' + todoId)
if (!response.ok) {
throw new Error('Network response was not ok')
}
return response.json()
},
})
```
Query keys passed to query function via QueryFunctionContext
Query keys are passed into your query function as part of the QueryFunctionContext. This makes it possible to extract your query functions if needed. Example:
```tsx
function Todos({ status, page }) {
const result = useQuery({
queryKey: ['todos', { status, page }],
queryFn: fetchTodoList,
})
}
// Access the key, status and page variables in your query function!
function fetchTodoList({ queryKey }) {
const [_key, { status, page }] = queryKey
return new Promise()
}
```
QueryFunctionContext properties
The QueryFunctionContext is the object passed to each query function. It consists of:
- queryKey: QueryKey - the query keys used to identify the query
- client: QueryClient - the QueryClient instance
- signal?: AbortSignal - an AbortSignal instance provided by TanStack Query, can be used for Query Cancellation
- meta: Record<string, unknown> | undefined - an optional field you can fill with additional information about your query
Additionally, Infinite Queries get the following options passed:
- pageParam: TPageParam - the page parameter used to fetch the current page
- direction: 'forward' | 'backward' - deprecated, the direction of the current page fetch. To get access to the direction of the current page fetch, add a direction to pageParam from getNextPageParam and getPreviousPageParam.
queryOptions helper for sharing query configuration
The queryOptions helper allows you to define query configuration in one place, co-locating queryKey and queryFn. At runtime it returns whatever is passed into it. It provides advantages when using TypeScript, enabling type inference and type safety for all query options.
queryOptions with select override example
Example of overriding queryOptions with a select function: const query = useQuery({ ...groupOptions(1), select: (data) => data.groupName }); The data type is inferred from the select return type, not the original queryFn.
Override queryOptions at component level with select
You can override options from queryOptions at the component level using the select function. A common pattern is to create per-component select functions that transform query data. Type inference still works, so query.data will be the return type of select instead of queryFn.