coderain guide

Real-Time Data Fetching with React and SWR

Real-time data fetching refers to the practice of dynamically updating application data to reflect the latest state from a server or external source. Traditional approaches (e.g., manual `fetch` calls with `useEffect`) often struggle with: - **Stale data**: Showing outdated information if the user is inactive. - **Loading states**: Poor user experience due to delayed feedback. - **Boilerplate**: Writing重复 code for caching, retries, and error handling. SWR addresses these issues by implementing the *stale-while-revalidate* HTTP caching strategy. This means it first returns cached (stale) data immediately, then sends a background request to fetch fresh data, and finally updates the UI with the new data once it arrives.

In today’s fast-paced digital world, users expect applications to feel responsive and up-to-date. Whether it’s a live dashboard, a social media feed, or a collaborative tool, real-time data fetching is no longer a nice-to-have—it’s a requirement. For React developers, achieving this can be tricky: managing network requests, caching, revalidation, and handling errors often leads to boilerplate code and complex state management.

Enter SWR (short for Stale-While-Revalidate), a lightweight React Hooks library developed by Vercel. SWR simplifies real-time data fetching by abstracting away the complexity of caching, revalidation, and state management. In this blog, we’ll explore how SWR works, its key features, and how to implement real-time data fetching in React applications using SWR.

Table of Contents

  1. Introduction to Real-Time Data Fetching
  2. What is SWR?
  3. Key Features of SWR
  4. Setting Up SWR in a React Project
  5. Basic Data Fetching with SWR
  6. Real-Time Updates with SWR
  7. Advanced SWR Features
  8. Performance Considerations
  9. SWR vs. React Query: A Quick Comparison
  10. Conclusion
  11. References

What is SWR?

SWR is a React Hooks library for data fetching developed by Vercel. The name “SWR” comes from the HTTP cache-control directive stale-while-revalidate, which allows a cache to serve stale data while asynchronously fetching fresh data in the background.

At its core, SWR provides a useSWR hook that takes a unique key (to identify the data) and a fetcher function (to fetch the data), then returns the current state of the request (data, error, loading status, etc.).

Key Features of SWR

SWR simplifies real-time data fetching with these built-in features:

  • Stale-While-Revalidate: Serves cached data immediately and updates in the background.
  • Automatic Revalidation: Refetches data when the window regains focus or the network reconnects.
  • Polling: Configurable interval-based refetching for live data (e.g., dashboards).
  • Caching: In-memory cache to avoid redundant network requests.
  • Deduplication: Ensures multiple components fetching the same data don’t trigger duplicate requests.
  • Error Handling: Automatic retries with configurable backoff strategies.
  • Mutations: Manually update cached data and trigger revalidation.
  • TypeScript Support: Full type safety for data and hooks.

Setting Up SWR in a React Project

Let’s set up SWR in a new or existing React project.

Step 1: Install SWR

Use npm or yarn to install SWR:

npm install swr  
# or  
yarn add swr  

Step 2: Configure a Global Fetcher (Optional)

For consistency, define a reusable fetcher function to handle API requests. For example, using fetch:

// lib/fetcher.js  
export const fetcher = async (url) => {  
  const res = await fetch(url);  

  // If the request failed, throw an error  
  if (!res.ok) {  
    const error = new Error('An error occurred while fetching data');  
    error.status = res.status;  
    throw error;  
  }  

  return res.json();  
};  

Step 3: Wrap Your App with SWRConfig (Optional)

To share global configuration (e.g., default fetcher, retry settings) across all useSWR hooks, wrap your app with SWRConfig:

// App.js  
import { SWRConfig } from 'swr';  
import { fetcher } from './lib/fetcher';  

function App() {  
  return (  
    <SWRConfig  
      value={{  
        fetcher, // Default fetcher for all useSWR hooks  
        revalidateOnFocus: true, // Auto-revalidate when window is focused  
        dedupingInterval: 3000, // Deduplicate requests within 3 seconds  
      }}  
    >  
      <YourAppComponents />  
    </SWRConfig>  
  );  
}  

Basic Data Fetching with SWR

Let’s start with a simple example: fetching a list of posts from a REST API using useSWR.

Example: Fetching Posts

// components/Posts.js  
import useSWR from 'swr';  

function Posts() {  
  // Use SWR to fetch data from the API  
  const { data, error, isLoading } = useSWR('/api/posts', fetcher);  

  // Handle loading state  
  if (isLoading) return <div>Loading posts...</div>;  

  // Handle error state  
  if (error) return <div>Error: {error.message}</div>;  

  // Render data once loaded  
  return (  
    <ul>  
      {data.map((post) => (  
        <li key={post.id}>{post.title}</li>  
      ))}  
    </ul>  
  );  
}  

export default Posts;  

Key Return Values of useSWR

  • data: The fetched data (or cached data if available).
  • error: An error object if the request failed.
  • isLoading: true if the request is in progress and no cached data exists.
  • isValidating: true if SWR is revalidating data in the background (e.g., fetching fresh data while showing stale data).

Real-Time Updates with SWR

SWR makes real-time data updates simple with built-in tools like polling, manual revalidation, and auto-revalidation on focus.

Polling for Continuous Updates

To fetch data at regular intervals (e.g., a live dashboard), use the refreshInterval option.

Example: Live Stock Prices

// components/StockPrice.js  
import useSWR from 'swr';  
import { fetcher } from '../lib/fetcher';  

