Introduction
TanStack Query's caching system is what makes it dramatically better than manual fetching. Understanding how cache lifetimes, stale times, and garbage collection work allows you to fine-tune the balance between data freshness and network efficiency.
Key Concepts
- staleTime: How long data is considered fresh. While fresh, useQuery returns cached data without refetching. Defaults to 0 (immediately stale).
- gcTime (formerly cacheTime): How long unused cache entries are kept in memory before garbage collection. Defaults to 5 minutes.
- Refetch triggers: TanStack Query can automatically refetch on window focus, network reconnection, and at regular intervals.
Real World Context
A news site has articles that update rarely (staleTime: 10 minutes), stock prices that update constantly (staleTime: 0, refetchInterval: 5 seconds), and user profile data that almost never changes (staleTime: 30 minutes). Each query configures its own caching behavior based on how frequently the underlying data changes.
Deep Dive
The relationship between staleTime and gcTime determines the caching behavior:
```tsx const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 5 * 60 * 1000, // 5 minutes default gcTime: 10 * 60 * 1000, // 10 minutes }, }, });
// Per-query override function StockPrice({ symbol }: { symbol: string }) { const { data } = useQuery({ queryKey: ['stock', symbol], queryFn: () => fetchStockPrice(symbol), staleTime: 0, refetchInterval: 5000, });
return <span>{data?.price}</span>; }
function UserProfile() { const { data } = useQuery({ queryKey: ['user', 'profile'], queryFn: fetchProfile, staleTime: 30 * 60 * 1000, // 30 minutes });
return <h1>{data?.name}</h1>; } ```
Key behaviors to understand:
- Fresh data: When staleTime has not elapsed, navigating to a component that uses this query shows cached data instantly with no network request.
- Stale data: After staleTime elapses, the cached data is still shown immediately, but a background refetch is triggered. Once fresh data arrives, the UI updates seamlessly.
- No data: If the query has never been fetched or was garbage collected, a full loading state is shown while fetching.
Background refetch triggers include window focus (`refetchOnWindowFocus`), mount (`refetchOnMount`), and reconnection (`refetchOnReconnect`). All are configurable.
Common Pitfalls
- Leaving staleTime at 0 for rarely-changing data — Every navigation triggers a refetch even when the data hasn't changed, wasting bandwidth.
- Setting gcTime too low — Cached data is discarded too quickly, causing full loading states when users navigate back.
Best Practices
- Set staleTime based on data change frequency — Use 0 for real-time data, minutes for moderate data, and longer for rarely-changing data.
- Use select to transform data — The `select` option transforms cached data without refetching, useful for deriving computed values from the same query.
Summary
- staleTime controls how long data is considered fresh (no refetch needed).
- gcTime controls how long unused cache entries survive in memory.
- Configure per-query based on how frequently the underlying data changes.
Code Examples
import { useQuery } from '@tanstack/react-query';
// Rarely-changing data: long stale time
function UserSettings() {
const { data } = useQuery({
queryKey: ['settings'],
queryFn: fetchSettings,
staleTime: 30 * 60 * 1000, // 30 min
});
return <SettingsForm defaults={data} />;
}
// Frequently-changing data: short stale time + polling
function LiveScores() {
const { data } = useQuery({
queryKey: ['scores'],
queryFn: fetchScores,
staleTime: 0,
refetchInterval: 5000, // poll every 5s
});
return <Scoreboard scores={data} />;
}