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.
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.
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
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.
Frequently Asked Questions
Why is the query key an array?
What is stale-while-revalidate?
Does useQuery fetch on every render?
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.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.