useQuery Explained: Query Keys, Caching, and Refetching

Learn how useQuery works in TanStack Query. Query keys identify data, the cache reuses results, and refetching keeps stale data fresh.

7 min read

useQuery uses query keys to fetch, cache, and refetch data in a React component. Two components with the same key share one request and one cached value. This guide explains the key, the cache, and the background refetching together.

The query key is the identity

A query key is an array that names a cached value. It is the primary input to useQuery, and it is what the rest of the cache keys on. TanStack Query hashes the array into a stable key, so every component using the same array reads the same data and shares the same request.

App.jsxApp.jsx
useQuery({
  queryKey: ["posts", postId],
  queryFn: () => fetch(`/api/posts/${postId}`).then((res) => res.json()),
});

The key is the array ["posts", postId]. When postId changes, the key changes, so the hook fetches the new post instead of returning the old one.

Keeping the identifier inside the key is what makes the cache reliable, because the cache never guesses what a request was for. The old value stays cached under its old key, so switching back to a previous id can restore it instantly.

How the cache reuses results

The cache lives on the QueryClient, not in the component, so it survives unmounts and re-mounts. When a second component mounts with the same key, it reads the cached value immediately instead of starting a duplicate request.

If the value is still fresh, the second component renders it from memory with no network call. If it is stale, TanStack Query returns the cached data first and refetches in the background. This is stale-while-revalidate, and it is why a list can appear instantly and then update quietly.

The result is that moving between screens feels faster, and the network is not hit twice for the same data. The cache also deduplicates requests that start at the same moment, so a page full of components loading the same list triggers one fetch. The cache also means the provider setup from how to set up TanStack Query in React matters: without a shared client, every component would keep its own copy.

When useQuery refetches

By default, cached data is considered stale the moment it arrives, because staleTime defaults to zero. A stale query refetches when a component mounts, when the window regains focus, and when the network reconnects. You do not call refetch by hand for most of this; the library schedules the work.

App.jsxApp.jsx
useQuery({
  queryKey: ["posts", postId],
  queryFn: fetchPost,
  staleTime: 30 * 1000,
});

Setting staleTime to 30 seconds keeps the value fresh for half a minute, so those refetch triggers are skipped in that window. The isFetching flag reports background refetches, while isPending only reports the first load.

That difference is why a background refresh does not replace the whole screen with a spinner. The fine points between freshness and cache lifetime are in staleTime vs gcTime explained.

Choose good keys

Good keys are stable and specific. Include the parts of the request that change, and leave out the parts that do not. A key that changes shape between renders creates a new cache entry, so keep it consistent.

  • Use an array, not a joined string, so the library can hash values and match prefixes.
  • Put the resource first, then any identifiers or filters.
  • Keep the key serializable, since it is hashed and compared across renders.

A key like ["posts", { status: "draft" }] separates drafts from the full list, while ["posts"] stays the base list. If a key must include an object, keep its properties in a consistent order, because the hash compares the array structurally. This structure becomes important when you update data and need to refresh the affected queries, which is the subject of how to mutate data and invalidate queries with TanStack Query.

Rune AI

Rune AI

Key Insights

  • The query key is the identity of a cached value.
  • Same key means one request and one shared result.
  • Data is stale immediately by default.
  • Stale data refetches in the background, not in a blocking spinner.
  • Include changing request parts in the key.
RunePowered by Rune AI

Frequently Asked Questions

Why is the query key an array?

An array holds ordered, serializable values such as a resource name, an id, and filters. TanStack Query hashes the array to a stable key and can match prefixes like the first element.

What is stale-while-revalidate?

It means the cache serves the previous value immediately, then refetches in the background when the value is stale. Users see data instantly instead of a spinner.

Does useQuery fetch on every render?

No. It fetches when the key changes, when the data becomes stale and a refetch trigger fires, or when you invalidate the query manually. Re-rendering alone does not start a new request.

Conclusion

useQuery identifies data by a query key, stores the result in the shared cache, and refetches stale data in the background. Choose stable, specific keys and tune staleTime so fresh data is not refetched unnecessarily.