Cache and Providers

Purpose and Scope

SWR uses a cache to make React data fetching feel immediate: a hook can render previously known data, then revalidate and publish the updated value to every interested component. In this repository, that behavior is not only a user-facing feature but also a configurable boundary. Applications can use the default global cache, replace it with a custom provider, or create nested providers that intentionally isolate state. Understanding that boundary helps you decide whether a piece of data should be shared across the whole app, scoped to a subtree, seeded during render, or mutated locally before the next network response arrives.

Sources: test/use-swr-cache.test.tsx, src/_internal/utils/config.ts

A cache provider is the storage object SWR reads from and writes to for serialized keys. The tests exercise providers backed by JavaScript Map instances, including maps preloaded with entries such as { data: 'cached value' }. When a component calls useSWR with a key, the hook can read the cached entry first, render it, and still run the fetcher so the cache advances to a fresher value. This is the practical meaning of SWR's stale-while-revalidate model at the provider layer: cached state is useful immediately, but it is not necessarily final.

Sources: test/use-swr-cache.test.tsx

This page covers the source-backed mechanics that matter when designing cache boundaries: the default cache, custom providers, nested provider isolation, initial cached values, the scoped mutate returned from useSWRConfig, and the internal structures SWR keeps alongside the cache. It does not try to document every revalidation trigger, but it explains how provider identity affects where those triggers store results and which components observe them. For local state sharing, the official example describes SWR as a way to share state between React components; the same provider rules determine whether that sharing is global or scoped.

Relevant Source Files

  • test/use-swr-cache.test.tsx — exercises custom providers, initial cache reads, scoped mutation, nested providers, isolated cache trees, and provider-aware behavior under revalidation options.
  • test/use-swr-context-config.test.tsx — validates that global mutation before mount can hydrate later hooks and that useSWRConfig keeps a stable reference across re-renders.
  • src/_internal/types.ts — defines the public and internal cache-related types, including CacheData, GlobalState, fetch cache structures, preload cache structures, scoped mutator, setter, and subscriber functions.
  • src/_internal/utils/config.ts — constructs the default cache provider with initCache(new Map()), exports the default mutate, and includes default configuration fields such as cache, mutate, fallback, and compare.

Core Cache Model

The default configuration creates SWR's process-wide cache by calling initCache(new Map()) and extracting both a cache object and a scoped mutate function. Those values are placed into defaultConfig alongside defaults for callbacks, revalidation switches, retry timing, comparison, pause handling, and fallback. The important design point is that cache storage and mutation are paired: the mutator is scoped to the cache it was created with. When a provider changes the cache for a subtree, components in that subtree should use the corresponding configuration rather than assuming the package-level mutator always targets the same storage.

Sources: src/_internal/utils/config.ts

Internally, SWR keeps more than user data in memory. The GlobalState tuple maps cache keys to event revalidators, mutation timestamps, fetch promises or values with timestamps, preload responses, a scoped mutator, a setter that compares previous and current values, and a subscriber registration function. That structure explains why a provider is not just a passive dictionary. It is the anchor for coordination: deduplication, mutation ordering, revalidation callbacks, preload consumption, and subscription-style updates all need to agree on the same key space and the same cache instance.

Sources: src/_internal/types.ts

The public CacheData type describes cache data produced on the server and consumed by SWRConfig on the client as an object whose keys map to fetcher responses. That type is separate from the provider tests that seed Map entries with full cache records, but both ideas serve the same reader problem: initial data can be supplied before the first client fetch completes. When initial data exists, hooks can render useful content immediately and then let normal SWR revalidation update the cache entry if a fetcher returns newer data.

Sources: src/_internal/types.ts, test/use-swr-cache.test.tsx

Provider Scoping and Isolation

SWRConfig can receive a provider function, and the tests show that passing provider: () => provider lets the caller inspect and control the backing map. In the cache update test, a component begins with one key, fetches data into the provider, then switches to another key. The assertions check that the first and second cache entries are stored under their own keys after the component changes state. That behavior is essential for dynamic UIs: the provider stores records by request identity rather than by component instance.

Sources: test/use-swr-cache.test.tsx

A provider can also start with data already present. One test renders a hook with a map containing the target key and { data: 'cached value' }. The component first renders the cached value, then the fetcher resolves to updated value, producing a second render. This demonstrates the normal stale path: preloaded cache is not treated as an instruction to skip all work. Instead, it gives the UI an immediate value while SWR continues toward the latest fetch result according to the active configuration.

Sources: test/use-swr-cache.test.tsx

Nested providers create explicit cache boundaries. In the multi-level cache test, an outer SWRConfig provides a map where the key resolves to 1, while an inner SWRConfig provides a different map where the same key resolves to 2. The rendered output is 1:2, proving that identical keys can intentionally have different values when they are resolved against different providers. This is useful for tests, embedded widgets, previews, or multi-tenant surfaces where a subtree must not read or mutate the surrounding application's cache.

