ReactSWR

SWR

SWR is Vercel's open-source data fetching library for React. Its name comes from the HTTP cache-control strategy stale-while-revalidate: show cached (stale) data immediately, then fetch fresh data in the background, and update the UI when the new data arrives. SWR is lighter and requires less configuration than TanStack Query, making it an excellent choice for Next.js applications and simpler data requirements.

Installation

Bash
npm install swr
Basic Usage

useSWR takes a key (typically the URL) and a fetcher function. The fetcher is any function that accepts the key and returns a Promise:

JSX
import useSWR from 'swr'

// Define a reusable fetcher function
const fetcher = (url) => fetch(url).then(res => {
  if (!res.ok) throw new Error(`HTTP ${res.status}`)
  return res.json()
})

function UserProfile({ userId }) {
  const { data, error, isLoading } = useSWR(
    userId ? `/api/users/${userId}` : null,  // null key = don't fetch
    fetcher
  )

  if (isLoading) return <p>Loading…</p>
  if (error)     return <p>Error: {error.message}</p>
  if (!data)     return null

  return (
    <div>
      <h2>{data.name}</h2>
      <p>{data.email}</p>
    </div>
  )
}
Note
Passing null as the key suspends the fetch entirely — useful for conditional fetching when you don't have the required data yet (like a userId that hasn't loaded).
Return Values
  • data — the fetched data, or undefined while loading.

  • error — the error thrown by the fetcher, or undefined on success.

  • isLoading — true only on the initial load (no cached data yet).

  • isValidating — true whenever a request is in-flight (initial or background).

  • mutate — function to update the cache or trigger a revalidation.

Global Configuration

Use SWRConfig to set global defaults — fetcher, revalidation options, and error retries — so you don't repeat them on every call:

JSX
import { SWRConfig } from 'swr'

const globalFetcher = (url) =>
  fetch(url).then(res => {
    if (!res.ok) throw new Error(`Request failed: ${res.status}`)
    return res.json()
  })

function App() {
  return (
    <SWRConfig
      value={{
        fetcher: globalFetcher,
        revalidateOnFocus: true,       // refetch when window regains focus
        revalidateOnReconnect: true,   // refetch when network reconnects
        dedupingInterval: 2000,        // deduplicate requests within 2s window
        errorRetryCount: 3,            // retry failed requests 3 times
        refreshInterval: 0,            // 0 = no polling (set ms for live data)
      }}
    >
      <YourApp />
    </SWRConfig>
  )
}
Mutation & Cache Invalidation

SWR's mutate function updates the cache locally and/or triggers a revalidation. It's available from the useSWR return value (scoped to one key) or as a global mutate (affects any key):

JSX
import useSWR, { mutate } from 'swr'

function PostList() {
  const { data: posts, mutate } = useSWR('/api/posts', fetcher)

  async function deletePost(id) {
    // Optimistic update: remove from local cache immediately
    mutate(
      posts.filter(p => p.id !== id),
      false   // false = don't revalidate yet
    )

    try {
      await fetch(`/api/posts/${id}`, { method: 'DELETE' })
      mutate()   // trigger a fresh fetch to confirm the deletion
    } catch {
      mutate()   // revert by refetching on error
    }
  }

  return (
    <ul>
      {posts?.map(post => (
        <li key={post.id}>
          {post.title}
          <button onClick={() => deletePost(post.id)}>Delete</button>
        </li>
      ))}
    </ul>
  )
}

// From anywhere — invalidate a key without having access to the hook instance
async function createNewPost(data) {
  await fetch('/api/posts', { method: 'POST', body: JSON.stringify(data) })
  mutate('/api/posts')   // global mutate: revalidate the posts list
}
useSWRMutation for Explicit Mutations

For operations like form submissions that should not run automatically, useSWRMutation provides an explicit trigger function:

JSX
import useSWRMutation from 'swr/mutation'

async function createPost(url, { arg }) {
  const res = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(arg),
  })
  if (!res.ok) throw new Error('Failed to create post')
  return res.json()
}

function CreatePostForm() {
  const { trigger, isMutating, error } = useSWRMutation(
    '/api/posts',
    createPost
  )

  async function handleSubmit(e) {
    e.preventDefault()
    const form = new FormData(e.target)
    await trigger({ title: form.get('title'), body: form.get('body') })
    e.target.reset()
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="title" placeholder="Title" required />
      <textarea name="body" placeholder="Body" />
      <button type="submit" disabled={isMutating}>
        {isMutating ? 'Saving…' : 'Create Post'}
      </button>
      {error && <p>Error: {error.message}</p>}
    </form>
  )
}
Infinite Pagination with useSWRInfinite

JSX
import useSWRInfinite from 'swr/infinite'

const PAGE_SIZE = 10

function getKey(pageIndex, previousPageData) {
  if (previousPageData && !previousPageData.length) return null  // end of list
  return `/api/posts?page=${pageIndex + 1}&limit=${PAGE_SIZE}`
}

function InfinitePostList() {
  const { data, size, setSize, isLoading } = useSWRInfinite(getKey, fetcher)

  const posts   = data ? data.flat() : []
  const hasMore = data && data[data.length - 1]?.length === PAGE_SIZE

  if (isLoading) return <p>Loading…</p>

  return (
    <div>
      {posts.map(post => <p key={post.id}>{post.title}</p>)}
      {hasMore && (
        <button onClick={() => setSize(size + 1)}>Load more</button>
      )}
    </div>
  )
}
SWR vs TanStack Query

Feature

SWR

TanStack Query

Bundle size

~4 kB gzipped

~13 kB gzipped

Configuration

Minimal

Highly configurable

Mutations

useSWRMutation (simpler)

useMutation (more features)

Optimistic updates

Manual mutate()

onMutate / setQueryData

DevTools

None built-in

Dedicated devtools panel

SSR / Next.js

First-class support

Good, needs SWR workaround

Infinite queries

useSWRInfinite

useInfiniteQuery

Subscriptions

No

No

Offline support

Basic

Full (v5)

When to Choose SWR
  • Next.js projects — SWR is made by Vercel and integrates seamlessly with Next.js data patterns.

  • Simpler applications — if you primarily need GET requests with caching and deduplication, SWR is less setup.

  • Smaller bundle budgets — SWR is roughly 3x smaller than TanStack Query.

  • Already using Vercel / SWR ecosystem — consistent tooling choice.

  • Choose TanStack Query instead when you need advanced mutation workflows, offline support, the DevTools panel, or fine-grained cache control.

Tip
Both SWR and TanStack Query support pre-fetching server-side data and hydrating the client cache. In Next.js, this means you can render the initial HTML with data on the server, then let SWR/React Query take over on the client — the user sees no loading state at all.