Tools for managing “global state” are plentiful these days, but most of these tools:
React Query exports a set of hooks that address these issues. Out of the box, React Query:
Zeit’s SWR is a great library, and is very similar is spirit and implementation to React Query with a few notable differences:
todos
in its key, regardless of variables, or you can target specific queries with (or without) variables, and even use functional filtering to select queries in most places. This architecture is much more robust and forgiving especially for larger apps.<link rel='preload'>
and/or manually fetching and updating the query cacheusePaginatedQuery
useInfiniteQuery
useQuery
usePaginatedQuery
useInfiniteQuery
useMutation
queryCache
queryCache.prefetchQuery
queryCache.getQueryData
queryCache.setQueryData
queryCache.refetchQueries
queryCache.removeQueries
queryCache.getQuery
queryCache.isFetching
queryCache.subscribe
queryCache.clear
useIsFetching
ReactQueryConfigProvider
setConsole
source-shell
$ npm i --save react-query
# or
$ yarn add react-query
To make a new query, call the useQuery
hook with at least:
const info = useQuery('todos', fetchTodoList)
The unique key you provide is used internally for refetching, caching, deduping related queries.
This key can be whatever you’d like it to be as long as:
The query info
returned contains all information about the query and can be easily destructured and used in your component:
function Todos() {
const { status, data, error } = useQuery('todos', fetchTodoList)
return (
<div>
{status === 'loading' ? (
<span>Loading...</span>
) : status === 'error' ? (
<span>Error: {error.message}</span>
) : (
// also status === 'success', but "else" logic works, too
<ul>
{data.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
)}
</div>
)
}
At its core, React Query manages query caching for you and uses a serializable array or “query key” to do this. Using a query key that is simple and unique to the query’s data is very important. In other similar libraries you’ll see the use of URL’s and/or GraphQL query template strings to achieve this, but we believe at scale, this becomes prone to typos and errors. To relieve this issue, React Query Keys can be strings or an array with a string and then any number of serializable primitives and/or objects.
The simplest form of a key is actually not an array, but an individual string. When a string query key is passed, it is converted to an array internally with the string as the only item in the query key. This format is useful for:
// A list of todos
useQuery('todos', ...) // queryKey === ['todos']
// Something else, whatever!
useQuery('somethingSpecial', ...) // queryKey === ['somethingSpecial']
When a query needs more information to uniquely describe its data, you can use an array with a string and any number of serializable objects to describe it. This is useful for:
// A list of todos that are "done"
useQuery(['todos', { status: 'done' }], ...) // queryKey === ['todos', { status: 'done' }]
// An individual todo
useQuery(['todos', 5], ...) // queryKey === ['todos', 5]
// And individual todo in a "preview" format
useQuery(['todos', 5, { preview: true }], ...) // queryKey === ['todos', 5, { preview: 'true' } }]
This means that no matter the order of keys in objects, all of the following queries would result in the same final query key of ['todos', { page, status }]
:
useQuery(['todos', { status, page }], ...)
useQuery(['todos', { page, status }], ...)
useQuery(['todos', { page, status, other: undefined }], ...)
The following query keys, however, are not equal. Array item order matters!
useQuery(['todos', status, page], ...)
useQuery(['todos', page, status], ...)
useQuery(['todos', undefined, page, status], ...)
To use external props, state, or variables in a query function, it’s easiest to pass them as an items in your array query keys! All query keys get passed through to your query function as parameters in the order they appear in the array key:
function Todos({ completed }) {
const { status, data, error } = useQuery(
['todos', { completed, page }],
fetchTodoList
)
}
// Access the key, status and page variables in your query function!
function fetchTodoList(key, { status, page }) {
return new Promise()
// ...
}
If you send through more items in your query key, they will also be available in your query function:
function Todo({ todoId, preview }) {
const { status, data, error } = useQuery(
['todo', todoId, { preview }],
fetchTodoById
)
}
// Access status and page in your query function!
function fetchTodoById(key, todoId, { preview }) {
return new Promise()
// ...
}
Whenever a query’s key changes, the query will automatically update. In the following example, a new query is created whenever todoId
changes:
function Todo({ todoId }) {
const { status, data, error } = useQuery(['todo', todoId], fetchTodo)
}
In some scenarios, you may find yourself needing to pass extra information to your query that shouldn’t (or doesn’t need to be) a part of the query key. useQuery
, usePaginatedQuery
and useInfiniteQuery
all support passing an optional array of additional parameters to be passed to your query function:
function Todo({ todoId, preview }) {
const { status, data, error } = useQuery(
// These will be used as the query key
['todo', todoId],
// These will get passed directly to our query function
[
debug,
{
foo: true,
bar: false,
},
],
fetchTodoById
)
}
function fetchTodoById(key, todoId, debug, { foo, bar }) {
return new Promise()
// ...
}
React Query makes it easy to make queries that depend on other queries for both:
To do this, you can use the following 2 approaches:
If a query isn’t ready to be requested yet, just pass a falsey value as the query key or as an item in the query key:
// Get the user
const { data: user } = useQuery(['user', { email }], getUserByEmail)
// Then get the user's projects
const { data: projects } = useQuery(
// `user` would be `null` at first (falsey),
// so the query will not execute until the user exists
user && ['projects', { userId: user.id }],
getProjectsByUser
)
Similar to above, you can also pass falsey items in you query key array:
// Only get the user when `email` is available
const { data: user } = useQuery(['user', email], getUserByEmail)
// Then get the user's projects
const { data: projects } = useQuery(
// `user && user.id` would be (falsey) at first,
// so the query will not execute until the user exists
['projects', user && user.id], // You could also do `user?.id` if you're using the latest babel!
getProjectsByUser
)
If a function is passed, the query will not execute until the function can be called without throwing:
// Get the user
const { data: user } = useQuery(['user', { email }])
// Then get the user's projects
const { data: projects } = useQuery(
// This will throw trying to access property `id` of `undefined` until the `user` is available
() => ['projects', { userId: user.id }]
)
const [ready, setReady] = React.useState(false)
// Get the user when we are `ready`
const { data: user } = useQuery(ready && ['user', { email }]) // Wait for ready to be truthy
// Then get the user's projects
const { data: projects } = useQuery(
() => ['projects', { userId: user.id }] // Wait for user.id to become available (and not throw)
React Query caching is automatic out of the box. It uses a stale-while-revalidate
in-memory caching strategy together with robust query deduping to always ensure a query’s data is only cached when it’s needed and only cached once even if that query is used multiple times across your application.
At a glance:
staleTime
option at both the global and query-level.cacheTime
option at both the global and query-level.cacheTime
specified (defaults to 5 minutes), the query is deleted and garbage collected.A more detailed example of the caching lifecycle
Let’s assume we are using the default cacheTime
of 5 minutes and the default staleTime
of 0
.
useQuery('todos', fetchTodos)
mounts.
'todos'
and `` as the unique identifiers for that cache.staleTime
option as a delay (defaults to 0
, or immediately).useQuery('todos', fetchTodos)
mounts elsewhere.
useQuery('todos', fetchTodos)
query are unmounted and no longer in use.
cacheTime
to delete and garbage collect the query (defaults to 5 minutes).useQuery('todos', fetchTodos)
appear within 5 minutes.
usePaginatedQuery
Rendering paginated data is a very common UI pattern to avoid overloading bandwidth or even your UI. React Query exposes a usePaginatedQuery
that is very similar to useQuery
that helps with this very scenario.
Consider the following example where we would ideally want to increment a pageIndex (or cursor) for a query. If we were to use useQuery
, it would technically work fine, but the UI would jump in and out of the success
and loading
states as different queries are created and destroyed for each page or cursor. By using usePaginatedQuery
we get a few new things:
data
, you should use resolvedData
instead. This is the data from the last known successful query result. As new page queries resolve, resolvedData
remains available to show the last page’s data while a new page is requested. When the new page data is received, resolvedData
get’s updated to the new page’s data.latestData
is available. When the desired page is being requested, latestData
will be undefined
until the query resolves, then it will get updated with the latest pages data result.function Todos() {
const [page, setPage] = React.useState(0)
const fetchProjects = (key, page = 0) => fetch('/api/projects?page=' + page)
const {
status,
resolvedData,
latestData,
error,
isFetching,
} = usePaginatedQuery(['todos', page], fetchProjects)
return (
<div>
{status === 'loading' ? (
<div>Loading...</div>
) : status === 'error' ? (
<div>Error: {error.message}</div>
) : (
// `resolvedData` will either resolve to the latest page's data
// or if fetching a new page, the last successful page's data
<div>
{resolvedData.projects.map(project => (
<p key={project.id}>{project.name}</p>
))}
</div>
)}
<span>Current Page: {page + 1}</span>
<button
onClick={() => setPage(old => Math.max(old - 1, 0))}
disabled={page === 0}
>
Previous Page
</button>{' '}
<button
onClick={() =>
// Here, we use `latestData` so the Next Page
// button isn't relying on potentially old data
setPage(old => (!latestData || !latestData.hasMore ? old : old + 1))
}
disabled={!latestData || !latestData.hasMore}
>
Next Page
</button>
{// Since the last page's data potentially sticks around between page requests,
// we can use `isFetching` to show a background loading
// indicator since our `status === 'loading'` state won't be triggered
isFetching ? <span> Loading...</span> : null}{' '}
</div>
)
}
useInfiniteQuery
Rendering lists that can additively “load more” data onto an existing set of data or “infinite scroll” is also a very common UI pattern. React Query supports a useful version of useQuery
called useInfiniteQuery
for querying these types of lists.
When using useInfiniteQuery
, you’ll notice a few things are different:
data
is now an array of arrays that contain query group results, instead of the query results themselvesfetchMore
function is now availablegetFetchMore
option is available for both determining if there is more data to load and the information to fetch it. This information is supplied as an additional parameter in the query function (which can optionally be overridden when calling the fetchMore
function)canFetchMore
boolean is now available and is true
if getFetchMore
returns a truthy valueisFetchingMore
boolean is now available to distinguish between a background refresh state and a loading more stateLet’s assume we have an API that returns pages of projects
3 at a time based on a cursor
index along with a cursor that can be used to fetch the next group of projects
fetch('/api/projects?cursor=0')
// { data: [...], nextCursor: 3}
fetch('/api/projects?cursor=3')
// { data: [...], nextCursor: 6}
fetch('/api/projects?cursor=6')
// { data: [...], nextCursor: 9}
fetch('/api/projects?cursor=9')
// { data: [...] }
With this information, we can create a “Load More” UI by:
useInfiniteQuery
to request the first group of data by defaultgetFetchMore
fetchMore
functionNote: It’s very important you do not call
fetchMore
with arguments unless you want them to override thefetchMoreInfo
data returned from thegetFetchMore
function. eg. Do not do this:<button onClick={fetchMore} />
as this would send the onClick event to thefetchMore
function.
import { useInfiniteQuery } from 'react-query'
function Projects() {
const fetchProjects = (key, cursor = 0) =>
fetch('/api/projects?cursor=' + cursor)
const {
status,
data,
isFetching,
isFetchingMore,
fetchMore,
canFetchMore,
} = useInfiniteQuery('projects', fetchProjects, {
getFetchMore: (lastGroup, allGroups) => lastGroup.nextCursor,
})
return status === 'loading' ? (
<p>Loading...</p>
) : status === 'error' ? (
<p>Error: {error.message}</p>
) : (
<>
{data.map((group, i) => (
<React.Fragment key={i}>
{group.projects.map(project => (
<p key={project.id}>{project.name}</p>
))}
</React.Fragment>
))}
<div>
<button
onClick={() => fetchMore()}
disabled={!canFetchMore || isFetchingMore}
>
{isFetchingMore
? 'Loading more...'
: canFetchMore
? 'Load More'
: 'Nothing more to load'}
</button>
</div>
<div>{isFetching && !isFetchingMore ? 'Fetching...' : null}</div>
</>
)
}
When an infinite query becomes stale
and needs to be refetched, each group is fetched individually
and in parallel with the same variables that were originally used to request each group. If an infinite query’s results are ever removed from the cache, the pagination restarts at the initial state with only the initial group being requested.
By default, the info returned from getFetchMore
will be supplied to the query function, but in some cases, you may want to override this. You can pass custom variables to the fetchMore
function which will override the default info like so:
function Projects() {
const fetchProjects = (key, cursor = 0) =>
fetch('/api/projects?cursor=' + cursor)
const {
status,
data,
isFetching,
isFetchingMore,
fetchMore,
canFetchMore,
} = useInfiniteQuery('projects', fetchProjects, {
getFetchMore: (lastGroup, allGroups) => lastGroup.nextCursor,
})
// Pass your own custom fetchMoreInfo
const skipToCursor50 = () => fetchMore(50)
}
Out of the box, “scroll restoration” for all queries (including paginated and infinite queries) Just Works™️ in React Query. The reason for this is that query results are cached and able to be retrieved synchronously when a query is rendered. As long as your queries are being cached long enough (the default time is 5 minutes) and have not been garbage collected, you should never experience any problems with scroll restoration.
If you ever want to disable a query from automatically running, you can use the manual = true
option. When manual
is set to true:
status === 'success'
statePro Tip #1: Because manual queries start in the
status === 'success'
state, you should consider supplying aninitialData
option to pre-populate the cache or similarly use a default parameter value when destructuring the query result
Pro Tip #2: Don’t use
manual
for dependent queries. Use Dependent Queries instead!
function Todos() {
const { status, data, error, refetch, isFetching } = useQuery(
'todos',
fetchTodoList,
{
manual: true,
initialData: [],
}
)
return (
<>
<button onClick={() => refetch()}>Fetch Todos</button>
{status === 'loading' ? (
<span>Loading...</span>
) : status === 'error' ? (
<span>Error: {error.message}</span>
) : (
// `status === 'success'` will be the initial state, so we need
// account for our initial data (an empty array)
<>
<ul>
{!data.length
? 'No todos yet...'
: data.map(todo => <li key={todo.id}>{todo.title}</li>)}
</ul>
<div>{isFetching ? 'Fetching...' : null}</div>
</>
)}
</>
)
}
When a useQuery
query fails (the function throws an error), React Query will automatically retry the query if that query’s request has not reached the max number of consecutive retries (defaults to 3
).
You can configure retries both on a global level and an individual query level.
retry = false
will disable retries.retry = 6
will retry failing requests 6 times before showing the final error thrown by the function.retry = true
will infinitely retry failing requests.import { useQuery } from 'react-query'
// Make specific query retry a certain number of times
const { status, data, error } = useQuery(
['todos', { page: 1 }],
fetchTodoList,
{
retry: 10, // Will retry failed requests 10 times before displaying an error
}
)
By default, retries in React Query do not happen immediately after a request fails. As is standard, a back-off delay is gradually applied to each retry attempt.
The default retryDelay
is set to double (starting at 1000
ms) with each attempt, but not exceed 30 seconds:
// Configure for all queries
import { ReactQueryConfigProvider } from 'react-query'
const queryConfig = {
retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
}
function App() {
return (
<ReactQueryConfigProvider config={queryConfig}>
...
</ReactQueryConfigProvider>
)
}
Though it is not recommended, you can obviously override the retryDelay
function/integer in both the Provider and individual query options. If set to an integer instead of a function the delay will always be the same amount of time:
const { status, data, error } = useQuery('todos', fetchTodoList, {
retryDelay: 10000, // Will always wait 1000ms to retry, regardless of how many retries
})
If you’re lucky enough, you may know enough about what your users will do to be able to prefetch the data they need before it’s needed! If this is the case, then you’re in luck. You can either use the prefetchQuery
function to prefetch the results of a query to be placed into the cache:
import { prefetchQuery } from 'react-query'
const prefetchTodos = async () => {
const queryData = await prefetchQuery('todos', () => fetch('/todos'))
// The results of this query will be cached like a normal query
}
The next time a useQuery
instance is used for a prefetched query, it will use the cached data! If no instances of useQuery
appear for a prefetched query, it will be deleted and garbage collected after the time specified in cacheTime
.
Alternatively, if you already have the data for your query synchronously available, you can use the[Query Cache’s setQueryData
method to directly add or update a query’s cached result
There may be times when you already have the initial data for a query synchronously available in your app. If and when this is the case, you can use the config.initialData
option to set the initial data for a query and skip the first round of fetching!
When providing an initialData
value that is anything other than undefined
:
status
will initialize as success
instead of loading
isStale
property will initialize as true
instead of falsefunction Todos() {
const queryInfo = useQuery('todos', () => fetch('/todos'), {
initialData: initialTodos,
})
}
If the process for accessing a query’s initial data is intensive or just not something you want to perform on every render, you can pass a function as the initialData
value. This function will be executed only once when the query is initialized, saving you precious memory and CPU:
function Todos() {
const queryInfo = useQuery('todos', () => fetch('/todos'), {
initialData: () => {
return getExpensiveTodos()
},
})
}
In some circumstances, you may be able to provide the initial data for a query from the cached result of another. A good example of this would be searching the cached data from a todos list query for an individual todo item, then using that as the initial data for your individual todo query:
function Todo({ todoId }) {
const queryInfo = useQuery(['todo', todoId], () => fetch('/todos'), {
initialData: () => {
// Use a todo from the 'todos' query as the initial data for this todo query
return queryCache.getQueryData('todos')?.find(d => d.id === todoId)
},
})
}
Most of the time, this pattern works well, but if your source query you’re using to look up the initial data from is old, you may not want to use the data at all and just fetch from the server. To make this decision easier, you can use the queryCache.getQuery
method instead to get more information about the source query, including an updatedAt
timestamp you can use to decide if the query is “fresh” enough for your needs:
function Todo({ todoId }) {
const queryInfo = useQuery(['todo', todoId], () => fetch('/todos'), {
initialData: () => {
// Get the query object
const query = queryCache.getQuery('todos')
// If the query exists and has data that is no older than 10 seconds...
if (query && Date.now() - query.updatedAt <= 10 * 1000) {
// return the individual todo
return query.state.data.find(d => d.id === todoId)
}
// Otherwise, return undefined and let it fetch!
},
})
}
When using SSR (server-side-rendering) with React Query there are a few things to note:
initialData
of an unfetched query. This means that by default, data
will be set to undefined
. To get around this in SSR, you can either pre-seed a query’s cache data using the config.initialData
option:const { status, data, error } = useQuery('todos', fetchTodoList, {
initialData: [{ id: 0, name: 'Implement SSR!' }],
})
// data === [{ id: 0, name: 'Implement SSR!'}]
Or, alternatively you can just destructure from undefined
in your query results:
const { status, data = [{ id: 0, name: 'Implement SSR!' }], error } = useQuery(
'todos',
fetchTodoList
)
The query’s state will still reflect that it is stale and has not been fetched yet, and once mounted, it will continue as normal and request a fresh copy of the query result.
React Query can also be used with React’s new Suspense for Data Fetching API’s. To enable this mode, you can set either the global or query level config’s suspense
option to true
.
Global configuration:
// Configure for all queries
import { ReactQueryConfigProvider } from 'react-query'
const queryConfig = {
suspense: true,
}
function App() {
return (
<ReactQueryConfigProvider config={queryConfig}>
...
</ReactQueryConfigProvider>
)
}
Query configuration:
const { useQuery } from 'react-query'
// Enable for an individual query
useQuery(queryKey, queryFn, { suspense: true })
When using suspense mode, status
states and error
objects are not needed and are then replaced by usage of the React.Suspense
component (including the use of the fallback
prop and React error boundaries for catching errors). Please see the Suspense Example for more information on how to set up suspense mode.
In addition to queries behaving differently in suspense mode, mutations also behave a bit differently. By default, instead of supplying the error
variable when a mutation fails, it will be thrown during the next render of the component it’s used in and propagate to the nearest error boundary, similar to query errors. If you wish to disable this, you can set the useErrorBoundary
option to false
. If you wish that errors are not thrown at all, you can set the throwOnError
option to false
as well!
Out of the box, React Query in suspense
mode works really well as a Fetch-on-render solution with no additional configuration. However, if you want to take it to the next level and implement a Fetch-as-you-render
model, we recommend implementing Prefetching on routing and/or user interactions events to initialize queries before they are needed.
By default, queries that become inactive before their promises are resolved are simply ignored instead of cancelled. Why is this?
But don’t worry! If your queries are high-bandwidth or potentially very expensive to download, React Query exposes a generic way to cancel query requests using a cancellation token or other related API. To integrate with this feature, attach a cancel
function to the promise returned by your query that implements your request cancellation. When a query becomes out-of-date or inactive, this promise.cancel
function will be called (if available):
Using axios
:
import { CancelToken } from 'axios'
const query = useQuery('todos', () => {
// Create a new CancelToken source for this request
const source = CancelToken.source()
const promise = axios.get('/todos', {
// Pass the source token to your request
cancelToken: source.token,
})
// Cancel the request if React Query calls the `promise.cancel` method
promise.cancel = () => {
source.cancel('Query was cancelled by React Query')
}
return promise
})
Using fetch
:
const query = useQuery('todos', () => {
// Create a new AbortController instance for this request
const controller = new AbortController()
// Get the abortController's signal
const signal = controller.signal
const promise = fetch('/todos', {
method: 'get',
// Pass the signal to your request
signal,
})
// Cancel the request if React Query calls the `promise.cancel` method
promise.cancel = controller.abort
return promise
})
Unlike queries, mutations are typically used to create/update/delete data or perform server side-effects. For this purpose, React Query exports a useMutation
hook.
Assuming the server implements a ping mutation, that returns “pong” string, here’s an example of the most basic mutation:
const PingPong = () => {
const [mutate, { status, data, error }] = useMutation(pingMutation)
const onPing = async () => {
try {
const data = await mutate()
console.log(data)
// { ping: 'pong' }
} catch {
// Uh oh, something went wrong
}
}
return <button onClick={onPing}>Ping</button>
}
Mutations without variables are not that useful, so let’s add some variables to closer match reality.
To pass variables
to your mutate
function, call mutate
with an object.
const CreateTodo = () => {
const [title, setTitle] = useState('')
const [mutate] = useMutation(createTodo)
const onCreateTodo = async e => {
// Prevent the form from refreshing the page
e.preventDefault()
try {
await mutate({ title })
// Todo was successfully created
} catch (error) {
// Uh oh, something went wrong
}
}
return (
<form onSubmit={onCreateTodo}>
<input
type="text"
value={title}
onChange={e => setTitle(e.target.value)}
/>
<br />
<button type="submit">Create Todo</button>
</form>
)
}
Even with just variables, mutations aren’t all that special, but when used with the onSuccess
option, the Query Cache’s refetchQueries
method method and the Query Cache’s setQueryData
method, mutations become a very powerful tool.
When a mutation succeeds, it’s likely that other queries in your application need to update. Where other libraries that use normalized caches would attempt to update locale queries with the new data imperatively, React Query helps you avoids the manual labor that comes with maintaining normalized caches and instead prescribes atomic updates and refetching instead of direct cache manipulation.
For example, assume we have a mutation to post a new todo:
const [mutate] = useMutation(postTodo)
When a successful postTodo
mutation happens, we likely want all todos
queries to get refetched to show the new todo item. To do this, you can use useMutation
’s onSuccess
options and the queryCache
’s refetchQueries
:
import { useMutation, queryCache } from 'react-query'
// When this mutation succeeds, refetch any queries with the `todos` or `reminders` query key
const [mutate] = useMutation(addTodo, {
onSuccess: () => {
queryCache.refetchQueries('todos')
queryCache.refetchQueries('reminders')
},
})
mutate(todo)
// The 3 queries below will be refetched when the mutation above succeeds
const todoListQuery = useQuery('todos', fetchTodoList)
const todoListQuery = useQuery(['todos', { page: 1 }], fetchTodoList)
const remindersQuery = useQuery('reminders', fetchReminders)
You can even refetch queries with specific variables by passing a more specific query key to the refetchQueries
method:
const [mutate] = useMutation(addTodo, {
onSuccess: () => {
queryCache.refetchQueries(['todos', { status: 'done' }])
},
})
mutate(todo)
// The query below will be refetched when the mutation above succeeds
const todoListQuery = useQuery(['todos', { status: 'done' }], fetchTodoList)
// However, the following query below will NOT be refetched
const todoListQuery = useQuery('todos', fetchTodoList)
The refetchQueries
API is very flexible, so even if you want to only refetch todos
queries that don’t have any more variables or sub keys, you can pass an exact: true
option to the refetchQueries
method:
const [mutate] = useMutation(addTodo, {
onSuccess: () => {
queryCache.refetchQueries('todos', { exact: true })
},
})
mutate(todo)
// The query below will be refetched when the mutation above succeeds
const todoListQuery = useQuery(['todos'], fetchTodoList)
// However, the following query below will NOT be refetched
const todoListQuery = useQuery(['todos', { status: 'done' }], fetchTodoList)
If you find yourself wanting even more granularity, you can pass a predicate function to the refetchQueries
method. This function will receive each query object from the queryCache and allow you return true
or false
for whether you want to refetch that query:
const [mutate] = useMutation(addTodo, {
onSuccess: () => {
queryCache.refetchQueries(
query => query.queryKey[0] === 'todos' && query.queryKey[1]?.version >= 10
)
},
})
mutate(todo)
// The query below will be refetched when the mutation above succeeds
const todoListQuery = useQuery(['todos', { version: 20 }], fetchTodoList)
// The query below will be refetched when the mutation above succeeds
const todoListQuery = useQuery(['todos', { version: 10 }], fetchTodoList)
// However, the following query below will NOT be refetched
const todoListQuery = useQuery(['todos', { version: 5 }], fetchTodoList)
If you prefer that the promise returned from mutate()
only resolves after the onSuccess
callback, you can return a promise in the onSuccess
callback:
const [mutate] = useMutation(addTodo, {
onSuccess: () =>
// return a promise!
queryCache.refetchQueries(
query => query.queryKey[0] === 'todos' && query.queryKey[1]?.version >= 10
),
})
const run = async () => {
try {
await mutate(todo)
console.log('I will only log after onSuccess is done!')
} catch {}
}
If you would like to refetch queries on error or even regardless of a mutation’s success or error, you can use the onError
or onSettled
callbacks:
const [mutate] = useMutation(addTodo, {
onError: error => {
// Refetch queries or more...
},
onSettled: (data, error) => {
// Refetch queries or more...
},
})
mutate(todo)
You might find that you want to override some of useMutation
’s options at the time of calling mutate
. To do that, you can optionally override them by sending them through as options to the mutate
function after your mutation variable. Supported option overrides are include:
onSuccess
onSettled
onError
throwOnError
const [mutate] = useMutation(addTodo)
mutate(todo, {
onSuccess: () => {},
onSettled: () => {},
onError: () => {},
throwOnError: true,
})
When dealing with mutations that update objects on the server, it’s common for the new object to be automatically returned in the response of the mutation. Instead of refetching any queries for that item and wasting a network call for data we already have, we can take advantage of the object returned by the mutation function and update the existing query with the new data immediately using the Query Cache’s setQueryData
method:
const [mutate] = useMutation(editTodo)
mutate(
{
id: 5,
name: 'Do the laundry',
},
{
onSuccess: data => queryCache.setQueryData(['todo', { id: 5 }], data),
}
)
// The query below will be updated with the response from the
// successful mutation
const { status, data, error } = useQuery(['todo', { id: 5 }], fetchTodoByID)
It’s sometimes the case that you need to clear the error
or data
of a mutation request. To do this, you can use the reset
function to handle this:
const CreateTodo = () => {
const [title, setTitle] = useState('')
const [mutate, { error, reset }] = useMutation(createTodo)
const onCreateTodo = async e => {
e.preventDefault()
await mutate({ title })
}
return (
<form onSubmit={onCreateTodo}>
{error && <h5 onClick={() => reset()}>{error}</h5>}
<input
type="text"
value={title}
onChange={e => setTitle(e.target.value)}
/>
<br />
<button type="submit">Create Todo</button>
</form>
)
}
In rare circumstances, you may want to manually update a query’s response with a custom value. To do this, you can again use the Query Cache’s setQueryData
method:
**It’s important to understand that when you manually or optimistically update a query’s data value, the potential that you display out-of-sync data to your users is very high. It’s recommended that you only do this if you plan to refetch the query very soon or perform a mutation to “commit” your manual changes (and also roll back your eager update if the refetch or mutation fails).
// Full replacement
queryCache.setQueryData(['todo', { id: 5 }], newTodo)
// or functional update
queryCache.setQueryData(['todo', { id: 5 }], previous => ({
...previous,
status: 'done',
}))
A query’s status === 'loading'
state is sufficient enough to show the initial hard-loading state for a query, but sometimes you may want to display an additional indicator that a query is refetching in the background. To do this, queries also supply you with an isFetching
boolean that you can use to show that it’s in a fetching state, regardless of the state of the status
variable:
function Todos() {
const { status, data: todos, error, isFetching } = useQuery(
'todos',
fetchTodos
)
return status === 'loading' ? (
<span>Loading...</span>
) : status === 'error' ? (
<span>Error: {error.message}</span>
) : (
<>
{isFetching ? <div>Refreshing...</div> : null}
<div>
{todos.map(todo => (
<Todo todo={todo} />
))}
</div>
</>
)
}
In addition to individual query loading states, if you would like to show a global loading indicator when any queries are fetching (including in the background), you can use the useIsFetching
hook:
import { useIsFetching } from 'react-query'
function GlobalLoadingIndicator() {
const isFetching = useIsFetching()
return isFetching ? (
<div>Queries are fetching in the background...</div>
) : null
}
If a user leaves your application and returns to stale data, you may want to trigger an update in the background to update any stale queries. Thankfully, React Query does this automatically for you, but if you choose to disable it, you can use the ReactQueryConfigProvider
’s refetchAllOnWindowFocus
option to disable it:
const queryConfig = { refetchAllOnWindowFocus: false }
function App() {
return (
<ReactQueryConfigProvider config={queryConfig}>
...
</ReactQueryConfigProvider>
)
}
In rare circumstances, you may want to manage your own window focus events that trigger React Query to revalidate. To do this, React Query provides a setFocusHandler
function that supplies you the callback that should be fired when the window is focused and allows you to set up your own events. When calling setFocusHandler
, the previously set handler is removed (which in most cases will be the default handler) and your new handler is used instead. For example, this is the default handler:
setFocusHandler(handleFocus => {
// Listen to visibillitychange and focus
if (typeof window !== 'undefined' && window.addEventListener) {
window.addEventListener('visibilitychange', handleFocus, false)
window.addEventListener('focus', handleFocus, false)
}
return () => {
// Be sure to unsubscribe if a new handler is set
window.removeEventListener('visibilitychange', handleFocus)
window.removeEventListener('focus', handleFocus)
}
})
A great use-case for replacing the focus handler is that of iframe events. Iframes present problems with detecting window focus by both double-firing events and also firing false-positive events when focusing or using iframes within your app. If you experience this, you should use an event handler that ignores these events as much as possible. I recommend this one! It can be set up in the following way:
import { setFocusHandler } from 'react-query'
import onWindowFocus from './onWindowFocus' // The gist above
setFocusHandler(onWindowFocus) // Boom!
WARNING: This is an advanced and experimental feature. There be dragons here. Do not change the Query Key Serializer unless you know what you are doing and are fine with encountering edge cases in React Query’s API
If you absolutely despise the default query key implementation, then please file an issue in this repo first. If you still believe you need something different, then you can choose to replace the default query key serializer with your own by using the ReactQueryConfigProvider
hook’s queryKeySerializerFn
option:
const queryConfig = {
queryKeySerializerFn: queryKey => {
// Your custom logic here...
// Make sure object keys are sorted and all values are
// serializable
const queryFnArgs = getQueryArgs(queryKey)
// Hash the query key args to get a string
const queryHash = hash(queryFnArgs)
// Return both the queryHash and normalizedQueryHash as a tuple
return [queryHash, queryFnArgs]
},
}
function App() {
return (
<ReactQueryConfigProvider config={queryConfig}>
...
</ReactQueryConfigProvider>
)
}
userQueryKey: any
useQuery
and all other public methods and utilities exported by React Query.queryFnArgs
queryHash: string
string
representing the entire query key.queryFnArgs: Array<any>
An additional
stableStringify
utility is also exported to help with stringifying objects to have sorted keys.
The example below shows how to build your own serializer for use with urls and use it with React Query:
import { ReactQueryConfigProvider, stableStringify } from 'react-query'
function urlQueryKeySerializer(queryKey) {
// Deconstruct the url
let [url, params = ''] = queryKey.split('?')
// Remove trailing slashes from the url to make an ID
url = url.replace(/\/{1,}$/, '')
// Build the searchQuery object
params.split('&').filter(Boolean)
// If there are search params, return a different key
if (Object.keys(params).length) {
let searchQuery = {}
params.forEach(param => {
const [key, value] = param.split('=')
searchQuery[key] = value
})
// Use stableStringify to turn searchQuery into a stable string
const searchQueryHash = stableStringify(searchQuery)
// Get the stable json object for the normalized key
searchQuery = JSON.parse(searchQueryHash)
return [`${url}_${searchQueryHash}`, [url, searchQuery]]
}
return [url, [url]]
}
const queryConfig = {
queryKeySerializerFn: urlQueryKeySerializer,
}
function App() {
return (
<ReactQueryConfigProvider config={queryConfig}>
...
</ReactQueryConfigProvider>
)
}
// Heck, you can even make your own custom useQueryHook!
function useUrlQuery(url, options) {
return useQuery(url, (url, params) =>
axios
.get(url, {
params,
})
.then(res => res.data)
)
}
// Use it in your app!
function Todos() {
const todosQuery = useUrlQuery(`/todos`)
}
function FilteredTodos({ status = 'pending' }) {
const todosQuery = useUrlQuery(`/todos?status=pending`)
}
function Todo({ id }) {
const todoQuery = useUrlQuery(`/todos/${id}`)
}
refetchQuery('/todos')
refetchQuery('/todos?status=pending')
refetchQuery('/todos/5')
The example below shows how to you build your own functional serializer and use it with React Query:
import { ReactQueryConfigProvider, stableStringify } from 'react-query'
// A map to keep track of our function pointers
const functionSerializerMap = new Map()
function functionQueryKeySerializer(queryKey) {
if (!queryKey) {
return []
}
let queryFn = queryKey
let variables
if (Array.isArray(queryKey)) {
queryFn = queryKey[0]
variables = queryKey[1]
}
// Get or create an ID for the function pointer
const queryGroupId =
functionSerializerMap.get(queryFn) ||
(() => {
const id = Date.now()
functionSerializerMap.set(queryFn, id)
return id
})()
const variablesIsObject = isObject(variables)
const variablesHash = variablesIsObject ? stableStringify(variables) : ''
const queryHash = `${queryGroupId}_${variablesHash}`
return [queryHash, queryGroupId, variablesHash, variables]
}
const queryConfig = {
queryKeySerializerFn: functionQueryKeySerializer,
}
function App() {
return (
<ReactQueryConfigProvider config={queryConfig}>
...
</ReactQueryConfigProvider>
)
}
// Heck, you can even make your own custom useQueryHook!
function useFunctionQuery(functionTuple, options) {
const [queryFn, variables] = Array.isArray(functionTuple)
? functionTuple
: [functionTuple]
return useQuery(functionTuple, queryFn, options)
}
// Use it in your app!
function Todos() {
const todosQuery = useFunctionQuery(getTodos)
}
function FilteredTodos({ status = 'pending' }) {
const todosQuery = useFunctionQuery([getTodos, { status }])
}
function Todo({ id }) {
const todoQuery = useFunctionQuery([getTodo, { id }])
}
refetchQuery(getTodos)
refetchQuery([getTodos, { status: 'pending' }])
refetchQuery([getTodo, { id: 5 }])
useQuery
const {
status,
data,
error,
isFetching,
failureCount,
refetch,
} = useQuery(queryKey, [, queryVariables], queryFn, {
manual,
retry,
retryDelay,
staleTime
cacheTime,
refetchInterval,
refetchIntervalInBackground,
refetchOnWindowFocus,
onSuccess,
onError,
onSettled,
suspense,
initialData,
refetchOnMount
})
queryKey: String | [String, Variables: Object] | falsey | Function => queryKey
[String, Object]
tuple is passed, they will be serialized into a stable query key. See Query Keys for more information.manual
is not set to true
).Variables: Object
queryFn: Function(variables) => Promise(data/error)
manual: Boolean
true
to disable automatic refetching when the query mounts or changes query keys.refetch
method returned from the useQuery
instance.retry: Boolean | Int
false
, failed queries will not retry by default.true
, failed queries will retry infinitely.Int
, e.g. 3
, failed queries will retry until the failed query count meets that number.retryDelay: Function(retryAttempt: Int) => Int
retryAttempt
integer and returns the delay to apply before the next attempt in milliseconds.attempt => Math.min(attempt > 1 ? 2 ** attempt * 1000 : 1000, 30 * 1000)
applies exponential backoff.attempt => attempt * 1000
applies linear backoff.staleTime: Int
cacheTime: Int
refetchInterval: false | Integer
refetchIntervalInBackground: Boolean
true
, queries that are set to continuously refetch with a refetchInterval
will continue to refetch while their tab/window is in the backgroundrefetchOnWindowFocus: Boolean
false
to disable automatic refetching on window focus (useful, when refetchAllOnWindowFocus
is set to true
).true
to enable automatic refetching on window focus (useful, when refetchAllOnWindowFocus
is set to false
.onSuccess: Function(data) => data
onError: Function(err) => void
onSettled: Function(data, error) => data
suspense: Boolean
true
to enable suspense mode.true
, useQuery
will suspend when status === 'loading'
true
, useQuery
will throw runtime errors when status === 'error'
initialData: any | Function() => any
refetchOnMount: Boolean
true
false
, will disable additional instances of a query to trigger background refetchesstatus: String
loading
if the query is in an initial loading state. This means there is no cached data and the query is currently fetching, eg isFetching === true
)error
if the query attempt resulted in an error. The corresponding error
property has the error received from the attempted fetchsuccess
if the query has received a response with no errors and is ready to display its data. The corresponding data
property on the query is the data received from the successful fetch or if the query is in manual
mode and has not been fetched yet data
is the first initialData
supplied to the query on initialization.data: Any
undefined
.error: null | Error
null
isFetching: Boolean
true
so long as manual
is set to false
true
if the query is currently fetching, including background fetching.failureCount: Integer
0
when the query succeeds.refetch: Function({ variables: Object, merge: Function, disableThrow: Boolean })
disableThrow
to true to disable this function from throwing if an error is encountered.usePaginatedQuery
const {
status,
resolvedData,
latestData,
error,
isFetching,
failureCount,
refetch,
} = usePaginatedQuery(queryKey, [, queryVariables], queryFn, {
manual,
retry,
retryDelay,
staleTime
cacheTime,
refetchInterval,
refetchIntervalInBackground,
refetchOnWindowFocus,
onSuccess,
onError,
suspense,
initialData,
refetchOnMount
})
queryKey: String | [String, Variables: Object] | falsey | Function => queryKey
[String, Object]
tuple is passed, they will be serialized into a stable query key. See Query Keys for more information.manual
is not set to true
).Variables: Object
queryFn: Function(variables) => Promise(data/error)
manual: Boolean
true
to disable automatic refetching when the query mounts or changes query keys.refetch
method returned from the useQuery
instance.retry: Boolean | Int
false
, failed queries will not retry by default.true
, failed queries will retry infinitely.Int
, e.g. 3
, failed queries will retry until the failed query count meets that number.retryDelay: Function(retryAttempt: Int) => Int
retryAttempt
integer and returns the delay to apply before the next attempt in milliseconds.attempt => Math.min(attempt > 1 ? 2 ** attempt * 1000 : 1000, 30 * 1000)
applies exponential backoff.attempt => attempt * 1000
applies linear backoff.staleTime: Int
cacheTime: Int
refetchInterval: false | Integer
refetchIntervalInBackground: Boolean
true
, queries that are set to continuously refetch with a refetchInterval
will continue to refetch while their tab/window is in the backgroundrefetchOnWindowFocus: Boolean
false
to disable automatic refetching on window focus (useful, when refetchAllOnWindowFocus
is set to true
).true
to enable automatic refetching on window focus (useful, when refetchAllOnWindowFocus
is set to false
.onSuccess: Function(data) => data
onError: Function(error) => void
onSettled: Function(data, error) => data
suspense: Boolean
true
to enable suspense mode.true
, useQuery
will suspend when status === 'loading'
true
, useQuery
will throw runtime errors when status === 'error'
initialData: any
refetchOnMount: Boolean
true
false
, will disable additional instances of a query to trigger background refetchesstatus: String
loading
if the query is in an initial loading state. This means there is no cached data and the query is currently fetching, eg isFetching === true
)error
if the query attempt resulted in an error. The corresponding error
property has the error received from the attempted fetchsuccess
if the query has received a response with no errors and is ready to display its data. The corresponding data
property on the query is the data received from the successful fetch or if the query is in manual
mode and has not been fetched yet data
is the first initialData
supplied to the query on initialization.resolveData: Any
undefined
.latestData: Any
undefined
.undefined
error: null | Error
null
isFetching: Boolean
true
so long as manual
is set to false
true
if the query is currently fetching, including background fetching.isFetchingMore: Boolean
paginated
mode, this will be true
when fetching more results using the fetchMore
function.failureCount: Integer
0
when the query succeeds.refetch: Function({ variables: Object, merge: Function, disableThrow: Boolean })
disableThrow
to true to disable this function from throwing if an error is encountered.fetchMore: Function(variables) => Promise
paginated
mode, this function allows you to fetch the next “page” of results.variables
should be an object that is passed to your query function to retrieve the next page of results.useInfiniteQuery
const queryFn = (...queryKey, nextPageVariables) => Promise
const {
status,
data,
error,
isFetching,
failureCount,
refetch,
} = useInfiniteQuery(queryKey, [, queryVariables], queryFn, {
getFetchMore: (lastPage, allPages) => nextPageVariables
manual,
retry,
retryDelay,
staleTime
cacheTime,
refetchInterval,
refetchIntervalInBackground,
refetchOnWindowFocus,
onSuccess,
onError,
suspense,
initialData,
refetchOnMount
})
queryKey: String | [String, Variables: Object] | falsey | Function => queryKey
[String, Object]
tuple is passed, they will be serialized into a stable query key. See Query Keys for more information.manual
is not set to true
).Variables: Object
queryFn: Function(variables) => Promise(data/error)
getFetchMore
function, used to fetch the next pagegetFetchMore: Function | Boolean
manual: Boolean
true
to disable automatic refetching when the query mounts or changes query keys.refetch
method returned from the useQuery
instance.retry: Boolean | Int
false
, failed queries will not retry by default.true
, failed queries will retry infinitely.Int
, e.g. 3
, failed queries will retry until the failed query count meets that number.retryDelay: Function(retryAttempt: Int) => Int
retryAttempt
integer and returns the delay to apply before the next attempt in milliseconds.attempt => Math.min(attempt > 1 ? 2 ** attempt * 1000 : 1000, 30 * 1000)
applies exponential backoff.attempt => attempt * 1000
applies linear backoff.staleTime: Int
cacheTime: Int
refetchInterval: false | Integer
refetchIntervalInBackground: Boolean
true
, queries that are set to continuously refetch with a refetchInterval
will continue to refetch while their tab/window is in the backgroundrefetchOnWindowFocus: Boolean
false
to disable automatic refetching on window focus (useful, when refetchAllOnWindowFocus
is set to true
).true
to enable automatic refetching on window focus (useful, when refetchAllOnWindowFocus
is set to false
.onSuccess: Function(data) => data
onError: Function(err) => void
onSettled: Function(data, error) => data
suspense: Boolean
true
to enable suspense mode.true
, useQuery
will suspend when status === 'loading'
true
, useQuery
will throw runtime errors when status === 'error'
initialData: any
refetchOnMount: Boolean
true
false
, will disable additional instances of a query to trigger background refetchesstatus: String
loading
if the query is in an initial loading state. This means there is no cached data and the query is currently fetching, eg isFetching === true
)error
if the query attempt resulted in an error. The corresponding error
property has the error received from the attempted fetchsuccess
if the query has received a response with no errors and is ready to display its data. The corresponding data
property on the query is the data received from the successful fetch or if the query is in manual
mode and has not been fetched yet data
is the first initialData
supplied to the query on initialization.data: Any
[]
.error: null | Error
null
isFetching: Boolean
true
so long as manual
is set to false
true
if the query is currently fetching, including background fetching.isFetchingMore: Boolean
paginated
mode, this will be true
when fetching more results using the fetchMore
function.failureCount: Integer
0
when the query succeeds.refetch: Function({ variables: Object, merge: Function, disableThrow: Boolean })
disableThrow
to true to disable this function from throwing if an error is encountered.fetchMore: Function(fetchMoreVariablesOverride) => Promise
paginated
mode, this function allows you to fetch the next “page” of results.variables
should be an object that is passed to your query function to retrieve the next page of results.canFetchMore: Boolean
paginated
mode, this will be true
if there is more data to be fetched (known via the required getFetchMore
option function).useMutation
const [mutate, { status, data, error }] = useMutation(mutationFn, {
onSuccess,
onSettled,
onError,
throwOnError,
useErrorBoundary,
})
const promise = mutate(variables, {
onSuccess,
onSettled,
onError,
throwOnError,
})
mutationFn: Function(variables) => Promise
variables: any
mutationFn
.onSuccess: Function(data) => Promise | undefined
onError: Function(err) => Promise | undefined
onSettled: Function(data, error) => Promise | undefined
throwOnError
false
true
if failed mutations should re-throw errors from the mutation function to the mutate
function.useErrorBoundary
useErrorBoundary
value, which is false
mutate: Function(variables, { onSuccess, onSettled, onError, throwOnError, })
status: String
loading
if the mutation is currently executing.error
if the last mutation attempt resulted in an error.data: null | Any
null
error: null | Error
promise: Promise
mutationFn
.queryCache
The queryCache
instance is the backbone of React Query that manages all of the state, caching, lifecycle and magic of every query. It supports relatively unrestricted, but safe, access to manipulate query’s as you need. Its available properties and methods are:
prefetchQuery
getQueryData
setQueryData
refetchQueries
removeQueries
getQuery
subscribe
isFetching
clear
queryCache.prefetchQuery
prefetchQuery
is an asynchronous function that can be used to fetch and cache a query response before it is needed or fetched with useQuery
. If the query does not exist, it will be created and immediately be marked as stale. If the query is not utilized by a query hook in the default cacheTime
of 5 minutes, the query will be garbage collected.
The difference between using
prefetchQuery
andupdateQuery
is thatprefetchQuery
is async and will ensure that duplicate requests for this query are not created withuseQuery
instances for the same query are rendered while the data is fetching.
import { prefetchQuery } from 'react-query'
const data = await prefetchQuery(queryKey, queryFn)
For convenience in syntax, you can also pass optional query variables to prefetchQuery
just like you can useQuery
:
import { prefetchQuery } from 'react-query'
const data = await prefetchQuery(queryKey, queryVariables, queryFn, config)
The options for prefetchQuery
are exactly the same as those of useQuery
with the exception of:
config.throwOnError: Boolean
true
if you want prefetchQuery
to throw an error when it encounters errors.promise: Promise
throwOnError
option to true
queryCache.getQueryData
getQueryData
is a synchronous function that can be used to get an existing query’s cached data. If the query does not exist, undefined
will be returned.
import { getQueryData } from 'react-query'
const data = getQueryData(queryKey)
queryKey: QueryKey
data: any | undefined
undefined
if the query does not exist.queryCache.setQueryData
setQueryData
is a synchronous function that can be used to immediately update a query’s cached data. If the query does not exist, it will be created and immediately be marked as stale. If the query is not utilized by a query hook in the default cacheTime
of 5 minutes, the query will be garbage collected.
The difference between using
setQueryData
andupdateQuery
is thatsetQueryData
is sync and assumes that you already synchronously have the data available. If you need to fetch the data asynchronously, it’s suggested that you either refetch the query key or useprefetchQuery
to handle the asynchronous fetch.
import { setQueryData } from 'react-query'
setQueryData(queryKey, updater)
queryKey: QueryKey
updater: Any | Function(oldData) => newData
setQueryData(queryKey, newData)
For convenience in syntax, you can also pass an updater function which receives the current data value and returns the new one:
setQueryData(queryKey, oldData => newData)
queryCache.refetchQueries
The refetchQueries
method can be used to refetch multiple queries in cache based on their query keys or any other functionally accessible property/state of the query.
import { queryCache } from 'react-query'
const queries = queryCache.refetchQueries(inclusiveQueryKeyOrPredicateFn, {
exact,
throwOnError,
})
queryKeyOrPredicateFn
can either be a Query Key or a function
queryKey: QueryKey
'todos'
, it would match queries with the todos
, ['todos']
, and ['todos', 5]
.Function(query) => Boolean
found
.exact
option has no effect with using a functionexact: Boolean
exact: true
option to return only the query with the exact query key you have passed. Don’t remember to destructure it out of the array!throwOnError: Boolean
true
, this function will throw if any of the query refetch tasks fail.This function returns a promise that will resolve when all of the queries are done being refetched. By default, it will not throw an error if any of those queries refetches fail, but this can be configured by setting the throwOnError
option to true
queryCache.removeQueries
The removeQueries
method can be used to remove queries from the cache based on their query keys or any other functionally accessible property/state of the query.
import { queryCache } from 'react-query'
const queries = queryCache.removeQueries(queryKeyOrPredicateFn, {
exact,
})
queryKeyOrPredicateFn
can either be a Query Key or a function
queryKey
'todos'
, it would match queries with the todos
, ['todos']
, and ['todos', 5]
.Function(query) => Boolean
found
.exact
option has no effect with using a functionexact: Boolean
exact: true
option to return only the query with the exact query key you have passed. Don’t remember to destructure it out of the array!This function does not return anything
queryCache.getQuery
getQuery
is a slightly more advanced synchronous function that can be used to get an existing query object from the cache. This object not only contains all the state for the query, but all of the instances, and underlying guts of the query as well. If the query does not exist, undefined
will be returned.
Note: This is not typically needed for most applications, but can come in handy when needing more information about a query in rare scenarios (eg. Looking at the query.updatedAt timestamp to decide whether a query is fresh enough to be used as an initial value)
import { getQuery } from 'react-query'
const query = getQuery(queryKey)
queryKey: QueryKey
query: QueryObect
queryCache.isFetching
This isFetching
property is an integer
representing how many queries, if any, in the cache are currently fetching (including background-fetching, loading new pages, or loading more infinite query results)
import { queryCache } from 'react-query'
if (queryCache.isFetching) {
console.log('At least one query is fetching!')
)
}
React Query also exports a handy useIsFetching
hook that will let you subscribe to this state in your components without creating a manual subscription to the query cache.
queryCache.subscribe
The subscribe
method can be used to subscribe to the query cache as a whole and be informed of safe/known updates to the cache like query states changing or queries being updated, added or removed
import { queryCache } from 'react-query'
const callback = cache => {}
const unsubscribe = queryCache.subscribe(callback)
callback: Function(queryCache) => void
query.setState
, queryCache.removeQueries
, etc). Out of scope mutations to the queryCache are not encouraged and will not fire subscription callbacksunsubscribe: Function => void
queryCache.clear
The clear
method can be used to clear the queryCache entirely and start fresh.
import { queryCache } from 'react-query'
const callback = cache => {}
queryCache.clear()
queries: Array<Query>
useIsFetching
useIsFetching
is an optional hook that returns true
if any query in your application is loading or fetching in the background (useful for app-wide loading indicators).
import { useIsFetching } from 'react-query'
const isFetching = useIsFetching()
isFetching: Boolean
true
if any query in your application is loading or fetching in the background.ReactQueryConfigProvider
ReactQueryConfigProvider
is an optional provider component and can be used to define defaults for all instances of useQuery
within it’s sub-tree:
import { ReactQueryConfigProvider } from 'react-query'
const queryConfig = {
// Global
suspense: false,
useErrorBoundary: undefined, // Defaults to the value of `suspense` if not defined otherwise
throwOnError: false,
refetchAllOnWindowFocus: true,
queryKeySerializerFn: queryKey => [queryHash, queryFnArgs],
onSuccess: () => {},
onError: () => {},
onSettled: () => {},
// useQuery
retry: 3,
retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
staleTime: 0,
cacheTime: 5 * 60 * 1000,
refetchInterval: false,
queryFnParamsFilter: args => filteredArgs,
refetchOnMount: true,
}
function App() {
return (
<ReactQueryConfigProvider config={queryConfig}>
...
</ReactQueryConfigProvider>
)
}
config: Object
useQuery
hook and the useMutation
hook.setConsole
setConsole
is an optional utility function that allows you replace the console
interface used to log errors. By default, the window.console
object is used. If no global console
object is found in the environment, nothing will be logged.
import { setConsole } from 'react-query'
import { printLog, printWarn, printError } from 'custom-logger'
setConsole({
log: printLog,
warn: printWarn,
error: printError,
})
console: Object
log
, warn
, and error
methods.#reactjs #webdeve #javascript