React SME Cookbook
All FAQs

Search Documentation

Search across all documentation pages

swrtypescriptgenericstypes

TypeScript with SWR

Recipe

Type your useSWR calls with generics to get full type safety on data, error, keys, and fetcher functions. SWR's TypeScript support enables inference of return types from fetcher signatures.

"use client";
 
import useSWR from "swr";
 
interface User {
  id: string;
  name: string;
  email: string;
}
 
const fetcher = (url: string): Promise<User> =>
  fetch(url).then((r) => r.json());
 
function Profile() {
  const { data, error } = useSWR<User, Error>("/api/me", fetcher);
  // data: User | undefined
  // error: Error | undefined
 
  if (data) {
    return <div>{data.name}</div>; // data is User here
  }
}

Working Example

"use client";
 
import useSWR, { Fetcher, SWRConfiguration, Key } from "swr";
import useSWRMutation, { SWRMutationConfiguration } from "swr/mutation";
 
// ---- Type-safe fetcher factory ----
 
function createFetcher<T>(): Fetcher<T, string> {
  return async (url: string) => {
    const res = await fetch(url);
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return res.json() as Promise<T>;
  };
}
 
// ---- Domain types ----
 
interface Product {
  id: string;
  name: string;
  price: number;
  category: string;
}
 
interface CreateProductInput {
  name: string;
  price: number;
  category: string;
}
 
// ---- Type-safe hooks ----
 
function useProduct(id: string) {
  return useSWR<Product, Error>(
    `/api/products/${id}`,
    createFetcher<Product>()
  );
}
 
function useProducts(category?: string) {
  const key = category ? `/api/products?category=${category}` : "/api/products";
  return useSWR<Product[], Error>(key, createFetcher<Product[]>());
}
 
// ---- Type-safe mutation ----
 
