TanStack Query's cache answers two independent questions about any data: is it stale, and is it still cached at all? This lesson covers staleTime, gcTime, and how to manually force a refetch with invalidateQueries using hierarchical query keys.
1Fresh vs. Stale: Two Different Questions
TanStack Query's cache separately tracks whether data is stale ā should it be refetched given the opportunity ā and whether it's still present in the cache at all, since unused data eventually gets garbage collected. Treating these as two independent settings is essential to configuring caching correctly.
2staleTime: When Cached Data Needs Refreshing
staleTime defaults to 0, meaning data is considered stale immediately after fetching, causing aggressive refetching on triggers like mount or window focus. Setting a longer staleTime tells the library the data can be trusted as fresh for that duration, skipping refetch triggers during that window.
3gcTime: When Unused Data Is Discarded
gcTime controls how long data with no currently-subscribed component (inactive data) remains in memory before being fully removed from the cache, defaulting to 5 minutes. This is why navigating back to a recently viewed page can show cached data instantly, without waiting on a fresh network request.
4Manual Invalidation with invalidateQueries
When application code knows data has changed ā such as right after a successful save ā calling queryClient.invalidateQueries marks matching queries as stale and triggers an immediate refetch for any currently active ones, regardless of the configured staleTime.
5Precise Invalidation with Query Key Matching
invalidateQueries matches query keys by prefix rather than exact equality, so invalidating ['user'] affects every query starting with that prefix, like ['user', 1] and ['user', 2]. Structuring query keys hierarchically, from general to specific, gives precise control over exactly what gets invalidated.
6Step-by-Step Breakdown
Fresh vs. Stale: Two Different Questions. TanStack Query's cache answers two separate questions about any piece of data: is it stale (should it be refetched given the chance?) and is it still in the cache at all (garbage collected or not?). Understanding these as two independent settings is the key to configuring caching correctly.
staleTime: When Cached Data Needs Refreshing. By default, staleTime is 0 ā data is considered stale the instant it's fetched, so TanStack Query refetches aggressively on every trigger (mount, focus, reconnect). Setting staleTime: 60000 tells it 'this data is good for 60 seconds; don't bother refetching it during that window even if a trigger fires.'
What does staleTime: 60000 tell TanStack Query about a query's data?
- āThe data is considered fresh for 60 seconds and won't be refetched during that window
- āThe data is deleted from the cache after 60 seconds
gcTime: When Unused Data Is Discarded. gcTime (garbage collection time, default 5 minutes) controls how long INACTIVE cached data ā data with no component currently subscribed to it ā stays in memory before being fully removed. This is why navigating back to a recently-viewed page often shows data instantly, even without a fresh network request.
Manual Invalidation with invalidateQueries. Sometimes you know data changed ā say, right after a successful save ā and want to force a refetch immediately, regardless of staleTime. Calling queryClient.invalidateQueries({ queryKey: [...] }) marks matching queries as stale and triggers an immediate refetch for any of them currently in use.
After a user successfully updates their profile, what's the correct way to make the UI reflect the new data immediately?
- āCall queryClient.invalidateQueries() to force a fresh refetch
- āReload the entire page with window.location.reload()
Precise Invalidation with Query Key Matching. invalidateQueries matches queryKeys by prefix, not exact equality ā queryKey: ['user'] invalidates every query starting with 'user', including ['user', 1] and ['user', 2]. Structuring queryKeys hierarchically, from general to specific, gives you precise control over exactly what gets invalidated.
Mastery Achieved. You now understand TanStack Query's caching model: staleTime for refetch aggressiveness, gcTime for how long unused data survives in memory, and invalidateQueries for forcing an immediate refetch after a known data change, using hierarchical query keys for precise control. Next, you'll learn Mutations for writing data back to the server.
Level Up š
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Cache-Served Instant Data Still Needs a Refresh Indicator If Refetching
When TanStack Query shows cached data instantly while silently refetching in the background (isFetching without isLoading), consider a subtle, non-intrusive indicator so users relying on assistive technology understand data may still be updating.
SEO Implications
- 1
Caching Configuration Doesn't Affect Server-Rendered Content
staleTime and gcTime govern client-side cache behavior after hydration; they have no bearing on what's present in server-rendered HTML seen by crawlers.
Best Practices
Set staleTime Based on How Often the Data Actually Changes
Data that rarely changes (like a list of countries) can have a long staleTime to avoid unnecessary refetches; frequently changing data (like a live order status) should keep a short or zero staleTime.
Invalidate the Narrowest Query Key That's Actually Affected
Prefer invalidating ['user', userId] over the broader ['user'] when only one user's data changed, to avoid unnecessarily refetching every cached user query.
Frequent Bugs
After successfully saving an edit, the UI still shows the old, stale data.
The mutation succeeded, but nothing told TanStack Query the related query is now stale. Call queryClient.invalidateQueries with the affected queryKey after the mutation resolves to force a refetch.
A rarely-changing dataset refetches far more often than expected, causing unnecessary network load.
The query is likely using the default staleTime of 0, causing a refetch on every mount or window focus. Set an appropriate staleTime matching how often the underlying data actually changes.
Real-World Examples
Invalidating a User's Query After a Profile Update
A settings form updates a user's profile via a mutation. After the mutation succeeds, calling queryClient.invalidateQueries({ queryKey: ['user', userId] }) ensures every component displaying that user's data ā the header avatar, the settings page itself ā refetches and shows the updated information immediately.
async function handleSave(updatedProfile) {
await saveProfile(updatedProfile);
queryClient.invalidateQueries({ queryKey: ['user', userId] });
}