Skip to content
Development
Skill

/web-data-fetching-swr

SWR data fetching patterns — keys and fetchers, isLoading vs isValidating, revalidation strategy, useSWRMutation, useSWRInfinite, conditional fetching

From plugin
agents-inc-skills
24200 skills
Install
$ npx -y skills add agents-inc/skills --skill web-data-fetching-swr --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/web-data-fetching-swr

Context preview

The summary Claude sees to decide when to auto-load this skill.

SWR data fetching patterns — keys and fetchers, isLoading vs isValidating, revalidation strategy, useSWRMutation, useSWRInfinite, conditional fetching

SKILL.md

web-data-fetching-swr.SKILL.md
name: web-data-fetching-swr
description: SWR data fetching patterns — keys and fetchers, isLoading vs isValidating, revalidation strategy, useSWRMutation, useSWRInfinite, conditional fetching

SWR Patterns

> **Quick Guide:** SWR renders the cached value immediately and revalidates behind it, so the cache > key is the whole identity of a request and an unstable key is the single most expensive mistake > here. `isLoading` covers the first fetch only and `isValidating` covers every fetch, which is why > using the second as a spinner hides the data SWR exists to show. Reads are `useSWR`, writes are > `useSWRMutation`, and a `null` key is how a request is skipped without breaking the rules of hooks.

**Detailed Resources:**

  • [examples/core.md](examples/core.md) — fetchers, key shapes, state handling, `SWRConfig`
  • [examples/mutations.md](examples/mutations.md) — `useSWRMutation`, optimistic updates, `populateCache`, global `mutate`
  • [examples/caching.md](examples/caching.md) — revalidation strategies, prefetching, cache persistence
  • [examples/pagination.md](examples/pagination.md) — `useSWRInfinite`, infinite scroll, offset and filtered paging
  • [examples/conditional.md](examples/conditional.md) — the null key, dependent queries, function keys
  • [examples/error-handling.md](examples/error-handling.md) — retry policy, status-specific handling, offline
  • [examples/suspense.md](examples/suspense.md) — suspense mode and server-rendered fallback data
  • [reference.md](reference.md) — every config option with its default, and the hook return shapes

---

Which path applies

  • **Reading a resource** — `useSWR`, which fetches on mount and keeps the value fresh. Patterns 1–3.
  • **Writing** — `useSWRMutation`, which does nothing until `trigger()` is called. Pattern 4.
  • **A list that grows** — `useSWRInfinite`, whose `getKey(pageIndex, previousPage)` both builds each

page's key and signals the end by returning `null`. Pattern 7.

  • **Suspense instead of loading branches** — `suspense: true` makes the component suspend and `data`

non-null; see [examples/suspense.md](examples/suspense.md). Everything else here still applies.

---

<critical_requirements>

Before writing SWR code

**Give each request a key that is stable across renders — a string, or an array of primitives.** The key is the cache identity and the dependency: an object or array literal is a new reference every render, so SWR sees a new key, fetches again, re-renders, and repeats.

**Throw from the fetcher on a non-OK response.** SWR's error state is driven by a rejected promise, so a fetcher that returns `res.json()` unconditionally hands the error body over as `data` and no error branch ever runs.

**Branch on `isLoading` for the first fetch and `isValidating` for a refresh in progress.** `isLoading` is true only when there is no data yet, which is exactly when a skeleton is right; `isValidating` is true during background revalidation, when there is data on screen to keep.

**Reach for `useSWRMutation` for anything that writes.** `useSWR` fires on mount, so a POST written as a `useSWR` fetcher sends itself as soon as the component renders.

</critical_requirements>

---

**Auto-detection:** `useSWR`, `useSWRMutation`, `useSWRInfinite`, `useSWRImmutable`, `SWRConfig`, `useSWRConfig`, `mutate`, `trigger`, `isValidating`, `revalidateOnFocus`, `dedupingInterval`, `keepPreviousData`, `fallbackData`, `optimisticData`, `rollbackOnError`, `populateCache`, `preload`, `swr/mutation`, `swr/infinite`, `swr/immutable`

**Applies to:**

  • Cache keys, fetchers and the shape of what a hook returns
  • Revalidation policy: focus, reconnect, interval, stale, and disabling all of it
  • Writes, optimistic updates and cache invalidation after them
  • Cursor and offset pagination that accumulates pages
  • Conditional and dependent fetching
  • Retry policy and hydrating from server-rendered data

**Handled elsewhere:**

  • Client state that never came from a server — this skill caches responses
  • The transport itself; a fetcher is any function returning a promise, and what it uses is open
  • How an error boundary is built — this skill settles which option throws an error into one
  • APIs addressed through a graph query language, whose clients cache normalised entities rather than

whole responses under a key

---

<philosophy>

Philosophy

The name is the algorithm: return what is cached, revalidate behind it, re-render if the answer changed. A component therefore has data at nearly every moment of its life, and the interesting states are not "loading or loaded" but "is this being checked" and "is this out of date".

Everything else follows. The key is a global identity, so two components asking for the same key share one request and one cache entry with no coordination between them. Revalidation is triggered by events the user causes — refocusing the tab, reconnecting — rather than by timers, because those are the moments the data on screen is most likely to be stale.

</philosophy>

---

<patterns>

Core patterns

Pattern 1: The fetcher

interface FetchError extends Error {
  info: unknown;
  status: number;
}

const fetcher = async <T>(url: string): Promise<T> => {
  const response = await fetch(url);
  if (!response.ok) {
    const error = new Error("Fetch failed") as FetchError;
    error.info = await response.json().catch(() => null);
    error.status = response.status;
    throw error;
  }
  return response.json();
};

Attaching `status` is what lets a component tell a 404 from a 500, and lets a retry policy decline to retry either. Define the fetcher at module scope — one created inside a component is a new reference on every render.

Full code: [examples/core.md](examples/core.md) — client-based and multi-argument fetchers

---

Pattern 2: isLoading vs isValidating

// data: undefined, isLoading: true,  isValidating: true   — first fetch
// data: T,         isLoading: false, isValidating: fal
Read more
Ships withagents-inc-skills

The official skills marketplace for Agents Inc. 150+ skills covering everything from React and Prisma to Redis, ElevenLabs, and infrastructure tooling. Pick the skills that match your stack and install them via Claude Code. Need more control?

Get the whole plugin

Other skills on agents-inc-skills.