react-query
Version:
Hooks for managing, caching and syncing asynchronous and remote data in React
1,250 lines (991 loc) • 98.5 kB
Markdown

<img src='https://github.com/tannerlinsley/react-query/raw/master/media/logo.png' width='300'/>
Hooks for fetching, caching and updating asynchronous data in React
<a href="https://twitter.com/intent/tweet?button_hashtag=TanStack" target="\_parent">
<img alt="#TanStack" src="https://img.shields.io/twitter/url?color=%2308a0e9&label=%23TanStack&style=social&url=https%3A%2F%2Ftwitter.com%2Fintent%2Ftweet%3Fbutton_hashtag%3DTanStack">
</a><a href="https://github.com/tannerlinsley/react-query/actions?query=workflow%3A%22react-query+tests%22">
<img src="https://github.com/tannerlinsley/react-query/workflows/react-query%20tests/badge.svg" />
</a><a href="https://npmjs.com/package/react-query" target="\_parent">
<img alt="" src="https://img.shields.io/npm/dm/react-query.svg" />
</a><a href="https://bundlephobia.com/result?p=react-query@latest" target="\_parent">
<img alt="" src="https://badgen.net/bundlephobia/minzip/react-query@latest" />
</a><a href="https://github.com/tannerlinsley/react-query/discussions">
<img alt="Join the discussion on Github" src="https://img.shields.io/badge/Github%20Discussions%20%26%20Support-Chat%20now!-blue" />
</a><a href="https://github.com/tannerlinsley/react-query" target="\_parent">
<img alt="" src="https://img.shields.io/github/stars/tannerlinsley/react-query.svg?style=social&label=Star" />
</a><a href="https://twitter.com/tannerlinsley" target="\_parent">
<img alt="" src="https://img.shields.io/twitter/follow/tannerlinsley.svg?style=social&label=Follow" />
</a>
Enjoy this library? Try them all! [React Table](https://github.com/tannerlinsley/react-table), [React Form](https://github.com/tannerlinsley/react-form), [React Charts](https://github.com/tannerlinsley/react-charts)
## Quick Features
- Transport/protocol/backend agnostic data fetching (REST, GraphQL, promises, whatever!)
- Auto Caching + Refetching (stale-while-revalidate, Window Refocus, Polling/Realtime)
- Parallel + Dependent Queries
- Mutations + Reactive Query Refetching
- Multi-layer Cache + Automatic Garbage Collection
- Paginated + Cursor-based Queries
- Load-More + Infinite Scroll Queries w/ Scroll Recovery
- Request Cancellation
- [React Suspense](https://reactjs.org/docs/concurrent-mode-suspense.html) + Fetch-As-You-Render Query Prefetching
- [Dedicated Devtools (React Query Devtools)](https://github.com/tannerlinsley/react-query-devtools)
- <a href="https://bundlephobia.com/result?p=react-query@latest" target="\_parent">
<img alt="" src="https://badgen.net/bundlephobia/minzip/react-query@latest" />
</a>
<details>
<summary>Core Issues and Solution</summary>
## The Challenge
Tools for managing "global state" are plentiful these days, but most of these tools:
- Mistake **server cache state** for **global state**
- Force you to manage async data in a synchronous way
- Duplicate unnecessary network operations
- Use naive or over-engineered caching strategies
- Are too basic to handle large-scale apps or
- Are too complex or built for highly-opinionated systems like Redux, GraphQL, [insert proprietary tools], etc.
- Do not provide tools for server mutations
- Either do not provide easy access to the cache or do, but expose overpowered foot-gun APIs to the developer
## The Solution
React Query exports a set of hooks that address these issues. Out of the box, React Query:
- Separates your **server cache state** from your **global state**
- Provides async aware APIs for reading and updating server state/cache
- Dedupes both async and sync requests to async resources
- Automatically caches data, invalidates and refetches stale data, and manages garbage collection of unused data
- Scales easily as your application grows
- Is based solely on Promises, making it highly unopinionated and interoperable with any data fetching strategy including REST, GraphQL and other transactional APIs
- Provides an integrated promise-based mutation API
- Opt-in Manual or Advance cache management
</details>
<details>
<summary>Inspiration & Hat-Tipping</summary>
<br />
A big thanks to both [Draqula](https://github.com/vadimdemedes/draqula) for inspiring a lot of React Query's original API and documentation and also [Zeit's SWR](https://github.com/zeit/swr) and its creators for inspiring even further customizations and examples. You all rock!
</details>
<details>
<summary>How is this different from Zeit's SWR?</summary>
<br />
[Zeit's SWR](https://github.com/zeit/swr) is a great library, and is very similar in spirit and implementation to React Query with a few notable differences:
- Automatic Cache Garbage Collection - React Query handles automatic cache purging for inactive queries and garbage collection. This can mean a much smaller memory footprint for apps that consume a lot of data or data that is changing often in a single session
- No Default Data Fetcher Function - React Query does not ship with a default fetcher (but can easily be wrapped inside of a custom hook to achieve the same functionality)
- Query Key Generation - React Query uses query key generation, query variables, and implicit query grouping. The query key and variables that are passed to a query are less URL-based by nature and much more flexible. Both the key (todos) and any variables ({ type: 'done' }) are used to compute the unique key for a query (and it's done in a very stable, deterministic way). This also allows you to use query key "groups" when defining query refetching configs, eg. you can refetch every query that starts with a `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.
- Query cancellation integration is baked into React Query. You can easily use this to wire up request cancellation in most popular fetching libraries, including but not limited to fetch and axios.
- Prefetching - React Query ships with 1st class prefetching utilities which not only come in handy with non-suspenseful apps but also make fetch-as-you-render patterns possible with React Query. SWR does not come with similar utilities and relies on `<link rel='preload'>` and/or manually fetching and updating the query cache
- Overall API design opinions
</details>
## Examples
- [Basic](./examples/basic)
- [Custom Hooks](./examples/custom-hooks)
- [Auto Refetching / Polling / Realtime](./examples/auto-refetching)
- [Window Refocus Refetching](./examples/focus-refetching)
- [Optimistic Updates](./examples/optimistic-updates)
- [Pagination](./examples/pagination)
- [Load-More & Infinite Scroll](./examples/load-more-infinite-scroll)
- [Suspense CodeSandbox](https://codesandbox.io/s/github/tannerlinsley/react-query/tree/master/examples/suspense)
- [Playground CodeSandbox (with devtools)](https://codesandbox.io/s/github/tannerlinsley/react-query/tree/master/examples/sandbox)
## Sponsors
This library is being built and maintained by me, @tannerlinsley and I am always in need of more support to keep projects like this afloat. If you would like to get premium support, add your logo or name on this README, or simply just contribute to my open source Sponsorship goal, [visit my Github Sponsors page!](https://github.com/sponsors/tannerlinsley/)
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://github.com/sponsors/tannerlinsley" target="_blank">
<img width='150' src="https://raw.githubusercontent.com/tannerlinsley/files/master/images/patreon/diamond.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://github.com/sponsors/tannerlinsley" target="_blank">
Become a Sponsor!
</a>
</td>
</tr>
</tbody>
</table>
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://github.com/sponsors/tannerlinsley/" target="_blank">
<img width='150' src="https://raw.githubusercontent.com/tannerlinsley/files/master/images/patreon/platinum.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://github.com/sponsors/tannerlinsley" target="_blank">
Become a Sponsor!
</a>
</td>
</tr>
</tbody>
</table>
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://github.com/sponsors/tannerlinsley/" target="_blank">
<img width='150' src="https://raw.githubusercontent.com/tannerlinsley/files/master/images/patreon/gold.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://nozzle.io" target="_blank">
<img width='225' src="https://nozzle.io/img/logo-blue.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://github.com/sponsors/tannerlinsley" target="_blank">
Become a Sponsor!
</a>
</td>
</tr>
</tbody>
</table>
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://github.com/sponsors/tannerlinsley/" target="_blank">
<img width='150' src="https://raw.githubusercontent.com/tannerlinsley/files/master/images/patreon/silver.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://www.reactbricks.com/" target="_blank">
<img width='170' src="https://www.reactbricks.com/reactbricks_vertical.svg">
</a>
</td>
<td align="center" valign="middle">
<a href="https://github.com/sponsors/tannerlinsley" target="_blank">
Become a Sponsor!
</a>
</td>
</tr>
</tbody>
</table>
<table>
<tbody>
<tr>
<td valign="top">
<a href="https://github.com/sponsors/tannerlinsley/">
<img width='150' src="https://raw.githubusercontent.com/tannerlinsley/files/master/images/patreon/supporters.png" />
</a>
</td>
<td>
<ul>
<li><a href="https://github.com/bgazzera">@bgazzera<a></li>
</ul>
</td>
<td>
<a href="https://github.com/sponsors/tannerlinsley" target="_blank">
Become a Supporter!
</a>
</td>
</tr>
</tbody>
</table>
<table>
<tbody>
<tr>
<td valign="top">
<a href="https://github.com/sponsors/tannerlinsley/">
<img width='150' src="https://raw.githubusercontent.com/tannerlinsley/files/master/images/patreon/fans.png" />
</a>
</td>
<!-- <td>
<ul>
<li></li>
</ul>
</td> -->
<td>
<a href="https://github.com/sponsors/tannerlinsley" target="_blank">
Become a Fan!
</a>
</td>
</tr>
</tbody>
</table>
# Documentation
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Installation](#installation)
- [Queries](#queries)
- [Query Keys](#query-keys)
- [Query Key Variables](#query-key-variables)
- [Optional Variables](#optional-variables)
- [Using a Query Object instead of parameters](#using-a-query-object-instead-of-parameters)
- [Dependent Queries](#dependent-queries)
- [Caching & Invalidation](#caching--invalidation)
- [Paginated Queries with `usePaginatedQuery`](#paginated-queries-with-usepaginatedquery)
- [Load-More & Infinite-Scroll with `useInfiniteQuery`](#load-more--infinite-scroll-with-useinfinitequery)
- [Scroll Restoration](#scroll-restoration)
- [Manual Querying](#manual-querying)
- [Retries](#retries)
- [Retry Delay](#retry-delay)
- [Prefetching](#prefetching)
- [Initial Data](#initial-data)
- [Initial Data Function](#initial-data-function)
- [Initial Data from Cache](#initial-data-from-cache)
- [SSR & Initial Data](#ssr--initial-data)
- [Suspense Mode](#suspense-mode)
- [Fetch-on-render vs Fetch-as-you-render](#fetch-on-render-vs-fetch-as-you-render)
- [Canceling Query Requests](#canceling-query-requests)
- [Mutations](#mutations)
- [Basic Mutations](#basic-mutations)
- [Mutation Variables](#mutation-variables)
- [Invalidate and Refetch Queries from Mutations](#invalidate-and-refetch-queries-from-mutations)
- [Query Updates from Mutations](#query-updates-from-mutations)
- [Resetting Mutation State](#resetting-mutation-state)
- [Manually or Optimistically Setting Query Data](#manually-or-optimistically-setting-query-data)
- [Displaying Background Fetching Loading States](#displaying-background-fetching-loading-states)
- [Displaying Global Background Fetching Loading State](#displaying-global-background-fetching-loading-state)
- [Window-Focus Refetching](#window-focus-refetching)
- [Custom Window Focus Event](#custom-window-focus-event)
- [Ignoring Iframe Focus Events](#ignoring-iframe-focus-events)
- [Custom Query Key Serializers (Experimental)](#custom-query-key-serializers-experimental)
- [React Query Devtools](#react-query-devtools)
- [API](#api)
- [`useQuery`](#usequery)
- [`usePaginatedQuery`](#usepaginatedquery)
- [`useInfiniteQuery`](#useinfinitequery)
- [`useMutation`](#usemutation)
- [`queryCache`](#querycache)
- [`queryCache.prefetchQuery`](#querycacheprefetchquery)
- [`queryCache.getQueryData`](#querycachegetquerydata)
- [`queryCache.setQueryData`](#querycachesetquerydata)
- [`queryCache.refetchQueries`](#querycacherefetchqueries)
- [`queryCache.removeQueries`](#querycacheremovequeries)
- [`queryCache.getQuery`](#querycachegetquery)
- [`queryCache.getQueries`](#querycachegetqueries)
- [`queryCache.isFetching`](#querycacheisfetching)
- [`queryCache.subscribe`](#querycachesubscribe)
- [`queryCache.clear`](#querycacheclear)
- [`useIsFetching`](#useisfetching)
- [`ReactQueryConfigProvider`](#reactqueryconfigprovider)
- [`setConsole`](#setconsole)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# Installation
```bash
$ npm i --save react-query
# or
$ yarn add react-query
```
# Queries
To make a new query, call the `useQuery` hook with at least:
- A **unique key for the query**
- An **asynchronous function (or similar then-able)** to resolve the data
```js
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:
- It changes when your query should be requested again
- It is consistent across all instances of that specific query in your application
The query `info` returned contains all information about the query and can be easily destructured and used in your component:
```js
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>
)
}
```
## Query Keys
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 URLs 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**.
### String-Only Query Keys
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:
- Generic List/Index resources
- Non-hierarchical resources
```js
// A list of todos
useQuery('todos', ...) // queryKey === ['todos']
// Something else, whatever!
useQuery('somethingSpecial', ...) // queryKey === ['somethingSpecial']
```
### Array Keys
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:
- Queries with additional parameters
- Individual resources
```js
// A list of todos that are "done"
useQuery(['todos', { type: 'done' }], ...) // queryKey === ['todos', { type: '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' } }]
```
### Query Keys are serialized deterministically!
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 }]`:
```js
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!
```js
useQuery(['todos', status, page], ...)
useQuery(['todos', page, status], ...)
useQuery(['todos', undefined, page, status], ...)
```
## Query Key Variables
To use external props, state, or variables in a query function, it's easiest to pass them as 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:
```js
function Todos({ completed }) {
const { status, data, error } = useQuery(
['todos', { status, 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:
```js
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:
```js
function Todo({ todoId }) {
const { status, data, error } = useQuery(['todo', todoId], fetchTodo)
}
```
## Optional Variables
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:
```js
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()
// ...
}
```
## Using a Query Object instead of parameters
Anywhere the `[queryKey, variables, queryFn, config]` options are supported throughout React Query's API, you can also use an object to express the same configuration:
```js
import { useQuery } from 'react-query'
useQuery({
queryKey: ['todo', 7],
queryFn: fetchTodos,
variables: [],
config: {},
})
```
## Dependent Queries
React Query makes it easy to make queries that depend on other queries for both:
- Parallel Queries (avoiding waterfalls) and
- Serial Queries (when a piece of data is required for the next query to happen).
To do this, you can use the following 2 approaches:
### Pass a falsy query key
If a query isn't ready to be requested yet, just pass a falsy value as the query key or as an item in the query key:
```js
// 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 (falsy),
// so the query will not execute while the query key is falsy
user && ['projects', user.id],
getProjectsByUser
)
```
### Use a query key function
If a function is passed, the query will not execute until the function can be called without throwing:
```js
// 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', user.id]
)
```
## Caching & Invalidation
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:
- The cache is keyed on a deterministic hash of your query key.
- By default, query results become **stale** immediately after a successful fetch. This can be configured using the `staleTime` option at both the global and query-level.
- Stale queries are automatically refetched whenever their **query keys change (this includes variables used in query key tuples)**, when they are freshly mounted from not having any instances on the page, or when they are refetched via the query cache manually.
- Though a query result may be stale, query results are by default **always** _cached_ **when in use**.
- If and when a query is no longer being used, it becomes **inactive** and by default is cached in the background for **5 minutes**. This time can be configured using the `cacheTime` option at both the global and query-level.
- After a query is inactive for the `cacheTime` specified (defaults to 5 minutes), the query is deleted and garbage collected.
<details>
<summary>A more detailed example of the caching lifecycle</summary>
Let's assume we are using the default `cacheTime` of **5 minutes** and the default `staleTime` of `0`.
- A new instance of `useQuery('todos', fetchTodos)` mounts.
- Since no other queries have been made with this query + variable combination, this query will show a hard loading state and make a network request to fetch the data.
- It will then cache the data using `'todos'` and `fetchTodos` as the unique identifiers for that cache.
- A stale invalidation is scheduled using the `staleTime` option as a delay (defaults to `0`, or immediately).
- A second instance of `useQuery('todos', fetchTodos)` mounts elsewhere.
- Because this exact data exist in the cache from the first instance of this query, that data is immediately returned from the cache.
- Both instances of the `useQuery('todos', fetchTodos)` query are unmounted and no longer in use.
- Since there are no more active instances to this query, a cache timeout is set using `cacheTime` to delete and garbage collect the query (defaults to **5 minutes**).
- No more instances of `useQuery('todos', fetchTodos)` appear within **5 minutes**.
- This query and its data are deleted and garbage collected.
</details>
## Paginated Queries with `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:
- Instead of `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.
- If you specifically need the data for the exact page being requested, `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.
```js
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(['projects', 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>
)
}
```
## Load-More & Infinite-Scroll with `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 themselves
- A `fetchMore` function is now available
- A `getFetchMore` 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)
- A `canFetchMore` boolean is now available and is `true` if `getFetchMore` returns a truthy value
- An `isFetchingMore` boolean is now available to distinguish between a background refresh state and a loading more state
### Example
Let'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
```js
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:
- Waiting for `useInfiniteQuery` to request the first group of data by default
- Returning the information for the next query in `getFetchMore`
- Calling `fetchMore` function
> Note: It's very important you do not call `fetchMore` with arguments unless you want them to override the `fetchMoreInfo` data returned from the `getFetchMore` function. eg. Do not do this: `<button onClick={fetchMore} />` as this would send the onClick event to the `fetchMore` function.
```js
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>
</>
)
}
```
### What happens when an infinite query needs to be refetched?
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.
### What if I need to pass custom information to my query function?
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:
```js
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)
}
```
## Scroll Restoration
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.
## Manual Querying
If you ever want to disable a query from automatically running, you can use the `manual = true` option. When `manual` is set to true:
- The query will start in the `status === 'success'` state
- The query will not automatically refetch due to changes to their query function or variables.
> Pro Tip #1: Because manual queries start in the `status === 'success'` state, you should consider supplying an `initialData` 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](#dependent-queries) instead!
```js
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>
</>
)}
</>
)
}
```
## Retries
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.
- Setting `retry = false` will disable retries.
- Setting `retry = 6` will retry failing requests 6 times before showing the final error thrown by the function.
- Setting `retry = true` will infinitely retry failing requests.
```js
import { useQuery } from 'react-query'
// Make specific query retry a certain number of times
const { status, data, error } = useQuery(['todos', 1], fetchTodoListPage, {
retry: 10, // Will retry failed requests 10 times before displaying an error
})
```
## Retry Delay
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:
```js
// 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:
```js
const { status, data, error } = useQuery('todos', fetchTodoList, {
retryDelay: 1000, // Will always wait 1000ms to retry, regardless of how many retries
})
```
## Prefetching
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:
```js
import { queryCache } from 'react-query'
const prefetchTodos = async () => {
const queryData = await queryCache.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](#querycachesetquerydata) to directly add or update a query's cached result
## Initial Data
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`:
- The query `status` will initialize as `success` instead of `loading`
- The query's `isStale` property will initialize as `true` instead of false
- The query will not automatically fetch until it is invalidated somehow (eg. window refocus, queryCache refetching, etc)
```js
function Todos() {
const queryInfo = useQuery('todos', () => fetch('/todos'), {
initialData: initialTodos,
})
}
```
## Initial Data Function
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:
```js
function Todos() {
const queryInfo = useQuery('todos', () => fetch('/todos'), {
initialData: () => {
return getExpensiveTodos()
},
})
}
```
## Initial Data from Cache
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:
```js
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 the 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:
```js
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!
},
})
}
```
## SSR & Initial Data
When using SSR (server-side-rendering) with React Query there are a few things to note:
- Query caches are not written to memory during SSR. This is outside of the scope of React Query and easily leads to out-of-sync data when used with frameworks like Next.js or other SSR strategies.
- Queries rendered on the server will by default use the `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:
```js
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:
```js
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.
## Suspense Mode
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:
```js
// Configure for all queries
import { ReactQueryConfigProvider } from 'react-query'
const queryConfig = {
suspense: true,
}
function App() {
return (
<ReactQueryConfigProvider config={queryConfig}>
...
</ReactQueryConfigProvider>
)
}
```
Query configuration:
```js
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](https://codesandbox.io/s/github/tannerlinsley/react-query/tree/master/examples/sandbox) 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!
## Fetch-on-render vs Fetch-as-you-render
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](#prefetching) on routing and/or user interactions events to initialize queries before they are needed.
## Canceling Query Requests
By default, queries that become inactive before their promises are resolved are simply ignored instead of canceled. Why is this?
- For most applications, ignoring out-of-date queries is sufficient.
- Cancellation APIs may not be available for every query function.
- If cancellation APIs are available, they typically vary in implementation between utilities/libraries (eg. Fetch vs Axios vs XMLHttpRequest).
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`:
```js
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`:
```js
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
})
```
# Mutations
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.
## Basic Mutations
Assuming the server implements a ping mutation, that returns "pong" string, here's an example of the most basic mutation:
```js
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.
## Mutation Variables
To pass `variables` to your `mutate` function, call `mutate` with an object.
```js
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](#querycacherefetchqueries) method and the [Query Cache's `setQueryData` method](#querycachesetquerydata), mutations become a very powerful tool.
## Invalidate and Refetch Queries from Mutations
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 local queries with the new data imperatively, React Query helps you to avoid 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:
```js
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`:
```js
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:
```js
const [mutate] = useMutation(addTodo, {
onSuccess: () => {
queryCache.refetchQueries(['todos', { type: 'done' }])
},
})
mutate(todo)
// The query below will be refetched when the mutation above succeeds
const todoListQuery = useQuery(['todos', { type: '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 subkeys, you can pass an `exact: true` option to the `refetchQueries` method:
```js
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', { type: '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 to return `true` or `false` for whether you want to refetch that query:
```js
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:
```js
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:
```js
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 include:
- `onSuccess`
- `onSettled`
- `onError`
- `throwOnError`
```js
const [mutate] = useMutation(addTodo)
mutate(todo, {
onSuccess: () => {},
onSettled: () => {},
onError: () => {},
throwOnError: true,
})
```
## Query Updates from Mutations
When dealing with mutations that **update** objects on the server, it's common for the