添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
Skip to content

Inside React Query

ReactJs , React Query , TypeScript , JavaScript 4 min read

I've been asked a lot lately how React Query works internally. How does it know when to re-render? How does it de-duplicate things? How come it's framework-agnostic?

These are all very good questions - so let's take a look under the hood of our beloved async state management library and analyze what really happens when you call useQuery .

To understand the architecture, we have to start at the beginning:

The QueryClient

It all starts with a QueryClient . That's the class you create an instance of, likely at the start of your application, and then make available everywhere via the QueryClientProvider :

query-client-provider
1import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
2
3// ⬇️ this creates the client
4const queryClient = new QueryClient()
5
6function App() {
7 return (
8 // ⬇️ this distributes the client
9 <QueryClientProvider client={queryClient}>
10 <RestOfYourApp />
11 </QueryClientProvider>
12 )
13}

The QueryClientProvider uses React Context to distribute the QueryClient throughout the entire application. The client itself is a stable value - it's created once (make sure you don't inadvertently re-create it too often ), so this is a perfect case for using Context. It will not make your app re-render - it just gives you access to this client via useQueryClient .

A vessel that holds the cache

It might not be well known, but the QueryClient itself doesn't really do much. It's a container for the QueryCache and the MutationCache , which are automatically created when you create a new QueryClient .

It also holds some defaults that you can set for all your queries and mutations, and it provides convenience methods to work with the caches. In most situations, you will not interact with the cache directly - you will access it through the QueryClient .

QueryCache

Alright, so the client lets us work with the cache - what is the cache?

Simply put - the QueryCache is an in-memory object where the keys are a stably serialized version of your queryKeys (called a queryKeyHash ) and the values are an instance of the Query class.

I think it's important to understand that React Query, per default, only stores data in-memory and nowhere else. If you reload your browser page, the cache is gone. Have a look at the persisters if you want to write the cache to an external storage like localstorage.

Query

The cache has queries, and a Query is where most of the logic is happening. It not only contains all the information about a query (its data, status field or meta information like when the last fetch happened), it also executes the query function and contains the retry, cancellation and de-duplication logic.

It has an internal state machine to make sure we don't wind up in impossible states. For example, if a query function should be triggered while we are already fetching, that fetch can be de-duplicated. If a query is cancelled, it goes back to its previous state.

Most importantly, the query knows who's interested in the query data, and it can inform those Observers about all changes.

QueryObserver

Observers are the glue between the Query and the components that want to use it. An Observer is created when you call useQuery , and it is always subscribed to exactly one query. That's why you have to pass a queryKey to useQuery . 😉

The Observer does a bit more though - it's where most of the optimizations happen. The Observer knows which properties of the Query a component is using, so it doesn't have to notify it of unrelated changes. As an example, if you only use the data field, the component doesn't have to re-render if isFetching is changing on a background refetch.

Even more - each Observer can have a select option, where you can decide which parts of the data field you are interested in. I've written about this optimization before in #2: React Query Data Transformations . Most of the timers, like ones for staleTime or interval fetching, also happen on the observer-level.

Active and inactive Queries

A Query without an Observer is called an inactive query. It's still in the cache, but it's not being used by any component. If you take a look at the React Query Devtools, you will see that inactive queries are greyed out. The number on the left side indicates the number of Observers that are subscribed to the query.

The complete picture