async function createProduct(
  url: string,
  { arg }: { arg: CreateProductInput }
): Promise<Product> {
  const res = await fetch(url, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(arg),
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}
 
function useCreateProduct() {
  return useSWRMutation<Product, Error, string, CreateProductInput>(
    "/api/products",
    createProduct
  );
}
 
// ---- Usage ----
 
export default function ProductPage({ id }: { id: string }) {
  const { data: product, error, isLoading } = useProduct(id);
  const { trigger, isMutating } = useCreateProduct();
 
  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
 
  return (
    <div>
      <h1>{product?.name}</h1>
      <p>${product?.price}</p>
      <button
        onClick={() =>
          trigger({ name: "New Item", price: 29.99, category: "widgets" })
        }
        disabled={isMutating}
      >
        Add Product
      </button>
    </div>
  );
}

Deep Dive

How It Works

  • useSWR<Data, Error, Key> accepts up to three generic parameters: the data type, the error type, and the key type.
  • When you provide a typed fetcher, SWR can infer the Data generic from the fetcher's return type.
  • The Fetcher<Data, Key> type from SWR enforces that the fetcher accepts the key type and returns Promise<Data>.
  • useSWRMutation<Data, Error, Key, Arg> adds a fourth generic for the mutation argument type.
  • SWR exports utility types like SWRResponse, SWRConfiguration, Key, and Fetcher for advanced typing.

Variations

Inferred types from fetcher:

// SWR infers Data from the fetcher return type
const fetchUser = (url: string): Promise<User> => fetch(url).then((r) => r.json());
const { data } = useSWR("/api/me", fetchUser);
// data: User | undefined — inferred from fetchUser

Array key typing:

type FetcherArgs = [url: string, token: string];
 
const fetcher: Fetcher<User, FetcherArgs> = ([url, token]) =>
  fetch(url, { headers: { Authorization: `Bearer ${token}` } }).then((r) => r.json());
 
const { data } = useSWR<User, Error, FetcherArgs>(
  ["/api/me", authToken],
  fetcher
);

Typed middleware:

import { Middleware, SWRHook } from "swr";
 
const loggerMiddleware: Middleware = (useSWRNext: SWRHook) => {
  return (key, fetcher, config) => {
    const result = useSWRNext(key, fetcher, config);
    console.log(`SWR [${key}]:`, result.data);
    return result;
  };
};

Typed SWR configuration:

import type { SWRConfiguration } from "swr";
 
const defaultConfig: SWRConfiguration<User, Error> = {
  revalidateOnFocus: false,
  errorRetryCount: 3,
  onSuccess: (data) => {
    // data is typed as User
    console.log(data.name);
  },
};

TypeScript Notes

  • Always prefer explicit generics over as casts for type safety. useSWR<User> is better than data as User.
  • When using conditional keys (null), the data type remains Data | undefined even with suspense.
  • The Key type accepts string, any[], null, undefined, or () => Key.
  • For strict null checking, always handle the undefined case for data even when you expect it to be loaded.

Gotchas

  • res.json() returns Promise<any> by default. You must cast it: res.json() as Promise<User>. This is a trust boundary — runtime validation is not performed.
  • If your fetcher generic does not match the useSWR generic, TypeScript may silently widen to any. Always verify your types align.
  • error defaults to any if you do not specify the Error generic. Always specify it for type safety: useSWR<Data, Error>.
  • When wrapping useSWR in a custom hook, the return type is SWRResponse<Data, Error>. Import it for explicit typing.
  • Middleware type signatures are complex. Use the Middleware type export rather than writing the full signature manually.

Alternatives

ApproachProsCons
Explicit generics on useSWRFull control, clear typesVerbose, manual alignment
Inferred from fetcherLess boilerplateEasy to accidentally return any
Custom typed hooksEncapsulated, reusableExtra abstraction layer
Runtime validation (zod)True runtime safetyExtra dependency, performance cost

FAQs

How do I pass generics to useSWR for full type safety?
const { data, error } = useSWR<User, Error>("/api/me", fetcher);
// data: User | undefined
// error: Error | undefined

Up to three generics: useSWR<Data, Error, Key>.

Can SWR infer the data type from the fetcher without explicit generics?

Yes. If your fetcher has a typed return, SWR infers Data from it:

const fetchUser = (url: string): Promise<User> =>
  fetch(url).then((r) => r.json());
const { data } = useSWR("/api/me", fetchUser);
// data: User | undefined (inferred)
Gotcha: Why does res.json() return Promise<any> and how should I handle it?

res.json() always returns Promise<any> in TypeScript. You must cast it: res.json() as Promise<User>. This is a trust boundary -- TypeScript cannot validate the runtime shape. Consider using Zod for runtime validation.

What is the Fetcher type and how do I use it?
import { Fetcher } from "swr";
 
const fetcher: Fetcher<User[], string> = (url) =>
  fetch(url).then((r) => r.json());

Fetcher<Data, Key> enforces that the fetcher accepts the key type and returns Promise<Data>.

How do I type useSWRMutation with all four generics?
useSWRMutation<Data, Error, Key, Arg>(key, mutationFn);
  • Data: return type of the mutation
  • Error: error type
  • Key: the cache key type
  • Arg: the argument type passed to trigger
Gotcha: What happens if the fetcher generic does not match the useSWR generic?

TypeScript may silently widen to any instead of showing a compile error. Always verify that your fetcher's return type aligns with the Data generic you pass to useSWR.

How do I type array keys for useSWR?
type FetcherArgs = [url: string, token: string];
 
const fetcher: Fetcher<User, FetcherArgs> = ([url, token]) =>
  fetch(url, {
    headers: { Authorization: `Bearer ${token}` },
  }).then((r) => r.json());
 
const { data } = useSWR<User, Error, FetcherArgs>(
  ["/api/me", authToken],
  fetcher
);
What is the SWRResponse type and when should I use it?

When wrapping useSWR in a custom hook, the return type is SWRResponse<Data, Error>. Import it from swr for explicit typing of your custom hook's return value.

How do I type a SWR middleware function?
import { Middleware, SWRHook } from "swr";
 
const myMiddleware: Middleware = (useSWRNext: SWRHook) => {
  return (key, fetcher, config) => {
    return useSWRNext(key, fetcher, config);
  };
};

Use the Middleware type export rather than writing the full signature manually.

Should I use explicit generics or as-casts for typing SWR data?

Always prefer explicit generics (useSWR<User>) over as casts (data as User). Generics provide compile-time safety across the entire hook usage, while casts only silence the type checker at one point.

How do I type the SWRConfiguration object for a config variable?
import type { SWRConfiguration } from "swr";
 
const config: SWRConfiguration<User, Error> = {
  revalidateOnFocus: false,
  onSuccess: (data) => {
    console.log(data.name); // data is typed as User
  },
};