React Query

在 React 中抓取、缓存和更新异步数据的钩子。「⚛️ Hooks for fetching, caching and updating asynchronous data in React」

Github stars Tracking Chart

React Query Header

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 of useMutation'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

These analytics are made available via the awesome Scarf package analytics library

Examples

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!

Diamond Sponsors

Gold Sponsors

Silver Sponsors

Bronze Sponsors

Supporters

  • @bgazzera
  • Kent C. Dodds (kentcdodds.com)

Fans

  • Steven Miyakawa (@SamSamskies)

Become a Sponsor

Documentation

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 than 0 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 than 1000 * 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 and retryDelay options for queries to something other than 3 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' 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.

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.
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

Main metrics

Overview
Name With OwnerTanStack/query
Primary LanguageTypeScript
Program languageJavaScript (Language Count: 6)
PlatformLinux, Mac, Windows
License:MIT License
所有者活动
Created At2019-09-10 19:23:58
Pushed At2025-06-01 00:54:48
Last Commit At
Release Count1197
Last Release Namev4.39.1 (Posted on 2025-05-31 13:10:36)
First Release Namev0.0.3 (Posted on 2019-09-10 14:15:48)
用户参与
Stargazers Count45.4k
Watchers Count209
Fork Count3.2k
Commits Count3.8k
Has Issues Enabled
Issues Count2128
Issue Open Count65
Pull Requests Count2679
Pull Requests Open Count60
Pull Requests Close Count572
项目设置
Has Wiki Enabled
Is Archived
Is Fork
Is Locked
Is Mirror
Is Private