Introduction
Data fetching is one of the most common tasks in any React application. Whether you’re building dashboards, feeds, or analytics tools, your components often depend on external data sources like APIs. However, fetching and managing data efficiently — while maintaining performance and consistency — can be a challenge.
React developers have several tools to handle data fetching, from fetch() and axios to custom hooks and libraries like React Query or SWR. Among them, SWR (which stands for stale-while-revalidate) has gained popularity due to its simplicity, performance, and automatic caching behavior.
Developed by Vercel, the team behind Next.js, SWR provides a reactive data fetching mechanism that ensures your UI is always fast, up-to-date, and reliable. In this post, we’ll explore how SWR works, why it’s beneficial, and how to use it effectively in React applications for optimized data fetching and caching.
What is SWR?
SWR stands for Stale-While-Revalidate, which is a caching strategy originally from the HTTP RFC 5861 standard.
The concept works like this:
- When data is requested, SWR first returns the cached data (stale) immediately.
- Then, it fetches fresh data (revalidate) in the background.
- Once the new data is retrieved, SWR updates the cache and re-renders the component automatically.
This creates a smooth user experience:
- The UI never stays empty while waiting for data.
- Data stays fresh with automatic revalidation.
- Network requests are minimized because of intelligent caching.
Key Benefits of SWR
Using SWR in React applications brings several advantages:
- Automatic Caching
SWR automatically caches fetched data and reuses it between renders. - Stale-While-Revalidate
Returns cached data instantly and revalidates it in the background. - Auto Revalidation
Data is revalidated automatically when the user refocuses the window or reconnects the network. - Performance Optimization
Reduces network calls and speeds up page load times. - Simple API
SWR uses a minimal, hook-based API that integrates perfectly with React’s component model. - Error and Loading State Management
Provides built-in states for loading and error handling without extra configuration. - TypeScript and SSR Support
Works seamlessly with TypeScript and Next.js, making it ideal for modern web apps.
Installing SWR
To start using SWR, you first need to install it in your React project.
npm install swr
or
yarn add swr
Once installed, you can start importing its hooks in your React components.
Basic Usage of SWR
The primary hook provided by SWR is useSWR(). It takes two arguments:
- A key – usually the API endpoint or unique identifier for the resource.
- A fetcher function – a function responsible for fetching the data.
Example: Basic SWR Hook
import useSWR from 'swr';
const fetcher = (url) => fetch(url).then((res) => res.json());
function UserProfile() {
const { data, error, isLoading } = useSWR('https://api.example.com/user/1', fetcher);
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error loading data.</p>;
return (
<div>
<h2>{data.name}</h2>
<p>Email: {data.email}</p>
</div>
);
}
export default UserProfile;
Here’s what happens:
- SWR checks if the data for
'https://api.example.com/user/1'is in the cache. - If cached data exists, it’s returned immediately (stale data).
- SWR then fetches fresh data in the background.
- Once the data arrives, the component automatically re-renders.
Understanding the Fetcher Function
The fetcher function is the core of SWR’s functionality. It defines how the data is fetched.
You can use the native fetch() API, axios, or any other HTTP client.
Example: Using Axios as Fetcher
import useSWR from 'swr';
import axios from 'axios';
const fetcher = (url) => axios.get(url).then((res) => res.data);
function PostsList() {
const { data, error } = useSWR('https://api.example.com/posts', fetcher);
if (error) return <p>Failed to load posts.</p>;
if (!data) return <p>Loading...</p>;
return (
<ul>
{data.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
export default PostsList;
SWR does not depend on any specific HTTP library, making it flexible and easy to integrate into any existing setup.
Handling Loading and Error States
The useSWR hook provides three key states:
data: Contains the fetched or cached data.error: Captures any errors from the fetcher.isLoading: Indicates whether the request is still in progress.
These states make UI feedback simple and declarative.
Revalidation Behavior
SWR automatically revalidates data in several scenarios:
- When the component mounts
SWR fetches fresh data if it’s not in cache. - When the window regains focus
If the user switches tabs and comes back, SWR revalidates data automatically. - When the network reconnects
If the device was offline, SWR refetches data once the connection is restored. - Manual revalidation
You can trigger a revalidation programmatically using themutate()function.
Manual Revalidation with mutate()
The mutate() function allows you to update the cache manually or trigger a revalidation.
Example: Manual Update
import useSWR, { mutate } from 'swr';
const fetcher = (url) => fetch(url).then((res) => res.json());
function RefreshButton() {
const { data } = useSWR('/api/user', fetcher);
return (
<div>
<h3>{data?.name}</h3>
<button onClick={() => mutate('/api/user')}>Refresh Data</button>
</div>
);
}
export default RefreshButton;
When the button is clicked, mutate('/api/user') triggers revalidation and fetches the latest data from the server.
Local Mutations (Optimistic Updates)
SWR supports optimistic UI updates, where the UI updates instantly before the server confirms the change.
Example: Optimistic UI Update
import useSWR, { mutate } from 'swr';
const fetcher = (url) => fetch(url).then((res) => res.json());
function LikePost({ postId }) {
const { data } = useSWR(/api/posts/${postId}, fetcher);
const handleLike = async () => {
mutate(/api/posts/${postId}, { ...data, likes: data.likes + 1 }, false);
await fetch(/api/posts/${postId}/like, { method: 'POST' });
mutate(/api/posts/${postId});
};
if (!data) return <p>Loading...</p>;
return (
<div>
<p>{data.title}</p>
<button onClick={handleLike}>Like ({data.likes})</button>
</div>
);
}
export default LikePost;
Here’s what happens:
- The UI immediately increments the like count.
- The API request runs in the background.
- Once the API confirms, SWR revalidates the data.
This gives users instant feedback without waiting for a server response.
Global Configuration with SWRConfig
You can set up global configurations for all SWR hooks using the SWRConfig provider.
Example: Global Fetcher
import { SWRConfig } from 'swr';
import axios from 'axios';
import App from './App';
const fetcher = (url) => axios.get(url).then((res) => res.data);
function Root() {
return (
<SWRConfig value={{ fetcher, dedupingInterval: 10000 }}>
<App />
</SWRConfig>
);
}
export default Root;
Now, all useSWR() calls in your app will use this fetcher and share the same cache.
Data Deduplication
When multiple components use the same SWR key, only one network request is made.
All components share the same cached data and update simultaneously when revalidated.
This deduplication ensures efficiency and prevents redundant requests.
Pagination and Infinite Loading
SWR provides a special hook called useSWRInfinite() for handling pagination or infinite scroll patterns.
Example: Infinite Loading
import useSWRInfinite from 'swr/infinite';
const fetcher = (url) => fetch(url).then((res) => res.json());
function PaginatedPosts() {
const getKey = (pageIndex, previousPageData) => {
if (previousPageData && !previousPageData.length) return null;
return /api/posts?page=${pageIndex + 1};
};
const { data, size, setSize } = useSWRInfinite(getKey, fetcher);
const posts = data ? [].concat(...data) : [];
return (
<div>
{posts.map((post) => (
<p key={post.id}>{post.title}</p>
))}
<button onClick={() => setSize(size + 1)}>Load More</button>
</div>
);
}
export default PaginatedPosts;
This pattern allows for efficient infinite scrolling where each page is cached and automatically revalidated when needed.
Dependent Data Fetching
SWR supports dependent requests, where one API call depends on another’s data.
Example: Dependent Calls
import useSWR from 'swr';
const fetcher = (url) => fetch(url).then((res) => res.json());
function UserPosts() {
const { data: user } = useSWR('/api/user', fetcher);
const { data: posts } = useSWR(
user ? /api/posts?userId=${user.id} : null,
fetcher
);
if (!user || !posts) return <p>Loading...</p>;
return (
<div>
<h2>{user.name}'s Posts</h2>
{posts.map((post) => (
<p key={post.id}>{post.title}</p>
))}
</div>
);
}
export default UserPosts;
SWR won’t start fetching posts until the user data is available, ensuring efficient and conditional data loading.
SWR with Next.js
SWR integrates perfectly with Next.js, especially for client-side data fetching.
You can use it with static generation (SSG) and server-side rendering (SSR) to combine performance and freshness.
Example: SWR with Next.js Page
import useSWR from 'swr';
const fetcher = (url) => fetch(url).then((res) => res.json());
export default function Home() {
const { data, error } = useSWR('/api/posts', fetcher);
if (error) return <p>Error loading posts.</p>;
if (!data) return <p>Loading...</p>;
return (
<div>
<h1>Latest Posts</h1>
{data.map((post) => (
<p key={post.id}>{post.title}</p>
))}
</div>
);
}
SWR handles the client-side fetching while Next.js manages server-side rendering for improved performance.
SWR vs React Query
| Feature | SWR | React Query |
|---|---|---|
| Developer | Vercel | Tanner Linsley |
| Focus | Simplicity and caching | Comprehensive data management |
| Mutation | Simple via mutate() | Advanced mutation APIs |
| Query Keys | Simple strings | Hierarchical query keys |
| Bundle Size | Smaller | Larger |
| Suitable For | Lightweight apps | Complex state management |
Both are excellent libraries, but SWR is ideal when you want lightweight, declarative, and automatic data fetching with minimal configuration.
Performance Optimizations with SWR
- Adjust
revalidateOnFocusandrevalidateOnReconnect
Disable these for static or low-change data. - Use
dedupingInterval
Prevent repeated fetches within a time interval. - Leverage
fallbackData
Provide default cached data for faster initial rendering. - Combine with Suspense
Integrate SWR’s loading behavior with React’s Suspense for elegant fallbacks.
Example: Advanced SWR Configuration
import { SWRConfig } from 'swr';
import App from './App';
function Root() {
return (
<SWRConfig
value={{
revalidateOnFocus: true,
revalidateOnReconnect: true,
dedupingInterval: 5000,
refreshInterval: 0,
fetcher: (url) => fetch(url).then((res) => res.json()),
}}
>
<App />
</SWRConfig>
);
}
export default Root;
These settings ensure SWR behaves efficiently across your entire app.
Common Mistakes to Avoid
- Using SWR without a unique key – Each resource should have a distinct key for proper caching.
- Not using
nullfor conditional fetching – Prevents premature or unnecessary API calls. - Ignoring cache invalidation – Always revalidate when data updates.
- Over-fetching – Adjust revalidation intervals to balance freshness and performance.
Leave a Reply