Cache Lifecycle
TanStack Query treats server data as a cacheable resource with a lifecycle, not as component-local state. A query begins when an observer such as a React hook asks for a query key, then moves through fetching, freshness, staleness, inactivity, and eventual garbage collection. Understanding that sequence helps explain why data can appear immediately, why a request may still run in the background, and why an unused result disappears later. This page focuses on the React guide terminology while mapping the behavior to core cache and observer tests that validate the lifecycle events.
Sources: docs/framework/react/guides/caching.md, docs/framework/react/guides/important-defaults.md, packages/query-core/src/tests/queryCache.test.tsx, packages/query-core/src/tests/queryObserver.test.tsx
Purpose and Scope
Use this page when you need to reason about what happens after a query first succeeds. TanStack Query caches successful data under the query key, shares that data with later query instances that use the same key, and updates all observers when the cache entry changes. The important distinction is between having cached data and having fresh data. Cached data can be returned immediately to make the UI responsive, while stale data can still trigger refetching so the application synchronizes with the server.
The default configuration is intentionally active. Query instances start with cached data considered stale unless you choose a positive freshness window. With the default freshness window, a remount can return data from the cache and still launch a background request. With the default garbage collection window, inactive results stay available for a limited time so navigation away and back does not necessarily force a hard loading state. These defaults are useful, but they are also the source of many first-time surprises.
Sources: docs/framework/react/guides/caching.md, docs/framework/react/guides/important-defaults.md
Relevant Source Files
- docs/framework/react/guides/caching.md — Defines the reader-facing cache lifecycle story for query instances, background refetching, inactive queries, garbage collection, and the default example using
['todos']. - docs/framework/react/guides/important-defaults.md — Lists the defaults that shape lifecycle behavior, including staleness, stale-time variants, automatic background refetch triggers, inactive query collection, retries, and structural sharing.
- packages/query-core/src/tests/queryCache.test.tsx — Exercises cache subscription events, stale notifications, query addition notifications, initial data notifications, and a cache-size policy that removes inactive queries.
- packages/query-core/src/tests/queryObserver.test.tsx — Exercises observer subscription behavior, initial pending and fetching state, reading cached data with disabled observers, and disabled-query behavior during invalidation.
Core Lifecycle Terms
A query instance is an active consumer of a cache entry. In React, that consumer is commonly created with a query hook, but the core model is observer based. When the first observer for a key appears and there is no cached data, the result enters a hard loading path and the query function runs. When the request succeeds, the returned value is stored under the key. Another observer with the same key can then read that value immediately, even if it supplies a different function, because the key is the cache identity.
Freshness controls whether a cached result should be trusted without another request. The documented default freshness window is zero, which means data becomes stale immediately after it is stored. A positive freshness window keeps a query fresh until the duration elapses. An infinite freshness window prevents staleness-driven refetching until manual invalidation, while the static freshness mode is stricter and blocks invalidation-driven refetching as well. Choose these settings by asking whether the data can change during the current app session and whether manual invalidation should still be meaningful.
Inactive describes a cached query that currently has no active hook instances or core observers. Inactivity is not deletion. The cache retains the data in case the user returns to a view, another component mounts, or the application needs the result shortly afterward. By default, inactive queries are garbage collected after five minutes. Adjusting the collection time changes retention, not freshness: a long collection time can preserve stale data for fast display, while a short one frees memory sooner and makes remounts more likely to start from an empty cache.
Sources: docs/framework/react/guides/caching.md, docs/framework/react/guides/important-defaults.md
Execution Flow
The canonical lifecycle begins with a new query for a key such as a todos list. Because no data exists yet, the observer reports a pending state and the query function executes. The core observer tests show that this happens even when the function returns synchronously: the value is wrapped through the asynchronous fetching path, so the current result first exposes pending status with a fetching fetch status, then resolves to success with idle fetch status and data. That transition is important for consistent UI state across synchronous and asynchronous functions.
When a second consumer for the same key mounts, it does not create an independent data island. It receives the cached value immediately, then can trigger a background fetch because the default stale-time has already elapsed. The caching guide emphasizes that both query instances receive status updates for the shared key, including fetching and pending-related result values. After the request succeeds, the cache entry is updated and all observers of the key see the new data. This is why query keys should be designed around server-resource identity, not component location.
Unmounting every observer changes the query from active to inactive. At that point, the cache schedules garbage collection using the configured collection time. If a new observer mounts before the timer completes, it can reuse the cached data immediately and start any required background work. If no observer returns before the timeout, the cached data is removed. The practical result is a navigation-friendly cache: quick returns avoid unnecessary hard loading states, but abandoned data does not remain in memory forever.
Sources: docs/framework/react/guides/caching.md, packages/query-core/src/tests/queryObserver.test.tsx
Staleness, Invalidation, and Background Updates
Stale data is eligible for automatic background synchronization. The documented automatic triggers are new query instances mounting, the window being refocused, and the network reconnecting. Those triggers matter only when the query is stale under its stale-time rules. Periodic refetching is a separate mechanism: a refetch interval can run independently of the freshness window. In practice, use stale time as the first lever to reduce unnecessary requests, then customize mount, focus, reconnect, or interval behavior when a screen has more specific timing needs.
Manual invalidation overrides the normal freshness calculation for most queries by marking matching data stale and allowing a refetch policy to run. The important-defaults guide calls out the difference between infinite freshness and static freshness: invalidation can affect a query with infinite stale time, but static freshness is intentionally not refetched even when invalidated or when always-refetch options are configured. This makes static suitable for data that cannot change during the app run, while infinite is better for data that changes only after a known mutation or user action.
Disabled observers add another edge case. The observer tests create a query with a callback-based enabled option that initially returns false, infinite stale time, and an asynchronous function. Subscribing does not fetch, and invalidating with a broad refetch type still does not force that disabled query into a fetch. However, the same observer model can still read existing data from the cache when disabled. That means disabled or lazy queries can participate in display and cache reads without opting into automatic execution until the application explicitly enables or refetches them.
Sources: docs/framework/react/guides/important-defaults.md, packages/query-core/src/tests/queryObserver.test.tsx
Observer and Cache Events
Internally, lifecycle changes are observable through the core cache and observer APIs. The cache tests subscribe to a query cache, add data through the client, and assert that subscribers receive query events such as an added query notification. A stale-time test records a sequence that includes query addition, observer result updates, observer attachment, fetching updates, success updates, and a later observer result update when the query becomes stale. That sequence mirrors the docs story: lifecycle is not just data storage, but a stream of state transitions visible to observers.
Cache subscriptions are also powerful enough to implement policies around the lifecycle. One test creates a separate cache and, whenever a query is added, checks whether the cache holds more than a chosen number of entries. It then finds inactive queries and removes them, excluding the just-added query. This is not the default policy, but it demonstrates the public shape of lifecycle decisions: active queries are protected by their observers, inactive queries are candidates for removal, and cache events give tooling or advanced applications a place to react.
The observer tests also confirm that reading cached data and fetching new data are separate concerns. An observer created with a disabled option can subscribe after data has already been written with the client and immediately report success with the cached data. Conversely, an enabled observer with no data begins by triggering a fetch on subscription. These tests are useful when debugging UI state: a result can be successful because the cache already has data, or pending because no value exists and execution has not completed.
Sources: packages/query-core/src/tests/queryCache.test.tsx, packages/query-core/src/tests/queryObserver.test.tsx
Configuration Reference
| Lifecycle concern | Default or API surface | Practical effect |
|---|---|---|
| Freshness | staleTime: 0 | Cached data is considered stale immediately and may refetch on lifecycle triggers. |
| Fresh window | staleTime: number | Data remains fresh until the configured duration elapses. |
| Manual-only freshness | staleTime: Infinity | Staleness refetching is prevented until invalidation or explicit refetch behavior applies. |
| Static data | staleTime: 'static' | Even invalidation and always-refetch triggers do not refetch the query. |
| Inactive retention | gcTime: 1000 * 60 * 5 | Queries without active observers remain cached for five minutes by default. |
| Background triggers | refetchOnMount, refetchOnWindowFocus, refetchOnReconnect | Stale queries can synchronize when mounted, focused, or reconnected. |
| Polling | refetchInterval | Periodic refetching can run independently of stale time. |
| Data stability | structural sharing | JSON-compatible unchanged data can preserve references for render stability. |
Treat these options as a coordinated lifecycle policy rather than isolated toggles. Increasing freshness reduces automatic requests but can show older data for longer. Increasing collection time improves back-navigation responsiveness but retains more cache entries. Disabling refetch triggers can reduce network work in stable screens, while intervals can keep operational dashboards current even when data would otherwise be fresh. Structural sharing is not a freshness setting, but it affects what observers see after updates by keeping references stable when compatible response data has not actually changed.
Sources: docs/framework/react/guides/important-defaults.md
Testing Signals and Debugging Next Steps
When a cache behavior looks surprising, identify which lifecycle phase you are observing before changing options. If a component shows cached data while also fetching, the likely explanation is stale cached data plus an automatic background trigger. If a component returns to a hard loading state after navigation, the old query may have been garbage collected or may never have succeeded. If invalidation appears ineffective, check whether the query is disabled or uses static freshness. If many components update together, verify that they intentionally share the same query key.
For deeper work, read the query-key and query-options pages next so cache identity and reusable defaults are explicit. Then read background refetching for focus, reconnect, interval, and fetching-indicator patterns, and invalidations from mutations for the write-side lifecycle. The core cache and observers reference is the right follow-up when building tooling, custom framework adapters, or advanced cache policies that need to subscribe to query-cache events directly rather than only consuming hook-level results.