DEV Community

Mohamed Idris
Mohamed Idris

Posted on

React Query: What Is `staleTime` and Why Should You Care?

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

With this setup:

  1. User visits the page -> React Query fetches books from the API.
  2. User switches tabs and comes back 2 minutes later -> Data is still fresh. No refetch. Instant result from cache.
  3. 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
Enter fullscreen mode Exit fullscreen mode

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
});
Enter fullscreen mode Exit fullscreen mode

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) and gcTime (cache lifetime) are different things.

Happy coding!

Top comments (1)

Collapse
 
edriso profile image
Mohamed Idris

Let's Talk About gcTime

Do Most Devs Change gcTime?

Honestly? Most don't. The default gcTime of 5 minutes works well for the majority of apps. The one most devs do configure is staleTime, because the default of 0 causes way too many unnecessary refetches.

That said, there are real scenarios where tweaking gcTime makes a big difference.

How to Set gcTime

Just like staleTime, you can set it globally or per-query:

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60 * 5,  // fresh for 5 min
      gcTime: 1000 * 60 * 10,    // keep unused cache for 10 min
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

Or per-query:

const { data } = useQuery({
  queryKey: ['books', id],
  queryFn: () => fetchBook(id),
  gcTime: 1000 * 60 * 30, // keep this one cached for 30 min
});
Enter fullscreen mode Exit fullscreen mode

When Should You Use Each?

Here's a simple guide:

Scenario What to adjust Example
Too many API calls for data that doesn't change often Increase staleTime A list of countries, categories, or menu items
User navigates back and forth between pages and sees a loading spinner every time Increase gcTime User goes from book list -> detail -> back to list, and sees a spinner again
Data changes frequently and must be up-to-date Keep staleTime low (or default 0) Live chat messages, stock prices, notifications
App uses a lot of memory and you want to clean up old data faster Decrease gcTime A large dashboard with hundreds of different queries
Data almost never changes Set both high App config, feature flags, static content

The Real-World Scenario That Explains It Best

Imagine a user browsing books:

  1. They visit the book list page — data is fetched and cached.
  2. They click on a single book — they navigate away from the list.
  3. Now no component is using the list query — gcTime countdown starts.
  4. If gcTime is the default 5 minutes and the user comes back after 6 minutes — the cache is gone. Full loading spinner. Fresh fetch.
  5. If you set gcTime to 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

staleTime should always be less than or equal to gcTime.

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."

gcTime = 5 min, staleTime = 10 min  -->  BAD: cache deleted before it even goes stale
gcTime = 10 min, staleTime = 5 min  -->  GOOD: data is fresh for 5 min, then stale but still cached for another 5 min
Enter fullscreen mode Exit fullscreen mode

Quick Decision Flowchart

Do you have too many unnecessary API calls?
  └─ YES → increase staleTime

Do users see loading spinners when navigating back to a page?
  └─ YES → increase gcTime

Is your data real-time / frequently changing?
  └─ YES → keep staleTime low, keep gcTime default

Is your app using too much memory?
  └─ YES → decrease gcTime
Enter fullscreen mode Exit fullscreen mode

TL;DR

  • Most devs adjust staleTime — fewer touch gcTime because the default (5 min) is usually fine.
  • Increase gcTime when users navigate between pages and you want cached data to stick around longer.
  • Decrease gcTime if your app has tons of queries and you want to free up memory faster.
  • Always keep staleTime <= gcTime, otherwise your cache gets deleted before it even goes stale.
  • When in doubt: set staleTime for how fresh you need data, and leave gcTime alone unless you have a specific reason.