Hooks for fetching, caching and updating asynchronous data in React
Enjoy this library? Try them all! React Table, React Form, 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 + Fetch-As-You-Render Query Prefetching
- Dedicated Devtools (React Query Devtools)
- 4kb - 6kb (depending on features imported)
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
Zeit's 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
useMutation
- A dedicated hook for handling generic lifecycles around triggering mutations and handling their side-effects in applications. SWR does not ship with anything similar, and you may find yourself reimplementing most if not all ofuseMutation
's functionality in user-land. With this hook, you can extend the lifecycle of your mutations to reliably handle successful refetching strategies, failure rollbacks and error handling.- 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 - 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.
- 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/Query-based by nature and much more flexible. All items supplied to the query key array are used to compute the unique key for a query (using a stable and deterministic sorting/hashing implementation). This means you can spend less time thinking about precise key matching, but more importantly, allows you to use partial query-key matching when refetching, updating, or removing queries in mass 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.
Used By
- PayPal
- Amazon
- Walmart
- Microsoft
- Target
- HP
- Major League Baseball Association
- Volvo
- Ocado
- UPC.ch
- EFI.com
- ReactBricks
- Nozzle.io
These analytics are made available via the awesome Scarf package analytics library
Examples
- Basic - CodeSandbox - Source
- Custom Hooks - CodeSandbox - Source
- Auto Refetching / Polling / Realtime - CodeSandbox - Source
- Window Refocus Refetching - CodeSandbox - Source
- Optimistic Updates - CodeSandbox - Source
- Pagination - CodeSandbox - Source
- Load-More & Infinite Scroll - CodeSandbox - Source
- Suspense - CodeSandbox - Source
- Playground (with devtools) - CodeSandbox - Source
- Star Wars (with devtools) - CodeSandbox - Source
- Rick And Morty (with devtools) - CodeSandbox - Source
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!
- @bgazzera
- Kent C. Dodds (kentcdodds.com)
- Steven Miyakawa (@SamSamskies)
Become a Sponsor
Documentation
- Installation
- Defaults to keep in mind
- Queries
- Query Keys
- Query Key Variables
- Optional Variables
- Using a Query Object instead of parameters
- Dependent Queries
- Caching & Invalidation
- Paginated Queries with
usePaginatedQuery
- Load-More & Infinite-Scroll with
useInfiniteQuery
- Scroll Restoration
- Manual Querying
- Retries
- Retry Delay
- Prefetching
- Initial Data
- Initial Data Function
- Initial Data from Cache
- SSR & Initial Data
- Suspense Mode
- Fetch-on-render vs Fetch-as-you-render
- Canceling Query Requests
- Mutations
- Displaying Background Fetching Loading States
- Displaying Global Background Fetching Loading State
- Window-Focus Refetching
- Custom Query Key Serializers (Experimental)
- React Query Devtools
- API
useQuery
usePaginatedQuery
useInfiniteQuery
useMutation
queryCache
queryCache.prefetchQuery
queryCache.getQueryData
queryCache.setQueryData
queryCache.refetchQueries
queryCache.cancelQueries
queryCache.removeQueries
queryCache.getQuery
queryCache.getQueries
queryCache.isFetching
queryCache.subscribe
queryCache.clear
useQueryCache
useIsFetching
ReactQueryConfigProvider
ReactQueryCacheProvider
setConsole
- Contributors ✨
Installation
$ npm i --save react-query
# or
$ yarn add react-query
React Query uses Scarf to collect anonymized installation analytics. These analytics help support the maintainers of this library. However, if you'd like to opt out, you can do so by setting scarfSettings.enabled = false
in your project's package.json
. Alternatively, you can set the environment variable SCARF_ANALYTICS=false
before you install.
Defaults to keep in mind
Out of the box, React Query is configured with aggressive but sane defaults. Sometimes these defaults can catch new users off guard or make learning/debugging difficult if they are unknown by the user. Keep them in mind as you continue to learn and use React Query:
- Query results that are currently rendered on the screen will become "stale" immediately after they are resolved and will be refetched automatically in the background when they are rendered or used again. To change this, you can alter the default
staleTime
for queries to something other than0
milliseconds. - Query results that become unused (all instances of the query are unmounted) will still be cached in case they are used again for a default of 5 minutes before they are garbage collected. To change this, you can alter the default
cacheTime
for queries to something other than1000 * 60 * 5
milliseconds. - Stale queries will automatically be refetched in the background when the browser window is refocused by the user. You can disable this using the
refetchOnWindowFocus
option in queries or the global config. - Queries that fail will silently and automatically be retried 3 times, with exponential backoff delay before capturing and displaying an error to the UI. To change this, you can alter the default
retry
andretryDelay
options for queries to something other than3
and the default exponential backoff function. - Query results by default are deep compared to detect if data has actually changed and if not, the data reference remains unchanged to better help with value stabilization with regards to useMemo and useCallback. The default deep compare function use here (
config.isDataEqual
) only supports comparing JSON-compatible primitives. If you are dealing with any non-json compatible values in your query responses OR are seeing performance issues with the deep compare function, you should probably disable it (config.isDataEqual = () => false
) or customize it to better fit your needs.
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
import { useQuery } from 'react-query'
function App() {
const info = useQuery('todos', fetchTodoList)
}
The unique key you provide is used internally for refetching, caching, deduping related queries.
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)
if (status === 'loading') {
return <span>Loading...</span>
}
if (status === 'error') {
return <span>Error: {error.message}</span>
}
// also status === 'success', but "else" logic works, too
return (
<ul>
{data.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
)
}
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
// 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:
- Specific resources
- It's common to pass an ID, index, or other primitive
- Queries with additional parameters
- It's common to pass an object of additional options
// An individual todo
useQuery(['todo', 5], ...)
// queryKey === ['todo', 5]
// And individual todo in a "preview" format
useQuery(['todo', 5, { preview: true }], ...)
// queryKey === ['todo', 5, { preview: 'true' } }]
// A list of todos that are "done"
useQuery(['todos', { type: 'done' }], ...)
// queryKey === ['todos', { type: 'done' }]
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 }]
:
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], ...)
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:
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:
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)
}
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:
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:
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:
// 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 that throws an exception
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', 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.
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'
andfetchTodos
as the unique identifiers for that cache. - A stale invalidation is scheduled using the
staleTime
option as a delay (defaults to0
, 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).
- Since there are no more active instances to this query, a cache timeout is set using
- No more instances of
useQuery('todos', fetchTodos)
appear within 5 minutes.- This query and its data are deleted and garbage collected.
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 useresolvedData
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 beundefined
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(['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