Fetchers and Data Flow
Purpose and Scope
This page explains the contract between an SWR key, the fetcher function that loads data for that key, and the type system that connects them. A fetcher is the user-supplied function SWR calls when it needs fresh data. It can return data synchronously or return a promise, and SWR treats both shapes as valid fetcher responses. The practical reader problem is deciding what argument the fetcher receives, where the fetcher can be supplied, and how to keep TypeScript inference useful when keys are strings, objects, tuples, readonly tuples, conditional values, or key functions.
Sources: src/_internal/types.ts, test/type/fetcher.ts, test/type/option-fetcher.ts
SWR is intentionally transport agnostic: the hook does not require fetch, REST, GraphQL, or any particular client. The repository types model that by separating a permissive BareFetcher from the key-aware Fetcher type. BareFetcher accepts any arguments, while Fetcher<Data, SWRKey> derives its single argument from the key shape. This distinction matters for application code because the more precise form lets the key become a typed request descriptor instead of only a cache string.
Sources: src/_internal/types.ts
Runtime behavior and type behavior are tested together but answer different questions. Runtime tests verify that SWR uses the most recent fetcher reference when a component re-renders, when a bound mutate triggers revalidation, and when a key changes. Type tests verify that the fetcher parameter matches the resolved key form and that conditional keys remove null and false from the fetcher argument. Read these tests as the public contract developers rely on when refactoring fetchers or moving fetchers into options objects.
Sources: test/use-swr-fetcher.test.tsx, test/type/fetcher.ts, test/type/option-fetcher.ts
Relevant Source Files
src/_internal/types.tsdefines the public fetcher-related type contracts, includingFetcherResponse,BareFetcher, andFetcher<Data, SWRKey>.test/use-swr-fetcher.test.tsxvalidates runtime fetcher behavior, especially latest-reference handling and accepting falsy fetcher values.test/type/fetcher.tsvalidates TypeScript inference when the fetcher is passed as the seconduseSWRoruseSWRInfiniteargument.test/type/option-fetcher.tsvalidates the same inference style when the fetcher is supplied inside the options object asfetcher.
Core Fetcher Contract
At the type level, a fetcher response is either the data itself or a promise for the data. That is captured by FetcherResponse<Data> = Data | Promise<Data>. The hook can therefore work with simple examples that return a literal object, helpers that return fetch(...).then(...), and domain clients that already return promises. SWR does not force the data source to be asynchronous in the type contract, even though real network fetchers usually are asynchronous.
Sources: src/_internal/types.ts
The permissive fetcher shape is BareFetcher<Data> = (...args: any[]) => FetcherResponse<Data>. This exists for code paths where SWR cannot or should not infer a specific key argument. The key-aware fetcher is stricter. If the SWR key is a function returning an argument or a falsy value, Fetcher receives only the returned non-falsy argument. If the key itself is null, undefined, or false, the fetcher type becomes never, because there is no request to make. Otherwise, the fetcher receives the key directly.
Sources: src/_internal/types.ts
A useful mental model is: the key identifies the request and also becomes the fetcher input. For a string key such as /api/user, the fetcher receives that string. For an object key such as { a: '1', b: { c: '3', d: 2 } }, the fetcher receives an object with the same inferred structure. For a tuple key, the fetcher receives the tuple as one argument. This keeps parameter passing predictable and lets a key serve as a structured request descriptor.
Sources: test/type/fetcher.ts, test/type/option-fetcher.ts
import useSWR from 'swr'
const fetcher = ({ url }: { url: string }) => fetch(url).then(r => r.json())
const { data } = useSWR({ url: '/api/user' }, fetcher)Argument Inference by Key Shape
The type tests show that literal string keys preserve narrow inference. A useSWR('/api/user', key => ...) fetcher sees key as the literal /api/user, not just any string. When the same key is conditional, such as an expression that returns /api/user or null, the fetcher still receives only the usable key type. The tests apply the same expectation when the disabled branch is false, which reinforces that disabled request states are not passed to the fetcher.
Sources: test/type/fetcher.ts, test/type/option-fetcher.ts
Object keys are also preserved. In the type tests, an object with nested fields is inferred as an object containing a: string and nested b fields, including a numeric d. That means a fetcher can destructure or inspect a typed request object without separately declaring a stringly typed protocol. This is especially useful for applications that represent request parameters, filters, authentication scopes, or route variables as a single serializable key object.
Sources: test/type/fetcher.ts, test/type/option-fetcher.ts
Tuple keys let callers group multiple request parameters while retaining positional information. The tests cover a tuple containing an object and an array, and the fetcher receives the tuple as a typed value. Readonly tuple keys created with as const keep readonly literal information, including literal string and number values. This is important when a codebase wants request keys to be immutable constants and still expects the fetcher to receive the exact type, not a widened mutable approximation.
Sources: test/type/fetcher.ts, test/type/option-fetcher.ts
Function keys are handled as a separate inference case. When a key function returns /api/user, the fetcher receives a string, because the argument comes from the function result. When a key function can return a valid key or a disabled value such as null, the fetcher argument is still the valid return type. In other words, disabled request states gate execution; they do not become part of the fetcher input contract.
Sources: src/_internal/types.ts, test/type/fetcher.ts, test/type/option-fetcher.ts
Passing Fetchers Inline or Through Options
SWR supports the familiar form where the fetcher is the second argument: useSWR(key, fetcher). The type tests in test/type/fetcher.ts cover this style for generic data types, string keys, object keys, tuples, readonly tuples, and function keys. The same file also covers useSWRInfinite, where the page-key function receives index as a number and previousPageData as either the prior page data type or null. The fetcher for infinite loading then receives the key produced for each page.
Sources: test/type/fetcher.ts
SWR also supports placing the fetcher in the options object as useSWR(key, { fetcher }). The tests in test/type/option-fetcher.ts mirror the direct-fetcher tests, which documents that callers should expect the same key-based inference in both styles. This is helpful when options need to travel together, such as when a component sets a fetcher alongside revalidation settings, fallback data, callbacks, or suspense-related configuration.
Sources: test/type/option-fetcher.ts
The global-fetcher example in the project documentation uses SWRConfig to avoid passing a fetcher to every hook. The source evidence for this page focuses on per-call and options-based fetchers, but the same conceptual contract applies: whichever fetcher SWR resolves for a request is expected to accept the resolved key and return data or a promise. When choosing between styles, prefer a global fetcher for shared URL conventions, an options fetcher for component-local policy, and a direct fetcher for concise one-off hooks.
Sources: src/_internal/types.ts, test/type/option-fetcher.ts
useSWR('/api/user', key => fetch(key).then(r => r.json()))
useSWR('/api/user', {
fetcher: key => fetch(key).then(r => r.json())
})Runtime Data Flow and Fresh Fetcher References
At runtime, SWR must not hold onto a stale fetcher implementation after React renders a component with a newer function reference. The fetcher tests create a mutable fetcher variable that first returns foo, render a component with useSWR, then replace the variable with a function that returns bar. A bound mutate() triggers revalidation, and the test expects the displayed data to update to bar. That proves revalidation uses the latest fetcher reference visible to the hook.
Sources: test/use-swr-fetcher.test.tsx
The same latest-reference behavior is tested when the key changes. A component starts with a prefixed key, renders data from the initial fetcher, then changes the fetcher variable and updates local state so the key changes. SWR fetches with the newer function and displays the newer result. A similar scenario is tested under suspense mode, with a Suspense fallback wrapping the component. The practical guidance is that fetchers can close over current props or state, but developers should still use normal React patterns to avoid accidental stale closures.
Sources: test/use-swr-fetcher.test.tsx
The runtime tests also show that falsy fetcher values are accepted as inputs to the hook. A component is rendered with fetcher={null}, then rerendered with undefined, then with false, and the component remains renderable with empty data output. This should not be confused with a disabled key. A falsy key prevents a request from being represented for the fetcher type, while a falsy fetcher value means there is no callable fetcher supplied at that location.
Sources: test/use-swr-fetcher.test.tsx, src/_internal/types.ts
Compact Reference
| Concept | Source-backed contract |
|---|---|
FetcherResponse<Data> | A fetcher may return Data or Promise<Data>. |
BareFetcher<Data> | A permissive fetcher accepting any argument list and returning a FetcherResponse<Data>. |
Fetcher<Data, SWRKey> | A key-aware fetcher whose argument is inferred from the SWR key or key function result. |
| Disabled key values | null, undefined, and false are excluded from the fetcher argument path; a fully falsy key maps to never. |
| Direct fetcher form | useSWR(key, fetcher) receives the same inference tested for strings, objects, tuples, readonly tuples, and function keys. |
| Options fetcher form | useSWR(key, { fetcher }) mirrors direct-fetcher inference. |
| Infinite fetcher flow | useSWRInfinite page key builders receive index and previousPageData; the fetcher receives the generated page key. |
| Revalidation behavior | Revalidation uses the latest fetcher reference after rerender, key change, and tested suspense key-change paths. |
Next Steps
When designing a new hook around SWR, start by choosing the key shape you want your fetcher to receive. Use a string when a URL is enough, an object when named request parameters make the fetcher clearer, and a readonly tuple when positional parameters must remain exact. If many hooks share the same transport behavior, move that behavior into a shared fetcher or global configuration, but keep each key descriptive enough to identify the request and carry the parameters the fetcher needs.
Sources: test/type/fetcher.ts, test/type/option-fetcher.ts, src/_internal/types.ts
For related reading, continue with key serialization before building custom cache keys, then read the useSWR API reference for hook return values and revalidation behavior. If the code is paginated, move to the useSWRInfinite reference because its fetcher is driven by generated page keys. If the question is primarily TypeScript ergonomics, use the TypeScript types page as the companion to the contracts summarized here.