With SWR, a profile header and a settings page both call useSWR('/api/user', fetcher); how many requests go out, and how do they share data?
answer
- the key names one cache entry
- both hooks subscribe to it
- a two-second dedupe window
- cached data first, then revalidate
- isLoading versus isValidating
basics
~20 sBoth hooks share the one cache entry named by '/api/user'. Mounted together they send one request, because SWR dedupes revalidations of a key within dedupingInterval (2000 ms). A later mount shows the cached user at once and revalidates in the background.
solid answer
~40 sIn SWR the key is the cache identity, so both components read and subscribe to the same entry in the cache provider. When they mount together, the first hook starts the request and the second sees it already in flight and does not start another. SWR also keeps that request registered for `dedupingInterval` (2000 ms by default) after it resolves, so revalidations of the key inside that window reuse it. Both hooks get the same `data` and re-render together. If the settings page mounts later, outside the window, it renders the cached user immediately with `isLoading` false, and revalidates in the background with `isValidating` true, because `revalidateIfStale` defaults to `true`. Anything that updates the key, such as a `mutate` after the user saves, updates both components at once.
code
tsx · 30 linesimport useSWR from 'swr'
type User = { id: string; name: string; avatarUrl: string }
const fetcher = async (url: string): Promise<User> => {
const res = await fetch(url)
if (!res.ok) throw new Error('Failed to load user')
return res.json()
}
export function useUser() {
return useSWR('/api/user', fetcher)
}
export function HeaderAvatar() {
const { data: user, isLoading } = useUser()
if (isLoading) return <span className="avatar-skeleton" />
return <img src={user?.avatarUrl} alt={user?.name ?? ''} />
}
export function SettingsPage() {
const { data: user, isLoading, isValidating } = useUser()
if (isLoading) return <p>Loading settings…</p>
return (
<section>
<h1>Settings for {user?.name}</h1>
{isValidating ? <small>Refreshing…</small> : null}
</section>
)
}go deeper
Know that the key identifies one shared cache entry, that components using the same key share data and requests, and that isLoading means there is no data yet.
Explain deduplication with dedupingInterval, why a later mount shows cached data and revalidates because revalidateIfStale is true, and how isLoading differs from isValidating.
Spot the production traps: near-identical key strings, one key served by two fetchers, and treating dedupingInterval as a freshness window. Centralize keys in custom hooks.
Decide how the app names and owns keys, with one hook per resource, so that request volume and cache consistency stay predictable as teams add components.
## One key, one cache entry `useSWR(key, fetcher)` is SWR's data hook. The **key**, here the string `'/api/user'`, does two jobs. It is the **identity of a cache entry**, and it is the **argument passed to the fetcher**. Every hook in the app that uses the same key, under the same cache provider, reads the same entry and **subscribes** to it. When the entry changes, every subscribed component re-renders with the new value. There is no per-component copy of the data. ## Mounting together: deduplication On the settings route, the header and the settings page mount in the same commit. SWR handles them like this: 1. The first hook finds no cached data for `'/api/user'`, so it starts a revalidation, calls the fetcher and records the request for that key. 2. The second hook mounts, sees a request for the key already registered, and skips its own mount revalidation. 3. The response arrives and is written once to the cache entry. Both hooks are subscribed, so both re-render with the user. 4. SWR leaves the request registered for **`dedupingInterval`**, **2000 ms** by default, after it resolves. A mount, focus or reconnect revalidation for the same key inside that window reuses the finished request instead of sending a new one. The result is **one** request, however many components ask for the key at once. ## Mounting later: cached data, then revalidation If the user opens the settings page a minute after the header loaded, the window has long passed: - The settings page renders **immediately with the cached user**. That is the stale-while-revalidate behaviour SWR is named after. - Because **`revalidateIfStale`** defaults to `true`, it also starts a background revalidation on mount. - When the new response arrives, the entry updates and **both** the header and the settings page re-render, even though only one of them triggered the request. ## isLoading versus isValidating SWR exposes two loading flags, and the difference matters here: | Situation | `data` | `isLoading` | `isValidating` | |---|---|---|---| | First mount, nothing cached, request running | `undefined` | `true` | `true` | | Later mount, cached user shown, revalidating | cached user | `false` | `true` | | Settled, nothing running | user | `false` | `false` | | Key is `null`, so nothing is fetched | `undefined` | `false` | `false` | Use `isLoading` for skeletons, because it is true only while there is no loaded data. Use `isValidating` for a subtle "refreshing" indicator, if you show one at all. ## What updates both components - A **bound `mutate`** from either hook, or the **global `mutate('/api/user', …)`** from `useSWRConfig()`, writes the entry, and every subscriber re-renders. - **Focus** and **reconnect** revalidations run per mounted hook, but they are deduplicated per key just like mount revalidations. - **Polling** with `refreshInterval` is off by default (`0`). ## Options are per hook, data is per key Each `useSWR` call carries its **own options**, but the **entry is shared**. That combination surprises people: - If the header uses `useSWRImmutable('/api/user', fetcher)` and the settings page uses plain `useSWR`, the settings page still revalidates on mount, and the header re-renders with the result, even though the header itself never revalidates. - A `refreshInterval` on one hook polls the shared entry, so every component on that key sees the polled data. - `fallbackData` on one hook is only that hook's initial value; it is not written to the shared entry. When the settings page needs fresher data than the header, give the settings page's hook the stricter options. The header benefits automatically. ## Pitfalls - `'/api/user'` and `'/api/user/'` are **different keys**, so they give two entries and two requests. - Two hooks with the same key but **different fetchers** share one entry. The hook that happens to start a request uses its own fetcher, and both hooks then show that result. One key should always mean one fetcher and one response shape. - `dedupingInterval` collapses **repeats**; it does not keep data fresh for a period. A mount outside the window revalidates again unless you turn `revalidateIfStale` off for that hook.
- The header mounts, and the settings page mounts 1.5 seconds after the first response arrived. Is a second request sent?No. SWR keeps the finished request registered for `dedupingInterval` (2000 ms by default) after it resolves, and a mount inside that window does not revalidate again. The settings page renders the cached user straight away. At 2.5 seconds it would have started a background revalidation instead, because `revalidateIfStale` is true by default.
- Why wrap useSWR('/api/user', fetcher) in a custom useUser hook?It keeps the key and the fetcher in one place, so every component uses exactly the same key string and the same response shape. A typo such as a trailing slash would otherwise create a second entry and a second request. It also gives you one place to add options or types later.
saying these in an interview costs you the question
- Each useSWR call sends its own request, so two components mean two requests.
- Each component keeps its own copy of the data it fetched.
- isLoading is true whenever a revalidation is in progress.
- Deduplication only works when both components pass the same fetcher function.
- A later mount waits for the network before showing anything.