function StockPrice({ symbol }) {  
  const { data, error } = useSWR(  
    `/api/stocks/${symbol}`, // Unique key for the stock  
    fetcher,  
    { refreshInterval: 5000 } // Refresh every 5 seconds (5000 ms)  
  );  

  if (error) return <div>Failed to load stock data</div>;  
  if (!data) return <div>Loading stock price...</div>;  

  return (  
    <div>  
      <h3>{symbol}: ${data.price}</h3>  
      <p>Last updated: {new Date(data.updatedAt).toLocaleTimeString()}</p>  
    </div>  
  );  
}  

Here, refreshInterval: 5000 ensures the stock price updates every 5 seconds.

Manual Revalidation with mutate

Sometimes you need to trigger revalidation manually (e.g., after submitting a form). Use SWR’s mutate function to update the cache and revalidate data.

Example: Adding a New Post

// components/AddPost.js  
import useSWR, { mutate } from 'swr';  

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

  const handleAddPost = async (newPost) => {  
    // 1. Send new post to the server  
    await fetch('/api/posts', {  
      method: 'POST',  
      headers: { 'Content-Type': 'application/json' },  
      body: JSON.stringify(newPost),  
    });  

    // 2. Trigger revalidation to fetch the updated list of posts  
    mutate('/api/posts'); // Revalidates the data for the key '/api/posts'  
  };  

  return (  
    <button onClick={() => handleAddPost({ title: 'New Post' })}>  
      Add Post  
    </button>  
  );  
}  

mutate('/api/posts') tells SWR to revalidate the data for the key /api/posts, ensuring the UI updates with the new post.

Revalidation on Focus

SWR automatically revalidates data when the window regains focus (enabled by default via revalidateOnFocus: true). This is useful for apps where data might change while the user was inactive (e.g., a messaging app).

To disable this behavior for a specific hook:

useSWR('/api/data', fetcher, { revalidateOnFocus: false });  

Advanced SWR Features

Optimistic Updates

Optimistic updates improve UX by updating the UI before the server confirms the action. If the server request fails, SWR rolls back the cache to the previous state.

Example: Liking a Post Optimistically

// components/LikeButton.js  
import useSWR, { mutate } from 'swr';  

function LikeButton({ postId }) {  
  const { data: post } = useSWR(`/api/posts/${postId}`, fetcher);  

  const handleLike = async () => {  
    // 1. Optimistically update the cache  
    mutate(  
      `/api/posts/${postId}`,  
      { ...post, likes: post.likes + 1 }, // Updated data  
      false // Don't revalidate yet (we'll do it after the request)  
    );  

    try {  
      // 2. Send request to the server  
      await fetch(`/api/posts/${postId}/like`, { method: 'POST' });  
      // 3. Revalidate to ensure cache matches server state  
      mutate(`/api/posts/${postId}`);  
    } catch (error) {  
      // 4. Rollback on error  
      mutate(`/api/posts/${postId}`, post, false);  
      alert('Failed to like post. Please try again.');  
    }  
  };  

  return (  
    <button onClick={handleLike}>  
      Like ({post?.likes || 0})  
    </button>  
  );  
}  

Dependent Fetching

Fetch data that depends on another request (e.g., fetching a user’s posts after fetching the user). Use a null key to pause fetching until the dependency is ready.

Example: Fetching User Posts

// components/UserPosts.js  
import useSWR from 'swr';  

function UserPosts({ userId }) {  
  // Fetch user data first  
  const { data: user } = useSWR(userId ? `/api/users/${userId}` : null, fetcher);  

  // Fetch posts only if user data is available  
  const { data: posts } = useSWR(  
    user ? `/api/users/${user.id}/posts` : null, // Dependent key  
    fetcher  
  );  

  if (!user) return <div>Loading user...</div>;  
  if (!posts) return <div>Loading posts...</div>;  

  return (  
    <div>  
      <h2>Posts by {user.name}</h2>  
      <ul>  
        {posts.map((post) => (  
          <li key={post.id}>{post.title}</li>  
        ))}  
      </ul>  
    </div>  
  );  
}  

Error Handling and Retries

SWR automatically retries failed requests with exponential backoff. Customize retry behavior with options like retryCount and retryDelay:

useSWR('/api/data', fetcher, {  
  retryCount: 3, // Retry 3 times  
  retryDelay: (retryCount) => retryCount * 1000, // 1s, 2s, 3s delays  
  onError: (error) => console.error('Fetch failed:', error), // Custom error callback  
});  

Performance Considerations

  • Caching: SWR caches data in memory, so repeated requests for the same key are served instantly.
  • Deduplication: Multiple components fetching the same key trigger only one network request.
  • Avoid Over-Rendering: Use useSWR’s isValidating to distinguish between initial loading and background revalidation:
    const { data, isLoading, isValidating } = useSWR('/api/data', fetcher);  
    if (isLoading) return <Spinner />; // Show spinner only on initial load  
    if (isValidating) return <div>Updating...</div>; // Show "updating" during revalidation  

SWR vs. React Query: A Quick Comparison

Both SWR and React Query are popular for data fetching, but they differ in focus:

FeatureSWRReact Query
Size~5KB (lightweight)~12KB (more features)
PhilosophyStale-while-revalidateServer state management
Advanced FeaturesLimited (mutate, polling)Rich (query invalidation, pagination, infinite queries)
Learning CurveLowerSlightly steeper

Use SWR if: You want a minimal, easy-to-learn library focused on real-time updates.
Use React Query if: You need advanced server state features (e.g., pagination, infinite scrolling).

Conclusion

SWR simplifies real-time data fetching in React by abstracting caching, revalidation, and error handling into a single hook. Its stale-while-revalidate pattern ensures fast UI updates, while features like polling, optimistic updates, and dependent fetching make it versatile for modern apps.

Whether you’re building a live dashboard, a social media feed, or a collaborative tool, SWR helps you deliver a responsive and up-to-date user experience with minimal code.

References