Skip to main content
skill CX Codex

swr — Claude Code skill: what it does, how to install

SWR data-fetching guidelines for React and TypeScript apps. It guides agents on caching, request deduplication, and optimistic mutations.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add Mindrally/skills --skill swr -a claude-code
Open on GitHub

What swr does

This skill provides coding agents with standard patterns for client-side data fetching using SWR in React. It specifies a clean project layout across hooks/swr/, lib/fetcher.ts, and providers/SWRProvider.tsx. Generated setups configure automatic deduplication with a 2000 ms interval and up to three retry attempts on fetch errors.

The instructions cover conditional queries using null keys, polling via refreshInterval, and pagination with keepPreviousData: true. Mutate examples demonstrate optimistic UI updates paired with rollbackOnError: true for clean rollback. Without the swr package added to package.json, none of these hooks will compile.

What it helps with

  • configure a global SWRConfig provider with an authenticated fetcher
  • implement optimistic updates with automatic error rollback
  • set up conditional data fetching and paginated queries
  • add polling intervals and window focus revalidation

Example prompts for the agent

  • Create a useUser hook with conditional fetching based on userId using SWR
  • Write a global SWRProvider with a custom fetcher and deduping interval
  • Build an optimistic update handler for user profiles with rollbackOnError

How to install swr

1Skills CLI (Vercel)

npx skills add Mindrally/skills --skill swr -a claude-code

2Manually: put the files in place

~/.claude/skills/swr/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/swr/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about swr

How do I install swr in Claude Code?

Run npx skills add Mindrally/skills --skill swr -a claude-code in your terminal. You can also place the configuration directly into ~/.claude/skills/swr/SKILL.md.

What does the swr skill do?

It teaches the agent recommended SWR conventions for React, covering typed fetchers, cache invalidation, and custom hooks.

How does it handle optimistic updates?

It applies updates immediately using optimisticData in mutate and reverts local changes if the server call fails via rollbackOnError: true.

Source text

SKILL.md · Apache-2.0
---
name: swr
description: Guidelines for using SWR (stale-while-revalidate) React Hooks for efficient data fetching, caching, and revalidation
metadata:
  maintainer: Mindrally
  source: https://github.com/Mindrally/skills
---

# SWR Best Practices

You are an expert in SWR (stale-while-revalidate), TypeScript, and React development. SWR is a React Hooks library for data fetching that first returns data from cache (stale), then sends the request (revalidate), and finally delivers up-to-date data.

## Core Principles

- Use SWR for all client-side data fetching
- Leverage automatic caching and revalidation
- Minimize boilerplate with SWR's built-in state management
- Implement proper error handling and loading states
- Use TypeScript for full type safety

## Key Features

- **Stale-While-Revalidate**: Returns cached data immediately, then revalidates in background
- **Automatic Revalidation**: On mount, window focus, and network reconnection
- **Request Deduplication**: Multiple components using same key share one request
- **Built-in Caching**: Zero-configuration caching with smart invalidation
- **Minimal API**: Simple hook-based interface

## Project Structure

```
src/
  hooks/
    swr/
      useUser.ts
      usePosts.ts
      useProducts.ts
  lib/
    fetcher.ts           # Global fetcher configuration
  providers/
    SWRProvider.tsx      # SWR configuration provider
  types/
    api.ts               # API response types
```

## Setup and Configuration

### Global Configuration

```typescript
// providers/SWRProvider.tsx
import { SWRConfig } from 'swr';

const fetcher = async (url: string) => {
  const res = await fetch(url);

  if (!res.ok) {
    const error = new Error('An error occurred while fetching data.');
    throw error;
  }

  return res.json();
};

export function SWRProvider({ children }: { children: React.ReactNode }) {
  return (
    <SWRConfig
      value={{
        fetcher,
        revalidateOnFocus: true,
        revalidateOnReconnect: true,
        shouldRetryOnError: true,
        errorRetryCount: 3,
        dedupingInterval: 2000,
      }}
    >
      {children}
    </SWRConfig>
  );
}
```

### Custom Fetcher

```typescript
// lib/fetcher.ts
export async function fetcher<T>(url: string): Promise<T> {
  const response = await fetch(url, {
    headers: {
      'Content-Type': 'application/json',
    },
  });

  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }

  return response.json();
}

// With authentication
export async function authFetcher<T>(url: string): Promise<T> {
  const token = getAuthToken();

  const response = await fetch(url, {
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`,
    },
  });

  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }

  return response.json();
}
```

## Basic Usage

### useSWR Hook

The `useSWR` hook accepts three parameters:
- **key**: A unique string identifier for the request (like a URL)
- **fetcher**: An async function that fetches the data
- **options**: Configuration options

```typescript
import useSWR from 'swr';

