isFetching property to show background refetch indicator
The isFetching boolean property on a useQuery result indicates whether a query is currently fetching, regardless of the status state. This can be used to display a separate refetching indicator while preserving the display of the main data. For example, isFetching is true during background refetches but the component can still display the cached data and status remains 'success'.
useIsFetching hook for global loading indicator
The useIsFetching() hook from @tanstack/react-query returns a number representing how many queries are currently fetching in the background. When any queries are fetching, the value is truthy and can be used to display a global loading indicator that shows across the entire application.
Example showing global background fetching indicator with useIsFetching
import { useIsFetching } from '@tanstack/react-query'; function GlobalLoadingIndicator() { const isFetching = useIsFetching(); return isFetching ? <div>Queries are fetching in the background...</div> : null; }
Example showing isFetching for background refetch indicator
const { status, data: todos, error, isFetching } = useQuery({ queryKey: ['todos'], queryFn: fetchTodos }); return status === 'pending' ? <span>Loading...</span> : status === 'error' ? <span>Error: {error.message}</span> : <>{isFetching ? <div>Refreshing...</div> : null}<div>{todos.map((todo) => <Todo todo={todo} />)}</div></>;
First query instance with new key triggers hard loading state
When a useQuery instance mounts with a query key that has no cached data, it will show a hard loading state and make a network request to fetch the data.
Multiple instances with same query key share status updates
When multiple useQuery instances have the same query key, all instances share the same status values (status, isFetching, isPending, and related values). When one instance fetches data, all instances with that query key are updated with the same status and data.
Default query function example with axios
Example showing how to set up a default query function: Create an async function that receives {queryKey} and uses queryKey[0] to construct an API URL, then pass it to QueryClient with defaultOptions: { queries: { queryFn: defaultQueryFn } }. This example uses axios.get to fetch from https://jsonplaceholder.typicode.com${queryKey[0]}.
Default query function configuration
You can define a default query function for your entire app by providing it to the QueryClient via defaultOptions.queries.queryFn. The default query function receives an object with queryKey as a parameter, allowing you to use the query key to determine what to fetch. This allows you to omit the queryFn parameter when calling useQuery and instead just provide a queryKey.
Overriding default query function
You can override the default query function for any specific query by providing your own queryFn parameter to useQuery, just as you normally would.
Using default query function with useQuery
When a default query function is configured, you can call useQuery with only a queryKey parameter: useQuery({ queryKey: ['/posts'] }). You can also include other options like enabled: !!postId without needing to specify queryFn.
Optimize dependent queries by restructuring backend APIs
To avoid performance issues from dependent queries, restructure backend APIs to allow queries to be fetched in parallel when possible. For example, instead of first fetching `getUserByEmail` to then fetch `getProjectsByUser`, introduce a combined `getProjectsByUserEmail` query that flattens the request waterfall.
useQueries dependent queries example
Example showing how to fetch user IDs first, then use those IDs to fetch messages for each user in parallel:
```tsx
const { data: userIds } = useQuery({
queryKey: ['users'],
queryFn: getUsersData,
select: (users) => users.map((user) => user.id),
})
const usersMessages = useQueries({
queries: userIds
? userIds.map((id) => {
return {
queryKey: ['messages', id],
queryFn: () => getMessagesByUsers(id),
}
})
: [],
})
```
Dependent queries create request waterfalls
Dependent queries by definition create request waterfalls, which harm performance. If two queries take the same amount of time, executing them serially instead of in parallel takes twice as long. This is especially harmful on high-latency clients.
useQueries dependent queries
The `useQueries` hook can depend on a previous query by conditionally mapping over query results. If the dependency data is not yet available, pass an empty array to the queries array. The `useQueries` hook returns an array of query results.
useQuery dependent queries example
Example showing how to fetch user data first, then use the userId to fetch projects:
```tsx
const { data: user } = useQuery({
queryKey: ['user', email],
queryFn: getUserByEmail,
})
const userId = user?.id
const {
status,
fetchStatus,
data: projects,
} = useQuery({
queryKey: ['projects', userId],
queryFn: getProjectsByUser,
enabled: !!userId,
})
```
Dependent queries with useQuery
Dependent queries rely on previous queries finishing before they can execute. Use the `enabled` option to control when a query runs. When `enabled` is false (or falsy), the query will not execute. The query will start with status 'pending', isPending true, and fetchStatus 'idle'. Once the dependency is available and enabled becomes true, it transitions to status 'pending', isPending true, fetchStatus 'fetching'. After the fetch completes, it reaches status 'success', isPending false, fetchStatus 'idle'.
skipToken refetch limitation
The refetch function from useQuery will not work with skipToken. Calling refetch() on a query that uses skipToken will result in a Missing queryFn error because there is no valid query function to execute. If manual query triggering is needed, use enabled: false instead.
isLoading flag for lazy and disabled queries
The isLoading flag is a derived flag computed from isPending && isFetching and will only be true if the query is currently fetching for the first time. It is useful for disabled or lazy queries to show a loading spinner, since the status flag alone would not indicate whether data is being fetched.
skipToken for type-safe query disabling
TypeScript users can use skipToken to disable a query in a type-safe way by passing it to queryFn instead of using enabled: false. For example: queryFn: filter ? () => fetchTodos(filter) : skipToken.
Query behavior when enabled is false without cached data
If enabled is false and the query does not have cached data, the query will start in status === 'pending' and fetchStatus === 'idle' state.
Query behavior when enabled is false with cached data
If enabled is false and the query has cached data, the query will be initialized in status === 'success' or isSuccess state.
enabled option disables automatic query execution
The enabled option can be set to false to disable a query from automatically running. The enabled option also accepts a callback that returns a boolean.
enabled false prevents automatic fetch and refetch
When enabled is false, the query will not automatically fetch on mount, will not automatically refetch in the background, and will ignore query client invalidateQueries and refetchQueries calls.
Lazy queries using enabled option
The enabled option can be used to defer the initial fetch of a query until a condition is met. A common use case is a filter form where the query should only execute once the user has entered a filter value, using enabled: !!filter.
Manual refetch possible with enabled false but not with skipToken
The refetch function returned from useQuery can be used to manually trigger a query to fetch when enabled is false. However, refetch will not work with skipToken.
Example: disabled query with manual refetch
Example showing a disabled query (enabled: false) with a manual refetch button:
```tsx
function Todos() {
const { isLoading, isError, data, error, refetch, isFetching } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodoList,
enabled: false,
})
return (
<div>
<button onClick={() => refetch()}>Fetch Todos</button>
{data ? (
<ul>
{data.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
) : isError ? (
<span>Error: {error.message}</span>
) : isLoading ? (
<span>Loading...</span>
) : (
<span>Not ready ...</span>
)}
<div>{isFetching ? 'Fetching...' : null}</div>
</div>
)
}
```
Example: lazy query with skipToken
Example showing a lazy query using skipToken for type-safe disabling:
```tsx
import { skipToken, useQuery } from '@tanstack/react-query'
function Todos() {
const [filter, setFilter] = React.useState<string | undefined>()
const { data } = useQuery({
queryKey: ['todos', filter],
// ⬇️ disabled as long as the filter is undefined or empty
queryFn: filter ? () => fetchTodos(filter) : skipToken,
})
return (
<div>
// 🚀 applying the filter will enable and execute the query
<FiltersForm onApply={setFilter} />
{data && <TodosTable data={data} />}
</div>
)
}
```
Example: lazy query with filter form
Example showing a lazy query that only executes after a filter value is entered:
```tsx
function Todos() {
const [filter, setFilter] = React.useState('')
const { data } = useQuery({
queryKey: ['todos', filter],
queryFn: () => fetchTodos(filter),
// ⬇️ disabled as long as the filter is empty
enabled: !!filter,
})
return (
<div>
// 🚀 applying the filter will enable and execute the query
<FiltersForm onApply={setFilter} />
{data && <TodosTable data={data} />}
</div>
)
}
```
QueryFilters and MutationFilters objects
TanStack Query methods accept QueryFilters or MutationFilters objects to filter queries and mutations.
Query filter object properties
A query filter object supports the following properties:
- queryKey (optional, QueryKey): defines a query key to match on.
- exact (optional, boolean): when true, returns only queries with the exact query key provided; defaults to false for inclusive search.
- type (optional, 'active' | 'inactive' | 'all'): defaults to 'all'; when 'active' matches active queries, when 'inactive' matches inactive queries.
- stale (optional, boolean): when true matches stale queries, when false matches fresh queries.
- fetchStatus (optional, FetchStatus): when 'fetching' matches queries currently fetching, when 'paused' matches queries that wanted to fetch but are paused, when 'idle' matches queries not fetching.
- predicate (optional, function): takes a Query and returns boolean; used as final filter on all matching queries.
matchQuery utility function
The matchQuery utility function returns a boolean indicating whether a query matches the provided set of query filters. Usage: `const isMatching = matchQuery(filters, query)`
QueryFilters usage examples
Example usage of query filters:
```tsx
// Cancel all queries
await queryClient.cancelQueries()
// Remove all inactive queries that begin with `posts` in the key
queryClient.removeQueries({ queryKey: ['posts'], type: 'inactive' })
// Refetch all active queries
await queryClient.refetchQueries({ type: 'active' })
// Refetch all active queries that begin with `posts` in the key
await queryClient.refetchQueries({ queryKey: ['posts'], type: 'active' })
```
Default staleTime behavior
Query instances via useQuery or useInfiniteQuery by default consider cached data as stale immediately. This means queries will refetch their data frequently by default. To change this behavior, configure the staleTime option globally or per-query.
Default retry behavior for failed queries
Queries that fail are silently retried 3 times with exponential backoff delay before capturing and displaying an error to the UI. This can be changed by altering the default retry and retryDelay options.
When stale queries are automatically refetched
Stale queries are automatically refetched in the background when new instances of the query mount, the window is refocused, or the network is reconnected. Setting staleTime is the recommended way to avoid excessive refetches, though you can also customize refetch points with options like refetchOnMount, refetchOnWindowFocus, and refetchOnReconnect.
Difference between initial data and placeholder data
initialData is persisted to the cache and should contain complete, accurate data. placeholderData is recommended for incomplete or partial data that you don't want persisted to the cache. For a comparison, see the article by TkDodo on placeholder and initial data in React Query.
Initial data from cache with initialDataUpdatedAt example
const result = useQuery({
queryKey: ['todos', todoId],
queryFn: () => fetch(`/todos/${todoId}`),
initialData: () =>
queryClient.getQueryData(['todos'])?.find((d) => d.id === todoId),
initialDataUpdatedAt: () =>
queryClient.getQueryState(['todos'])?.dataUpdatedAt,
})
Conditional initial data from cache based on freshness example
const result = useQuery({
queryKey: ['todo', todoId],
queryFn: () => fetch(`/todos/${todoId}`),
initialData: () => {
const state = queryClient.getQueryState(['todos'])
if (state && Date.now() - state.dataUpdatedAt <= 10 * 1000) {
return state.data.find((d) => d.id === todoId)
}
},
})
initialData with staleTime and initialDataUpdatedAt example
const result = useQuery({
queryKey: ['todos'],
queryFn: () => fetch('/todos'),
initialData: initialTodos,
staleTime: 60 * 1000,
initialDataUpdatedAt: initialTodosUpdatedTimestamp,
})
Initial data from another query's cache example
const result = useQuery({
queryKey: ['todo', todoId],
queryFn: () => fetch('/todos'),
initialData: () => {
return queryClient.getQueryData(['todos'])?.find((d) => d.id === todoId)
},
})
initialData as function example
const result = useQuery({
queryKey: ['todos'],
queryFn: () => fetch('/todos'),
initialData: () => getExpensiveTodos(),
})
Conditionally use cached data based on freshness with queryClient.getQueryState
Use queryClient.getQueryState to get information about the source query before deciding whether to use its cached data as initial data. Check the state.dataUpdatedAt timestamp to determine if the data is fresh enough. If the source query data is too old, return undefined to let the query fetch from the server with a hard loading state.
Use source query's dataUpdatedAt when getting initial data from cache
When getting initial data from another query's cache, pass the source query's dataUpdatedAt to initialDataUpdatedAt. This provides the query with the information it needs to determine if and when it needs to be refetched, rather than using an artificial staleTime.
Get initial data from another query's cache using queryClient.getQueryData
You can provide initial data for a query from the cached result of another query. For example, you can search the cached data from a todos list query for an individual todo item, then use that as the initial data for your individual todo query by calling queryClient.getQueryData(['todos']) within the initialData function.
initialData as function executes once on query initialization
You can pass a function as the initialData value. This function will be executed only once when the query is initialized, saving memory and CPU compared to executing on every render.
initialDataUpdatedAt option specifies when initialData was last updated
The initialDataUpdatedAt option allows you to pass a numeric JavaScript timestamp in milliseconds indicating when the initialData was last updated (e.g., what Date.now() provides). If you have a unix timestamp, convert it to a JS timestamp by multiplying by 1000. This allows the staleTime to work properly by letting the query decide whether to refetch based on how old the initialData actually is.
initialData with staleTime delays refetch until staleness
When you configure a query observer with initialData and a staleTime value (e.g., 1000 ms), the data will be considered fresh for that duration as if just fetched from the query function. The query will not refetch until the staleTime has passed.
initialData treated as fresh by default with staleTime: 0
By default, initialData is treated as totally fresh, as if it were just fetched. With the default staleTime of 0, the query will immediately refetch when it mounts, even though initialData is shown first.
initialData is persisted to cache, use placeholderData for incomplete data
initialData is persisted to the cache, so it is not recommended to provide placeholder, partial, or incomplete data to this option. Use placeholderData instead for such cases.
initialData option prepopulates query cache and skips loading state
The config.initialData option sets the initial data for a query and allows you to skip the initial loading state. By providing initialData, the query will display data immediately without going through a loading phase.
initialData example with basic usage
const result = useQuery({
queryKey: ['todos'],
queryFn: () => fetch('/todos'),
initialData: initialTodos,
})
Four ways to supply initial data for a query
There are four main ways to supply initial data for a query to the cache before it is needed: (1) Provide initialData to a query to prepopulate its cache if empty, (2) Prefetch the data using queryClient.prefetchQuery, (3) Manually place data into the cache using queryClient.setQueryData, or (4) use initialData as a function to delay execution until query initialization.
QueryCache.findAll() replaces QueryCache.getQueries()
In React Query v3, QueryCache.getQueries() has been moved to QueryCache.findAll(). Use QueryCache.findAll() to look up multiple queries from a cache.
QueryClient.prefetchQuery() is async and does not return data
In React Query v3, QueryCache.prefetchQuery() has been moved to QueryClient.prefetchQuery(). The new function is async but does not return the data from the query. To get data, use QueryClient.fetchQuery() instead. Example: await queryClient.prefetchQuery('posts', fetchPosts) for prefetch, or const data = await queryClient.fetchQuery('posts', fetchPosts) to fetch with data.
No default QueryCache in React Query v3
React Query v3 no longer creates or exports a default QueryCache from the main package. You must create your own via new QueryClient() or new QueryCache() which can be passed to new QueryClient({ queryCache }).
QueryClientProvider replaces ReactQueryConfigProvider and ReactQueryCacheProvider
In React Query v3, the QueryClientProvider component connects a QueryClient to your application. Default options for queries and mutations are specified in QueryClient using defaultOptions property, not defaultConfig. The QueryClientProvider wraps the application: <QueryClientProvider client={queryClient}>...</QueryClientProvider>
QueryClient and separate caches in React Query v3
In React Query v3, the QueryCache has been split into a QueryClient and lower-level QueryCache and MutationCache instances. The QueryCache contains all queries, the MutationCache contains all mutations, and the QueryClient is used to set configuration and interact with them. When creating a new QueryClient(), a QueryCache and MutationCache are automatically created if not supplied.
QueryErrorResetBoundary replaces ReactQueryErrorResetBoundary
In React Query v3, ReactQueryErrorResetBoundary and QueryCache.resetErrorBoundaries() have been replaced by QueryErrorResetBoundary and useQueryErrorResetBoundary() hook. These provide the same experience but with added control to choose which component trees to reset.
QueryClient.isFetching() is now a function
In React Query v3, QueryCache.isFetching has been moved to QueryClient.isFetching(). Notice that it is now a function instead of a property.
useQueryClient hook replaces useQueryCache
In React Query v3, the useQueryCache hook has been replaced by the useQueryClient hook. It returns the provided queryClient for its component tree and generally only requires a rename.