React|Server State and Async Data

TanStack Query Lifecycle: The Difference Between staleTime and gcTime

7
TanStack Query Lifecycle: The Difference Between staleTime and gcTime

After adopting TanStack Query, Chris is puzzled.

"It said it handles caching, so why does an API request fire every time I click the window?"

Conversely, in other situations, he gets flustered: "I reopened the tab I closed a while ago, but why isn't there a loading spinner, and why is the old data still there?"

The cause of this confusion is a lack of clear understanding regarding TanStack Query's two core timers: staleTime and gcTime. While they look similar, they play completely different roles: deciding when data is "spoiled" and deciding when to "delete it from memory."

Today, we will perfectly organize the lifecycle of these two concepts, which are the most confusing aspects for React Query beginners.

1. Data Freshness: Fresh vs. Stale

In TanStack Query, data largely exists in two states.

  • Fresh: Hot, newly fetched data. In this state, no matter how many times the component re-renders, it will not send an API request again.
  • Stale: Data past its expiration date. The data exists, but it's in a state where the browser needs to check with the server (Refetch) to see if there is newer data.
  • staleTime: The Expiration Date

    staleTime is the time it takes for data to transition from Fresh to Stale.

    The shocking fact is that TanStack Query's default staleTime is 0ms.

    In other words, the moment data is fetched, it is considered stale. That is why every time Chris focused the window or re-mounted the component, the query thought, "Uh oh, this is stale. I need to fetch it again," and sent an immediate refetch request.

    // useMyQuery.ts
    const { data } = useQuery({
      queryKey: ['todos'],
      queryFn: fetchTodos,
      // Insist that the data is "fresh" for 1 minute.
      // Within 1 minute, no API request is sent even if the component remounts.
      staleTime: 1000 * 60, 
    });

    2. Memory Management: Inactive and gcTime

    So, what is gcTime (formerly cacheTime)? This determines how long data remains in memory when it is not being used (Inactive).

    Let's assume the user navigates away, and the component using useQuery disappears from the screen (Unmount). At this point, the data is not deleted immediately but enters the Inactive state in the cache.

    gcTime: Time to Empty the Trash

    The default value for gcTime is 5 minutes (300,000ms).

  • Component Unmounts -> Data enters Inactive state.
  • Timer starts (5 minutes).
  • If user returns within 5 minutes: Data is resurrected (Reused).
  • If user doesn't return for 5 minutes: Garbage Collector deletes it from memory.
  • The phenomenon Chris experienced—"the data is still there"—occurred because even though the data was spoiled (stale), it hadn't been deleted from memory yet (within gcTime), so the library showed the cached data first.

    3. Lifecycle at a Glance

    Visualizing this process looks like this:

  • Fetch: Data is retrieved.
  • Fresh: Cached data is used during staleTime. (Network Request: ❌)
  • Stale: staleTime has passed. Data is still usable, but it will be refetched in the background the next time it's needed. (Network Request: ⭕)
  • Inactive: All components have unmounted. The cache is preserved.
  • Deleted: gcTime has passed while in the Inactive state. It is completely deleted from memory.
  • 4. Real-World Strategies: How Should You Configure It?

    You need strategies to adjust these two values depending on the situation.

    Strategy 1: Use Defaults (staleTime: 0, gcTime: 5 min)

  • Target: Board post lists, data where real-time accuracy is important.
  • Behavior: Always refreshes when entering the page. However, if the user navigates back quickly, it shows the content instantly (Cache) without loading, while silently updating in the background (Stale-While-Revalidate).
  • Strategy 2: Static Data (staleTime: Infinity, gcTime: Infinity)

  • Target: Configuration info, region lists, content that rarely changes.
  • Behavior: Fetched exactly once while the app is running and never requested again.
  • // static-data.ts
    useQuery({
      queryKey: ['settings'],
      queryFn: fetchSettings,
      staleTime: Infinity, // Forever fresh
      gcTime: Infinity,    // Never deleted from memory
    });

    Strategy 3: Prevent Frequent Updates (staleTime: 30 sec)

  • Target: Stock prices, like counts.
  • Behavior: Even if the user mashes buttons or switches tabs frequently, network requests are blocked for at least 30 seconds to reduce server load.
  • Key Takeaways

  • staleTime (Default 0): Decides "When to refetch." No network requests are sent until this time passes.
  • gcTime (Default 5 min): Decides "When to delete from memory." The cache is deleted if this time passes after the component unmounts.
  • Tip: Starting from v5, cacheTime was renamed to gcTime. Don't get confused.

  • Now you can control the lifespan of your cache. However, if you manage the names (Keys) that store the cache poorly, data can get tangled, or duplicate calls can occur. What is a maintainable key management strategy?

    Continuing in: “Efficient Query Key Management Strategy: Boosting Maintainability with the Factory Pattern”

    🔗 References

  • TanStack Query - Caching Examples
  • Important Defaults
  • TkDodo's Blog - Practical React Query
  • Comments (0)

    0/1000 characters
    Loading comments...