interface User {
  id: string;
  name: string;
  email: string;
}

function useUser(userId: string) {
  const { data, error, isLoading, isValidating, mutate } = useSWR<User>(
    userId ? `/api/users/${userId}` : null,
    fetcher
  );

  return {
    user: data,
    isLoading,
    isError: !!error,
    isValidating,
    mutate,
  };
}
```

### Conditional Fetching

Pass `null` or a falsy value as key to conditionally skip fetching:

```typescript
// Only fetch when userId is available
const { data } = useSWR(userId ? `/api/users/${userId}` : null, fetcher);

// Using a function for dynamic keys
const { data } = useSWR(() => `/api/users/${userId}`, fetcher);
```

## Revalidation Strategies

### Automatic Revalidation

SWR automatically revalidates data in three cases:
1. Component is mounted (even with cached data)
2. Window gains focus
3. Browser regains network connection

```typescript
// Disable specific revalidation behaviors
const { data } = useSWR('/api/data', fetcher, {
  revalidateOnFocus: false,
  revalidateOnReconnect: false,
  revalidateOnMount: true,
});
```

### Manual Revalidation

```typescript
import { useSWRConfig } from 'swr';

function UpdateButton() {
  const { mutate } = useSWRConfig();

  const handleUpdate = async () => {
    // Revalidate specific key
    await mutate('/api/users');

    // Revalidate all keys matching a filter
    await mutate(
      (key) => typeof key === 'string' && key.startsWith('/api/users'),
      undefined,
      { revalidate: true }
    );
  };

  return <button onClick={handleUpdate}>Refresh</button>;
}
```

### Polling / Interval Refresh

```typescript
// Refresh every 3 seconds
const { data } = useSWR('/api/realtime', fetcher, {
  refreshInterval: 3000,
});

// Conditional polling
const { data } = useSWR('/api/realtime', fetcher, {
  refreshInterval: isVisible ? 1000 : 0,
});
```

## Mutation Patterns

### Optimistic Updates

```typescript
import useSWR, { useSWRConfig } from 'swr';

function useUpdateUser() {
  const { mutate } = useSWRConfig();

  const updateUser = async (userId: string, newData: Partial<User>) => {
    // Optimistic update
    await mutate(
      `/api/users/${userId}`,
      async (currentData: User | undefined) => {
        // Update API
        const updated = await fetch(`/api/users/${userId}`, {
          method: 'PATCH',
          body: JSON.stringify(newData),
        }).then(r => r.json());

        return updated;
      },
      {
        optimisticData: (current) => ({ ...current, ...newData } as User),
        rollbackOnError: true,
        populateCache: true,
        revalidate: false,
      }
    );
  };

  return { updateUser };
}
```

### Bound Mutate

```typescript
function UserProfile({ userId }: { userId: string }) {
  const { data: user, mutate } = useSWR(`/api/users/${userId}`, fetcher);

  const handleUpdate = async (newName: string) => {
    // Update local data immediately
    mutate({ ...user, name: newName }, false);

    // Send update to server
    await updateUserName(userId, newName);

    // Revalidate to ensure consistency
    mutate();
  };

  return (
    <div>
      <p>{user?.name}</p>
      <button onClick={() => handleUpdate('New Name')}>Update</button>
    </div>
  );
}
```

## Pagination

### Basic Pagination

```typescript
function usePaginatedUsers(page: number) {
  return useSWR(`/api/users?page=${page}`, fetcher, {
    keepPreviousData: true,
  });
}
```

### Infinite Loading with useSWRInfinite

```typescript
import useSWRInfinite from 'swr/infinite';

function useInfiniteUsers() {
  const getKey = (pageIndex: number, previousPageData: User[] | null) => {
    // Return null to stop fetching
    if (previousPageData && previousPageData.length === 0) return null;

    // Return key for next page
    return `/api/users?page=${pageIndex + 1}`;
  };

  const { data, error, size, setSize, isValidating } = useSWRInfinite(
    getKey,
    fetcher
  );

  const users = data ? data.flat() : [];
  const isLoadingMore = size > 0 && data && typeof data[size - 1] === 'undefined';
  const isEmpty = data?.[0]?.length === 0;
  const isReachingEnd = isEmpty || (data && data[data.length - 1]?.length < 10);

  return {
    users,
    isLoadingMore,
    isReachingEnd,
    loadMore: () => setSize(size + 1),
  };
}
```

### Cursor-Based Pagination

```typescript
import useSWRInfinite from 'swr/infinite';

