Custom API Hooks
Purpose and Scope
This page explains the API Hooks example pattern: wrap useSWR in small, domain-specific React hooks so application components ask for business data instead of repeating request keys, fetchers, and loading-state interpretation everywhere. In the example’s reader-facing description, the goal is to create custom hooks that use SWR internally for different data requirements and then consume those hooks in the application. That pattern is useful when a product has concepts such as projects, repositories, users, or suggestions, because it keeps cache keys and request details close to the API boundary while leaving components focused on rendering.
SWR’s repository README provides the core contract that makes this wrapper pattern practical. useSWR accepts a key and a fetcher; the key uniquely identifies the request, usually as a URL, and the fetcher receives that key and returns data asynchronously. The hook returns data, isLoading, and error, allowing components to render pending, failed, and successful states without writing imperative request lifecycle code. A custom API hook preserves that contract but gives it a domain name, such as useProject(id) or useRepository(owner, name). Sources: README.md
The important design choice is that a custom hook should hide transport details without hiding SWR semantics. Callers should still understand that the result is backed by SWR cache, stale-while-revalidate updates, request deduplication, focus and network recovery revalidation, and normal React rendering. The README describes SWR as cache-first and reactive: it can return cached data first, revalidate in the background, and then update the UI with fresh data. A wrapper hook should therefore expose enough state for the caller to render responsibly instead of pretending every request is a one-time promise. Sources: README.md
Relevant Source Files
README.md— Defines SWR as a React Hooks data-fetching library, explains the stale-while-revalidate model, and shows the minimaluseSWR(key, fetcher)return contract used inside custom hooks.e2e/test/issue-2702-too-many-hooks.ts— Provides an end-to-end signal that pages using many SWR hooks should remain stable and render expected data instead of crashing.e2e/site/README.md— Documents the local Next.js E2E site workflow, including development-server commands and thepages/apiroute convention that supports API-backed examples.
Core Pattern
A custom API hook is just a React hook that calls useSWR and returns either the raw SWR response or a shaped version of it. The wrapper normally owns the key construction, the fetcher selection, and any domain-specific naming. For example, instead of making every component know that projects live under /api/projects/:id, a useProject(id) hook can build that key internally. Components then depend on the application concept, not the URL convention. This keeps refactors localized: if the endpoint or fetch helper changes, the component tree does not need broad edits.
import useSWR from 'swr'
const fetcher = url => fetch(url).then(res => res.json())
export function useProject(id) {
const key = id ? `/api/projects/${id}` : null
return useSWR(key, fetcher)
}
export function ProjectName({ id }) {
const { data, error, isLoading } = useProject(id)
if (error) return <p>failed to load</p>
if (isLoading) return <p>loading...</p>
return <h1>{data.name}</h1>
}This example follows the README’s baseline usage: the key identifies the request, the fetcher resolves the data, and the component renders from data, error, and isLoading. The only difference is ownership. A page component no longer repeats the endpoint string and fetcher choice every time it needs project data. That makes the application easier to read and also reduces accidental cache fragmentation, because every consumer of the same resource can share the same key-building rule. Sources: README.md
Use conditional keys when the domain request is not ready. In the illustrative useProject(id) wrapper, null means the hook should not start a request until an id exists. This is especially important in route-driven or form-driven pages where some inputs arrive after the first render. The wrapper should make that condition explicit so callers do not need to remember the exact disabled-key convention. The result is a stable hook call on every render while the request itself remains conditional, which fits React’s rule that hooks must be called consistently.
Running the Example Locally
The API Hooks example README describes a self-contained workflow: download the examples/api-hooks directory from the repository archive, enter that directory, install dependencies, and start the development server with either Yarn or npm. The practical purpose of running it locally is to see the wrapper-hook boundary in a real app rather than only in a small snippet. You should inspect where the custom hooks are defined, how pages import them, and whether components render from hook-level fields or directly manipulate fetch requests.
curl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/api-hooks
cd api-hooks
yarn
yarn dev
# or
npm install
npm run devThe repository also includes a Next.js-based E2E site, and its README gives the standard local server commands: npm run dev, yarn dev, or pnpm dev, followed by opening http://localhost:3000. It notes that pages/api maps to /api/*, with files in that directory treated as API routes rather than React pages. That convention is directly relevant to API-hook examples because it lets a small demo app keep both the UI and mock API endpoints in one Next.js project. Sources: e2e/site/README.md
When adapting the pattern, start from one resource and one component. Move the useSWR call into a hook named after the resource, keep the fetcher simple, and return the SWR object unchanged at first. After the wrapper is proven useful, add domain-specific shaping only when it reduces duplication. For example, exposing project, isProjectLoading, and projectError can improve readability, but over-shaping every SWR field can also make advanced behaviors such as mutate or revalidation harder to reach.
System-to-Code Mapping
| Concept | Repository-backed signal | How it applies to API hooks |
|---|---|---|
| SWR request identity | README.md explains the key as a unique request identifier | Custom hooks should centralize key construction for each domain resource |
| Fetcher boundary | README.md says the fetcher receives the key and returns data asynchronously | Custom hooks can select a shared fetcher or resource-specific fetch helper |
| Render states | README.md documents data, isLoading, and error | Components using custom hooks should still render loading and failure states |
| Next.js API routes | e2e/site/README.md explains that pages/api maps to /api/* | Example apps can pair API-hook consumers with local API route implementations |
| Many hook instances | e2e/test/issue-2702-too-many-hooks.ts checks a page that renders expected text with many hooks | Wrapper hooks should remain ordinary React hooks and scale across component trees |
The mapping shows why this example is more than a naming convention. SWR gives the low-level, cache-aware data primitive; the application hook turns that primitive into an API boundary. Next.js API routes, when used in local examples, provide convenient endpoints for the keys. End-to-end tests then give confidence that pages containing many hook calls keep rendering correctly. Together, these pieces encourage a structure where data requirements are explicit, repeatable, and testable without pushing networking logic into every component.
Testing Signals
The E2E test for issue 2702 is not an API Hooks example test, but it is a useful guardrail for anyone building hook-heavy abstractions. It navigates to an issue-2702 page, waits for the page to become idle, expects the text fetching to be visible, and then expects a,b to appear. That scenario captures two things custom-hook authors should care about: a page can mount with active SWR work, and it should eventually render the expected data without crashing because the application uses many hooks. Sources: e2e/test/issue-2702-too-many-hooks.ts
For custom API hooks, test at two levels. First, test the hook contract by rendering a component that consumes the wrapper and verifying loading, error, and success output. Second, test a page that composes several wrappers together, because the value of the pattern is most visible when multiple resources are needed at once. The repository’s E2E signal suggests that SWR should tolerate many hook usages in a page, but your application wrappers still need stable keys, consistent hook ordering, and predictable conditional behavior.
Implementation Guidelines
Keep wrapper hooks thin unless the domain really needs more structure. A good first version takes input parameters, derives a key, calls useSWR, and returns the SWR response. If every wrapper invents a different result shape, components lose the shared mental model from the README’s quick start. If every wrapper returns the raw SWR response, developers can still reach data, error, isLoading, and related SWR capabilities consistently. Add aliases or computed values only where they make the domain clearer.
Prefer colocating key construction with the wrapper instead of exporting endpoint strings throughout the app. The key is not just a URL; it is the cache identity for a request. Two components that intend to share project data must construct the same key, and two different data requirements should not accidentally collide. A custom hook is the natural place to encode that rule because it sits at the boundary between product vocabulary and SWR’s cache vocabulary. Sources: README.md
Avoid calling custom API hooks conditionally. The request may be conditional, but the hook call should stay unconditional in the component. Put readiness checks inside the key expression, for example by passing null until required inputs exist. This keeps the wrapper compatible with React’s hook model while preserving SWR’s ability to skip work until a key is available. This distinction becomes especially important when the same page uses many custom hooks for related resources.
Next Steps
After running the example, compare a page before and after extracting a custom API hook. The best extraction removes repeated endpoint strings, fetcher setup, and state naming while preserving SWR’s visible behavior. If you need broader configuration, read the global fetcher and SWRConfig material next. If the hook writes data, continue to mutation and optimistic UI examples. If the hook loads collections over time, continue to the infinite loading examples so your domain hook can expose pagination controls cleanly.