Ever fetched data in React and noticed your app keeps hitting the API over and over for the same data? React Query fixes that — but to use it well, you need to understand one key concept: stale time.
First, What Does "Stale" Mean?
Think of it like milk in your fridge.
- Fresh milk = you just bought it, you trust it, you drink it without thinking.
- Stale milk = it's been sitting there for a while, it might still be fine, but you'd probably want to check or get a new one.
React Query treats your fetched data the same way:
- Fresh data = "I just got this, no need to fetch again."
- Stale data = "This might be outdated. I'll refetch it when I get the chance."
Important: stale data is still shown to the user from the cache. React Query doesn't show a loading spinner — it silently refetches in the background and updates the UI only if something changed.
The Default Behavior (staleTime = 0)
By default, staleTime is 0. That means the instant your data is cached, React Query marks it as stale.
So even if you fetched a list of books 1 second ago, React Query thinks: "This could be outdated, let me refetch it."
When Does React Query Actually Refetch Stale Data?
Not randomly! Only on specific triggers:
| Trigger | What happens |
|---|---|
| Window refocus | You switch to another tab, then come back |
| Component mount | A component using this query mounts/remounts |
| Network reconnect | You go offline and come back online |
Common Misconception
"Does moving my mouse trigger a refetch?"
Nope! Only switching away from the tab and coming back (window refocus) does. If you stay on the same tab for 30 minutes, no refetch happens until one of the triggers above occurs.
Setting staleTime: A Real Example
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5 minutes
},
},
});
function App() {
return (
<QueryClientProvider client={queryClient}>
<YourApp />
</QueryClientProvider>
);
}
With this setup:
- User visits the page -> React Query fetches books from the API.
- User switches tabs and comes back 2 minutes later -> Data is still fresh. No refetch. Instant result from cache.
- User switches tabs and comes back 7 minutes later -> Data is now stale. React Query refetches in the background, and if there's new data, the UI updates.
staleTime vs cacheTime (gcTime) — Don't Mix Them Up!
These are two different things:
| staleTime | gcTime (formerly cacheTime) | |
|---|---|---|
| What it controls | How long data is considered fresh | How long unused cached data stays in memory |
| Default |
0 (immediately stale) |
5 minutes |
| What happens after | Data is marked stale, refetched on next trigger | Data is garbage collected (removed from cache entirely) |
Think of it this way:
-
staleTime= "How long should I trust this data?" -
gcTime= "How long should I keep this data in memory after no component is using it?"
Quick Visual Timeline
Fetch happens at 0:00
|
|-- 0:00 to 5:00 --> Data is FRESH (no refetch, served from cache)
|-- 5:00+ --> Data is STALE (refetch on next trigger)
|
|-- If no component uses this data for 5 min --> cache is GARBAGE COLLECTED
Per-Query staleTime
You can also set staleTime on individual queries instead of globally:
// This specific query stays fresh for 10 minutes
const { data } = useQuery({
queryKey: ["books", id],
queryFn: () => fetchBook(id),
staleTime: 1000 * 60 * 10, // 10 minutes
});
This overrides the global default for just this one query.
TL;DR
-
staleTime = 0(default): data is immediately stale, refetched on every trigger. -
staleTime = 1000 * 60 * 5: data is fresh for 5 minutes, no unnecessary API calls during that time. - Stale data is still displayed from cache — refetch happens silently in the background.
- Refetch triggers: window refocus, component mount, network reconnect — NOT mouse movement.
-
staleTime(freshness) andgcTime(cache lifetime) are different things.
Happy coding!
Top comments (1)
Let's Talk About
gcTimeDo Most Devs Change gcTime?
Honestly? Most don't. The default
gcTimeof 5 minutes works well for the majority of apps. The one most devs do configure isstaleTime, because the default of0causes way too many unnecessary refetches.That said, there are real scenarios where tweaking
gcTimemakes a big difference.How to Set gcTime
Just like
staleTime, you can set it globally or per-query:Or per-query:
When Should You Use Each?
Here's a simple guide:
staleTimegcTimestaleTimelow (or default0)gcTimeThe Real-World Scenario That Explains It Best
Imagine a user browsing books:
gcTimecountdown starts.gcTimeis the default 5 minutes and the user comes back after 6 minutes — the cache is gone. Full loading spinner. Fresh fetch.gcTimeto 15 minutes — the cached list is still there. The user sees the list instantly (from cache), and React Query refetches in the background if it's stale.The Golden Rule
Why? If your cache is garbage collected before the stale time is up, the staleTime becomes meaningless — there's no cached data left to be "fresh."
Quick Decision Flowchart
TL;DR
staleTime— fewer touchgcTimebecause the default (5 min) is usually fine.gcTimewhen users navigate between pages and you want cached data to stick around longer.gcTimeif your app has tons of queries and you want to free up memory faster.staleTime <= gcTime, otherwise your cache gets deleted before it even goes stale.staleTimefor how fresh you need data, and leavegcTimealone unless you have a specific reason.