ποΈ 08062026 1400
Two knobs control how tanstack_query manages cached data. Getting them right is the difference between "why does it keep refetching?" and "why is my data gone?"
What Each Controlsβ
- staleTime β how long data is considered fresh after fetching. While fresh, no background refetch happens, even if a new component mounts requesting the same data
- gcTime (garbage collection time) β how long unused cache entries stay in memory after their last subscriber unmounts. After this window, the entry is deleted entirely
They answer different questions:
- staleTime: "Should I refetch this?"
- gcTime: "Should I keep this in memory?"
Cache Entry Lifecycleβ
fetch completes
β
βΌ
βββββββββββ staleTime expires βββββββββββ last subscriber ββββββββββββ gcTime expires βββββββββββββ
β FRESH β ββββββββββββββββββΊ β STALE β unmounts β INACTIVE β ββββββββββββββββΊ β DELETED β
β β β β βββββββββββββββββββΊ β β β β
β no β β refetch β β no β β gone from β
β refetch β β on next β β observersβ β memory β
β β β trigger β β β β β
βββββββββββ βββββββββββ ββββββββββββ βββββββββββββ
- Fresh β served from cache, no refetch triggered
- Stale β still served from cache instantly, but a background refetch fires on the next trigger (mount, window focus, reconnect)
- Inactive β no component is reading this data; cache entry lives on borrowed time (gcTime countdown starts)
- Deleted β gone; next request starts from scratch with a loading state
Defaults and Why They Existβ
| Option | Default | Rationale |
|---|---|---|
staleTime | 0 | Data is stale immediately β guarantees freshness at the cost of more network requests |
gcTime | 5 minutes | Keeps recently viewed data around for quick back-navigation without being a memory leak |
The staleTime: 0 default surprises people. It means every component mount triggers a background refetch β the UI still shows cached data instantly, but there's always a network request behind it. This is the stale-while-revalidate strategy in tanstack_query.
Common Configurationsβ
"Refetch freely" (default)β
staleTime: 0 // always refetch in background
gcTime: 5 * 60_000 // keep unused data 5 min
Good for: real-time dashboards, frequently changing data.
"Cache for a bit"β
staleTime: 60_000 // fresh for 1 minute
gcTime: 5 * 60_000 // keep unused data 5 min
Good for: most CRUD apps. Reduces network chatter without showing stale data for long.
"Rarely changes"β
staleTime: Infinity // never auto-refetch
gcTime: Infinity // never garbage collect
Good for: reference data (country lists, enum mappings, feature flags). Only refreshes on explicit invalidation.
How They Interactβ
staleTime > gcTimeis pointless β data gets deleted before it would have been refetchedgcTime: 0means data is deleted the instant a component unmounts β navigating back always shows a loading spinnerstaleTime: Infinitystill respects manualinvalidateQueries()β the data becomes stale on demand
SSR gotcha: don't set gcTime below 2000 (2 seconds) in SSR apps. The server-rendered data needs to survive long enough for the client to hydrate and reference it. See tanstack_query_ssr_hydration for the full pattern.
Setting Defaultsβ
Configure once in QueryClient, override per-query when needed:
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60_000,
gcTime: 5 * 60_000,
},
},
})
Per-query override (see tanstack_query_cheatsheet for full API):
useQuery({
queryKey: ['countries'],
queryFn: fetchCountries,
staleTime: Infinity, // overrides default
})