Sources: test/use-swr-cache.test.tsx

The isolated cache test makes the boundary even clearer by rendering two sibling SWRConfig providers, each with its own map and the same key. The same child hook reads 1 in one subtree and 2 in the other. For application authors, this means provider placement is a state architecture decision. Put a provider high in the tree when many components should share remote state. Put a provider around a feature, test case, or local state area when you need predictable isolation and want mutations in one subtree to avoid affecting another.

Sources: test/use-swr-cache.test.tsx

Mutation and Configuration Flow

useSWRConfig returns the configuration for the current provider scope, including the scoped mutate. In the cache test, a component reads mutate from useSWRConfig, renders data from an initially seeded map, then calls mutate(key, 'mutated value', false). The UI updates to the mutated value without requiring a fetcher. The third argument disables revalidation in that test, so the mutation behaves as a direct cache write. This pattern is the foundation for local updates, optimistic UI, and state sharing between components that use the same provider.

Sources: test/use-swr-cache.test.tsx

The context configuration tests add two important guarantees. First, calling the exported mutate before a component mounts can place prefetch data into the global cache; when the component later calls useSWR with the same key, it renders the prefetched data and then updates to the fetcher's response. Second, useSWRConfig maintains a stable reference across ordinary child re-renders when the parent configuration object is stable. Stability matters because many components put configuration-derived values into effects or callbacks; unnecessary reference changes would produce avoidable effects and confusing render behavior.

Sources: test/use-swr-context-config.test.tsx

Provider scope determines which mutate function you should call. The package-level mutate is suitable for the default global cache, including prefetch-before-mount flows validated by the tests. Inside a custom provider, prefer the mutate returned by useSWRConfig so the write targets the same cache the hook is reading. If you mix a global mutate call with a hook that is under an isolated provider, the two operations may refer to different storage boundaries even if the key string is identical. The tests around nested and isolated caches are the practical warning sign for that scenario.

Sources: test/use-swr-cache.test.tsx, test/use-swr-context-config.test.tsx

Compact Reference

ConceptSource-backed contractWhen to use it
Default cachedefaultConfig includes cache and mutate produced by initCache(new Map()).Use when application-wide sharing is desired and no custom provider is supplied.
Custom providerSWRConfig accepts provider: () => new Map(...) in tests.Use for tests, feature-local state, or alternate storage boundaries.
Initial cache entryA provider map can contain [key, { data: value }] before render.Use to show cached or server-provided data before revalidation completes.
Scoped mutateuseSWRConfig() exposes the mutator for the current cache scope.Use inside provider subtrees to update the cache the hooks actually read.
Global mutate before mountThe exported mutate can prefill global cache before a hook mounts.Use for prefetching flows that rely on the default global cache.
Isolated providersSibling or nested providers can return different data for the same key.Use when identical keys must not share state across subtrees.
Internal global stateGlobalState includes revalidators, mutation timestamps, fetch cache, preload cache, setter, and subscriber.Use as implementation context when debugging coordination behavior.

Sources: src/_internal/types.ts, src/_internal/utils/config.ts, test/use-swr-cache.test.tsx, test/use-swr-context-config.test.tsx

Practical Guidance

Choose the smallest provider scope that matches the sharing requirement. A profile menu, dashboard header, and account settings page probably benefit from the same global user cache. A storybook fixture, unit test, checkout preview, or embedded customer workspace may need its own SWRConfig provider so that keys do not collide with the rest of the app. The tests prove that provider identity, not just key identity, determines the observed value, so moving a provider in the React tree can change which components share state.

When seeding data, decide whether the value is a temporary cache value or a fallback for a known rendering boundary. A pre-populated provider entry can render immediately and still be replaced by a later fetcher response. A global prefetch with mutate can prepare the default cache before mount. In either case, treat the key as the contract between the preparation step and the consuming hook. If the key differs after serialization or if the hook lives under a different provider, the prepared data will not be the value that component observes.

For local state sharing, SWR's cache can act as a lightweight shared store between React components, as the repository example describes. The same caveats apply as with remote data: use stable keys, keep the provider boundary intentional, and update through the scoped mutator when operating under custom configuration. This lets you share local UI state without introducing a separate store, while still benefiting from SWR's subscriber model and render updates.

Next Steps

Read SWRConfig next if you need to configure providers, fallbacks, callbacks, or global fetchers consistently across an application. Read mutate when you need direct cache writes, optimistic updates, or prefetch-before-mount flows. Read Keys and Serialization before designing provider-level state conventions, because provider isolation only solves one part of cache identity; each value still needs a stable key that all participating hooks and mutation calls agree on. For examples, the local state sharing recipe shows the product-level idea, while the cache provider tests define the source-backed behavior that keeps those examples predictable.