Definition
In a server-state caching library (TanStack Query, etc.), when you want "the like or comment I just wrote to show up on screen immediately," people commonly try to lower staleTime. But that's the wrong knob. staleTime decides how many seconds to trust the data as fresh and hold off on background refetch, while invalidateQueries ignores that staleTime and immediately refetches the relevant queries. The mechanism for instant reflection is not staleTime but invalidate.
Here, stale means the state "marked as outdated," refetch means fetching the data again from the server, and invalidate means the act of stamping the cache as "outdated" to force it to be fetched again.
Why it matters
Symptom scenario: a user presses like, but the screen doesn't change immediately. If you think "then I'll just lower staleTime to 0," now a refetch fires on every window focus and component mount, and server traffic explodes. Freshness and traffic appear to be in conflict.
The correct answer is to separate the roles of the two knobs. Keep staleTime as a freshness SLA (e.g., "reflected within 10 seconds") to suppress background traffic, and invalidate only the relevant queries after a write mutation succeeds so a refetch is forced only at that moment. Then traffic stays quiet normally, and it updates immediately right after my write.
How it works
| Knob | What it controls | Used for instant reflection |
|---|---|---|
staleTime | Whether background/focus/mount refetch happens | No |
invalidateQueries | Immediate refetch now (active) + mark stale (inactive) | Yes |
- Invalidating an active (mounted) query refetches immediately, regardless of staleTime.
- An inactive query is only marked stale and refetches on the next visit.
- Narrow the blast radius with the queryKey prefix you pass to invalidate (scoped invalidation) — so you don't refetch unrelated queries too.
Practical application
If perceived immediacy matters, patch first with an onMutate optimistic update, then reconcile with server truth via invalidate in onSettled.
Trade-offs
- Securing immediacy by lowering staleTime: simple to implement, but traffic rises at every refetch point. At scale the server suffers.
- Keeping staleTime + invalidate after a write: a bit more code, but it satisfies both traffic and immediacy at once. Scoped invalidation also controls the refetch range.
- To also save download bytes, combine with conditional requests (ETag/304) — even if invalidate triggers a refetch, if the content hasn't changed a 304 means the body isn't received.
When not to use this
- Screens where others' changes pour in by the second, like real-time collaboration — subscriptions (WebSocket/Realtime) fit better than invalidate polling.
- Pure read dashboards that don't need write immediacy — if staleTime alone is enough, don't overuse invalidate.
Common mistakes
- Lowering staleTime to 0 because instant reflection doesn't happen, causing a traffic explosion.
- An invalidate scope that's too broad (full
invalidateQueries()), refetching unrelated queries too. - Using only invalidate without an optimistic patch, so a flicker is visible between write→refetch.
- Omitting the onError rollback, so a failed optimistic state stays on screen.
Related concepts
- ssr-prefetch-query-cache-hydration — hydration that fills the query cache on the server and hands it over
- optimistic-outbox-rebase — managing offline/optimistic writes with a queue and converging
- race-safe-async-ui-requests — optimistic rollback and handling request races