TanStack Query (React Query): Handling HTTP Requests with EaseEntry 3 of 10
How Caching Works: staleTime and gcTime
TanStack Query stores every query result in the QueryClient cache under its key, shows cached data instantly, and refetches in the background when the data is stale.
Notes
Stale-while-revalidate flow:
- A component calls
useQuerywith['events']. - If the cache has data for that key, it's shown immediately.
- If that data is stale,
queryFnruns again in the background. - If the new response differs, the cache updates and the UI re-renders.
staleTime (default 0): how long data counts as fresh.
- While fresh, cached data is reused with no refetch.
- Once stale, the next trigger (component mount, window refocus, reconnect) refetches in the background.
staleTime: 5 * 60 * 1000= no refetch for 5 minutes.
gcTime (default 5 minutes): how long unused data stays in the cache.
- Data is "unused" when no mounted component subscribes to its key.
- After
gcTime, it's garbage-collected; the nextuseQuerystarts withisPendingagain.
| Aspect | staleTime | gcTime |
|---|---|---|
| Controls | When to refetch | When to delete from cache |
| Clock starts | When data was fetched | When the last subscriber unmounts |
| Default | 0 (always stale) | 5 minutes |
What / Why / When
- What
Automatic caching with configurable freshness.
- Why
Navigating back to a page shows data instantly, and duplicate requests are avoided.
- When
Raise
staleTimefor data that rarely changes; lowergcTimefor large or sensitive data.
Code compare
useQuery({ queryKey: ["events", id], queryFn: ({ signal }) => fetchEvent({ id, signal }), staleTime: 5 * 60 * 1000, // fresh for 5 minutes gcTime: 30 * 60 * 1000, // kept 30 minutes after last use});Angular has no built-in shared query cache. A root service with signals provides the same behavior for the data that needs it:
// services/events-cache.service.ts@Injectable({ providedIn: "root" })export class EventsCacheService { private eventsService = inject(EventsService); private cache = new Map<string, { data: EventItem[]; fetchedAt: number }>(); private staleTime = 5 * 60 * 1000; getEvents(params: { search?: string; max?: number } = {}): Observable<EventItem[]> { const key = JSON.stringify(params); const cached = this.cache.get(key); if (cached && Date.now() - cached.fetchedAt < this.staleTime) { return of(cached.data); // fresh: no request } return this.eventsService .getEvents(params) .pipe(tap((data) => this.cache.set(key, { data, fetchedAt: Date.now() }))); } invalidate() { this.cache.clear(); }}Differences and gotchas
- This Angular version covers
staleTimeonly. Data lives as long as the service (nogcTime), and it doesn't show stale data while refetching. Add those only if the app needs them. - TanStack Query also refetches stale data on window focus. Disable with
refetchOnWindowFocus: falseif that's surprising during development.
Quick reference
| Feature | React | Angular |
|---|---|---|
| Shared cache | QueryClient | Root service + Map/signals |
| Freshness | staleTime | Timestamp check in service |
| Cleanup | gcTime | Service lifetime (manual if needed) |
| Instant cached UI | Built in | of(cached) |