function useCursorPagination() {
  const getKey = (pageIndex: number, previousPageData: any) => {
    if (previousPageData && !previousPageData.nextCursor) return null;

    if (pageIndex === 0) return '/api/items';

    return `/api/items?cursor=${previousPageData.nextCursor}`;
  };

  return useSWRInfinite(getKey, fetcher);
}
```

## Error Handling

### Component-Level Error Handling

```typescript
function UserProfile({ userId }: { userId: string }) {
  const { data, error, isLoading } = useSWR(`/api/users/${userId}`, fetcher);

  if (isLoading) return <Skeleton />;

  if (error) {
    return (
      <ErrorMessage
        message="Failed to load user profile"
        retry={() => mutate(`/api/users/${userId}`)}
      />
    );
  }

  return <ProfileCard user={data} />;
}
```

### Global Error Handler

```typescript
<SWRConfig
  value={{
    onError: (error, key) => {
      if (error.status !== 403 && error.status !== 404) {
        // Send error to tracking service
        reportError(error, key);
      }
    },
  }}
>
  <App />
</SWRConfig>
```

## Prefetching

```typescript
import { preload } from 'swr';

// Prefetch data before component renders
function prefetchUser(userId: string) {
  preload(`/api/users/${userId}`, fetcher);
}

// Usage: Call on hover or route change
function UserLink({ userId }: { userId: string }) {
  return (
    <Link
      to={`/users/${userId}`}
      onMouseEnter={() => prefetchUser(userId)}
    >
      View Profile
    </Link>
  );
}
```

## Key Conventions

1. Use SWR for client-side data fetching with minimal configuration
2. Implement conditional rendering for loading states and error messages
3. Validate data to ensure expected format and handle errors gracefully
4. Use prefetching to improve performance before users request data
5. Leverage automatic revalidation for real-time data freshness
6. Use `useSWRInfinite` for paginated and infinite scroll patterns

## Integration with Next.js

```typescript
// For server-side data, use getServerSideProps or getStaticProps
// For client-side caching and revalidation, use SWR

// pages/user/[id].tsx
export async function getStaticProps({ params }) {
  const user = await fetchUser(params.id);
  return { props: { fallback: { [`/api/users/${params.id}`]: user } } };
}

export default function UserPage({ fallback }) {
  return (
    <SWRConfig value={{ fallback }}>
      <UserProfile />
    </SWRConfig>
  );
}
```

## Anti-Patterns to Avoid

- Do not use `useEffect` for data fetching when SWR can handle it
- Do not ignore error states - always provide user feedback
- Do not forget to handle loading states appropriately
- Do not use polling when WebSocket or SSE would be more efficient
- Do not skip conditional fetching for dependent data
- Do not ignore TypeScript types for fetched data

© Mindrally · Apache-2.0 · Mindrally/skills

Similar skills

Everything for Frontend and UI →

From the same set168

scikit-learn-best-practices A skill that hands your agent scikit-learn conventions: pipelines, cross-validation, metrics, model persistence. Useful when you write ML code in Python. CX Codex +1 888installs astro A skill that tells the agent how to build Astro sites: static generation, partial hydration, file-based routing and as little client-side JavaScript as possible. CX Codex +1 886installs beautifulsoup-parsing Teaches AI agents to parse HTML and XML with BeautifulSoup in Python. Helps write cleaner scrapers with safe data extraction and DOM navigation. CX Codex +1 877installs bash-scripting This skill gives the agent rules for writing Bash scripts: input validation, error handling, portability. Useful when the agent writes deploy, backup or cron tasks. CX Codex +1 875installs django-rest-api-development A skill that gives your coding agent a house style for Django REST APIs: project layout, serializers, JWT auth and query performance. CX Codex +1 868installs data-jupyter-python Tells your agent how to write data analysis code: pandas and numpy, matplotlib and seaborn plots, reproducible notebooks, fewer loops. CX Codex +1 865installs openai-api-development A skill that steers your coding agent through Anthropic API work: Claude calls, function calling, embeddings and Assistants API, with Python examples. CX Codex +1 864installs jest A skill that tells your agent how to write Jest tests: structure, mocks, assertions, async. Useful when AI writes the tests in your repo. CX Codex +1 864installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor