DEV Community

Cover image for React Query's staleTime vs gcTime: the difference that actually bites people
Tarun Sharma
Tarun Sharma

Posted on

React Query's staleTime vs gcTime: the difference that actually bites people

You set up React Query, wire up a useQuery, and everything works. Then a teammate asks: "why did this refetch when I switched tabs and came back, even though nothing changed?" and a week later someone else asks the opposite: "why is this data still showing after the component unmounted and remounted five minutes later?"

Both questions come from the same confusion: mixing up staleTime and gcTime (called cacheTime before React Query v5).

What each one actually controls

staleTime answers: "how long is this data considered fresh, so React Query doesn't bother refetching it automatically?"

gcTime answers a completely different question: "how long should this data stay in memory after nothing is using it anymore, before React Query throws it away?"

They're not two settings for the same knob. One is about refetch behavior while something is actively subscribed to the query. The other is about cleanup after everything unsubscribes.

The default that surprises people

useQuery({
  queryKey: ['user', id],
  queryFn: () => fetchUser(id),
})
Enter fullscreen mode Exit fullscreen mode

With no options, staleTime defaults to 0. That means the instant the query resolves, it's already stale. It won't show a loading spinner again because you still have cached data to render, but React Query will quietly refetch in the background on the next relevant trigger, window focus, remount, reconnect. That's the tab-switch refetch people report as a bug. It isn't one. It's the default working as designed for data that changes outside your app's control.

gcTime defaults to 5 minutes. That's why data can outlive the component that fetched it. Unmount the component, and React Query doesn't delete the cache entry immediately, it waits, because you might navigate back within a few minutes and it can skip the network round trip entirely.

Where this actually bites

A case that comes up a lot: a details page fetches with staleTime: Infinity because the data rarely changes, then the user navigates away and back within a minute, expecting a fresh value because something changed server-side. With staleTime: Infinity, React Query will never consider it stale on its own, no refetch happens, no matter how long you wait. That's not a bug in the library, it's the setting doing exactly what it says. If you want "cache aggressively but still refetch after a while," you want a finite staleTime, not Infinity.

The opposite bites people building anything sensitive, carts, live counts, permissions. Setting a long gcTime doesn't make data go stale slower, it only controls how long unused data survives in memory. If you actually want fewer background refetches, that's staleTime's job, not gcTime's.

A mental model that holds up

Think of staleTime as an expiry date on food you're currently eating, and gcTime as how long you'll keep leftovers in the fridge after you're done. Extending the expiry date doesn't change how long the leftovers last once you stop eating, and keeping leftovers around longer doesn't make the food fresher while you're actively eating it. Same query, two independent lifecycles.

useQuery({
  queryKey: ['user', id],
  queryFn: () => fetchUser(id),
  staleTime: 60 * 1000,      // fresh for 1 minute, no background refetch during that window
  gcTime: 10 * 60 * 1000,    // keep it around 10 minutes after nothing subscribes
})
Enter fullscreen mode Exit fullscreen mode

Once you separate the two questions, "is this refetching too much" and "is this staying in memory too long or too short", picking values stops being guesswork.

Have you shipped a bug that turned out to be one of these two settings confused for the other? What was the giveaway that finally pointed you at the right one?

Top comments (0)