Fetching, caching and revalidating server-side data with SWR
Tags: webdev, tutorial, api, codenewbie
Published: 2023-09-19 18:51:08

In this tutorial, you will learn how to use SWR to fetch data in a React application. SWR is a library for data fetching, revalidating, and caching.

By the end of the article you will know:

* What is SWR?
    
* The problem SWR solves?
    
* How to set up SWR in your React app
    
* How to use SWR to fetch data in a React component
    
* How to use SWR for Pagination, auto revalidation, caching, etc.
    

## Prerequisites

Before you begin you should be familiar with:

* Basic understanding of JavaScript concepts such as the fetch method
    
* React hooks
    
* React components
    

## What is SWR?

SWR is a React Hooks for data fetching. It is derived from `stale-while-revalidate` an HTTP `cache-control` mechanism that allows a web browser to cache a response for a resource on a server and store a copy of the response in the browser's local storage.

This allows the browser to display the cached response to the user without further request to the server. Periodically, the browser revalidates the cache to ensure responses are still up-to-date.

There is a `Cache-Control` header returned by the server to specify the duration the browser should use the cache while in the background revalidating the data with the server. As a result, improving the performance of a web app

The strategy behind SWR is:

1. Display the cached response to the client for a specified duration( in seconds)
    
2. **Revalidates** the data (i.e. request fresh data) asynchronously while still displaying the stale data.
    
3. Get the up-to-date data and display the response to the user.

![swr react-hooks](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lweyf3oayj0vzyebwifp.jpg)    

## What problem does SWR solve?

The Axios library and the `fetch` APIs are great options used to request resources on a server. However, whenever you initiate a request for a resource, there is a waiting period for the response.

In a React app, you generally handle this waiting period with the loading state or loading animation on the UI. When you get the response, you will use the `set` function to update the state of your app, which triggers a re-render of your components.

There are several issues with this approach:

* Whenever the component is mounted, you will need to make another request to the API endpoint for data.
    
* When a response is returned by the server, you will need to perform the extra task of caching and paginating data as `fetch` and `axios` does not automate caching and pagination
    

To solve these challenges, you will use a React hook called `useSWR`. It provides an easier approach to fetch data from a server, cache , and paginate it.

### **Why Do You Need To Use SWR?**

Below are some benefits of using SWR in your React app:

* **Ease of use:** SWR provides a straightforward API that makes it easy to fetch data from a server and cache it.
    
* **Automatic caching:** SWR automatically caches data in the browser's local storage and displays the cached response to the user. This reduces the number of requests made to the server for a resource. As a result, it improves the performance of your app.
    
* **Auto-revalidation**: SWR ensures users view the most **up-to-date data**. Regardless of the number of tabs opened, the data will always be in sync when the tab is refocused.
    
* **Stale-while-revalidate (SWR) caching strategy:** SWR uses a stale-while-revalidate (SWR) caching strategy, which means that the cached data is used even after it has expired until the new data has been fetched from the server. This ensures that the user never sees stale data.
    
* **Error handling:** SWR handles errors neatly. If the request fails, the user is not presented with an error message.
    

SWR has various hooks that can be used to improve the performance of your app. The most basic hook is the `useSWR` which can be used for data fetching.

A basic example using the `useSWR` hook is shown below:

```javascript
const { data, error } = useSWR(key, fetcher);
```

## Data fetching with `fetch` vs `useSWR`

Let's use the `fetch` API to fetch data and compare it to how to fetch data using `useSWR`

**Fetching data with the** `fetch` **API**

Generally, to fetch data using the in-built JavaScript `fetch` method in your React app, you will follow these steps:

* Import `useEffect` and `useState` hooks from React
    
* Fetch the data once using the `useEffect` in the parent component.
    
* In the callback function of the `useEffect()`, call the `fetch()` method and pass the API endpoint as the argument.
    
* Update the `state` variable with the data.
    
* Now, all child components can access the fetched data when passed as props
    

The code snippet below fetches a list of products using the `fetch` method and pass the data as `props` in the `Products` component.

```javascript
import { useEffect, useState } from "react";
import Products from "./Products";

export default function App() {
  const [products, setProducts] = useState("");
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState("");

  useEffect(() => {
    fetch("https://fakestoreapi.com/products")
      .then((res) => res.json())
      .then((data) => {
        setIsLoading(false);
        setProducts(data);
      })
      .catch((err) => setError(err));
  }, []);

  //check if data has been fetched
  console.log(products);

  //check if error
  console.log(error);

  return (
    <div className="App">
      {isLoading && <h2>Fetching data... </h2>}
      <Products products={products} />
    </div>
  );
}
```

There are some issues with this approach:

* There is too much code to write just to handle data fetching. This code will be difficult to maintain if we keep adding data dependency to the page
    
* You will need to keep all the data fetching in the parent component and pass it down to child components via `props`
    
* You will need to manage a lot of `state`: Loading, error, and the fetched data. Whenever the state is updated, the parent component as well as the child components need to re-render to reflect the changes. This unnecessary re-rendering decreases the performance of the application.
    

Now, let's see how to fetch the same data using SWR. You can find the code snippet on the [data-fetching branch](https://github.com/emmanuelkumah/swr-react-tuts/blob/data-Fetching/src/App.jsx) on the GitHub repo.

<img width="100%" style="width:100%" src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExcjczbnliaGNsbXZseWVnOXh4aTMzdWl6YXU1ZGE3dWVtcXA3cWQ2NiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/pxgCV0AdfYyDfVo5Cn/giphy.gif">



**Follow these steps to fetch data with the** `useSWR` **hook :**

* Inside your React app run the command: `npm i swr` . This will install SWR in your React app.
    
* Navigate to the component to fetch data (eg. `<App/>`). Type `import useSWR from "swr"` at the top of the component.
    
* Within the `<App/>` component, create a `fetcher` function. The `fetcher` is an async function that **accepts the API endpoint** as a parameter to fetch the data.
    
* In the body of the `fetcher` function , you can use any library like `fetch` and `axios` to handle data fetching.
    

Below is the code snippet for the fetcher function

```javascript
//fetch data using fetch api
const fetcher = url => fetch(url).then(res => res.json())
```

* In the body of your top-level component, type the code below
    
    ```javascript
    //App.js
    const { data, error, isLoading, isValidating, mutate } = useSWR(key, fetcher, options)
    ```
    
    * At the right hand side of the expression, we call the `useSWR()` hook. It accepts the following arguments
        
        * The `key` is a unique string representing the URL to the API endpoint.
            
        * The `fetcher` will pass the `key` to the `fetcher` function and then proceed to fetch the data.
            
        * `option` is an object of options to help with data fetching
            
* On the left hand side of the expression , we use the array `destructuring` to extract the fetched `data`, `error`, and `isLoading` and `mutate` response.
    
    `const { data, error, isLoading, mutate } = useSWR(key, fetcher, options)`
    
* Now that you have access to the `data` , you can use it in the required component. In this example, we are passing the `data` to the `<Products/>` component.
    

The code snippet to fetch data from the API is as below:

```javascript
import "./App.css";
import Products from "./components/Products/Products";
import useSWR from "swr";

//function to fetch data
const fetcher = (...args) => fetch(...args).then((res) => res.json());
//api endpoint
const url = "https://fakestoreapi.com/products";

function App() {
  const { data, error, isLoading } = useSWR(url, fetcher);

  if (error) return <p>Error loading data</p>;
  if (isLoading) return <div>loading...</div>;
  return (
    <>
      <main>
        <h1>Data fetching with useSWR </h1>
        <Products data={data} />
      </main>
    </>
  );
}

export default App;
```

Now that the `Products` component has access to the `data`, we can use it to display the individual products.

Calling the `useSWR()` method returns the following:

* `data`: This represents the response or result returned by the `fetcher` function
    
* `error`: error that is thrown by the `fetcher` if unable to fetch the data
    
* `isLoading`: This helps you display a loading state if there's an ongoing request.
    
* `isValidating`: If there is a request or revalidation loading
    
* `mutate`: This is a function to modify the cached data.
    

Using `useSWR` hook :

* It reduces the amount of code to write to fetch data resulting in clean and maintainable code
    
* No need to manage a lot of `state` and `side effects` with `useState` and `useEffect` hooks
    

In the next section, we will use `useSWR` hook to paginate data.

## Handling Pagination with `SWR`

To handle pagination in React, you will generally use a `react-paginate` package or any suitable alternative. However, the `useSWR` hook automatically handles pagination.

In this section, we will demonstrate pagination using the [Rick and Morty Character API](https://rickandmortyapi.com/documentation/#introduction).

You can find the code in this [pagination branch](https://github.com/emmanuelkumah/swr-react-tuts/tree/pagination) repo.

Below is the final version of the application

<img width="100%" style="width:100%" src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExcWR0bHk3M2t3Y2IzdG1jbG56b2M4OTZod2J6bXpzMHVsdnpjczNlYiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/n41TU0hnuYX22bml0T/giphy.gif">


Let's get started:

* Create a "Characters" sub-folder in the "Component" folder of your React app
    
* Create a `Characters.jsx` and `SingleCharacter.jsx` files inside the "Characters" folder
    

Type the code below into your `Characters.jsx` file

```javascript
//component/characters/Characters.jsx 
import React, { useState } from "react";
import useSWR from "swr";
import SingleCharacter from "./SingleCharacter";
import "../../../App.css";

const Characters = () => {
  const [pageIndex, setPageIndex] = useState(1);

  const fetcher = (...args) => fetch(...args).then((res) => res.json());
   
// The API URL includes the page index, which is a React state.
  const { data, error, isLoading } = useSWR(
    `https://rickandmortyapi.com/api/character/?page=${pageIndex}`,
    fetcher
  );

  if (error) return <h3>Failed to fetch characters</h3>;

  if (isLoading) return <h3>Loading characters...</h3>;

  return (
    <div>
      <section className="character_card">
        {data.results.map((character) => (
          <div className="character_card__item">
            <SingleCharacter key={character.id} character={character} />
          </div>
        ))}
      </section>
      <div>
        <button onClick={() => setPageIndex(pageIndex - 1)}>Previous</button>
        <button
          className="character_btn"
          onClick={() => setPageIndex(pageIndex + 1)}
        >
          Next
        </button>
      </div>
    </div>
  );
};

export default Characters;
```

In the code snippet we:

* Import the `useSWR from "swr"` and `useState from "react"`
    
* `useState` helps us to keep track of the page number to be used to navigate to the next page or previous page.
    
* `useSWR` enables us to fetch data from the API endpoint. It accepts the API endpoint and `fetcher` functions as arguments. The endpoint will return 20 characters per request.
    
* The response from the API is assigned to the `data`.
    
* Finally, we iterate over each item in the array and display a single character with the `SingleCharacter.jsx` component.
    

To handle **pagination:**

* We defined the "next" and "previous" buttons in the `<App/>` component
    
* Whenever you click on the "next" button, we update the `pageIndex` ( `setState(pageIndex + 1)`). This initiates another request to the API endpoint for the next page.
    
* Similarly, whenever you click on the "previous" button, we decrease the `pageIndex` (`setState(pageIndex -1 )`.
    

```javascript
//code snippet for previous and next page
...
    <div>
        <button onClick={() => setPageIndex(pageIndex - 1)}>Previous</button>
        <button
          className="character_btn"
          onClick={() => setPageIndex(pageIndex + 1)}
        >
          Next
        </button>
      </div>
...
```

In the next section, you will learn how to prefetch data in SWR

## Prefetching Data

SWR can be used to preload data before rendering a component. It has `preload` API to prefetch resources and store the results in the cache. With that, all incoming requests for the same URL will reload the cached data. This prevents waterfalls in your application (waterfalls occur when data takes a long time to load, slowing down your applications).

For instance, you can preload all comments to a post from a CMS. Whenever the user clicks on the "show comments" button it displays the cached data, resulting in a faster and smoother user experience.

The `preload` accepts `key` and `fetcher` as the arguments. You can call `preload` even outside of React.

Below is the syntax for the `preload` API:

```javascript
preload(apiEndPoint, fetcher)
```

The code for this section is in the [preload](https://github.com/emmanuelkumah/swr-react-tuts/tree/preload) branch of the GitHub repo

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1694177455952/b9d31cb2-456f-46d7-802a-31407fb8fd84.gif align="center")

Below is the code snippet to preload comments

```javascript
import useSWR, { preload } from "swr";

const fetcher = (url) => fetch(url).then((res) => res.json());

// Preload the resource before rendering the Comments component below,
// this prevents potential waterfalls in your application.
preload("https://jsonplaceholder.typicode.com/posts/1/comments", fetcher);

const Comments = () => {
  const { data, isLoading, error } = useSWR(
    "https://jsonplaceholder.typicode.com/posts/1/comments",
    fetcher
  );

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error loading data</div>;
  return (
    <div>
      {data.map((comment) => (
        <div className="comments_card" key={comment.id}>
          <h3>{comment.body}</h3>
          <p>
            <span>author</span>
            {comment.name}
          </p>
        </div>
      ))}
    </div>
  );
};

export default Comments;
```

## Auto Revalidation

Revalidation involves clearing the cache data in the local storage and retrieving the latest data from the server.

A use case is when data changes in your app and you want to ensure you display the most up-to-date data.

There are several approaches to revalidating data:

* Revalidate on focus
    
* Revalidate on interval
    
* Revalidate on reconnect
    

### Revalidate on Focus

This automatically revalidates the data to immediately sync to the latest state whenever you switch between tabs or re-focus a page. This is helpful for refreshing data when the laptop "goes to sleep", or a tab is suspended.

In the screenshot below, you will notice that in the "network" section of the DevTools, whenever you switch between tabs, a request is made to revalidate the data and fetch the latest data (if any)

<img width="100%" style="width:100%" src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbXlndDJ1ZTV6eTJzcXl3MjM3dWNsNjJzdnNrenhua254ZW1odnFvNSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/PcVYgJOBjhmN5hYAMG/giphy.gif">


With the example below, whenever the page is refocused, a network request is made to automatically revalidate the daa to ensure the latest data is displayed to the user.



![SWR revalidate](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zhvm7apsg0w52n3lih9b.gif)
*(Demonstrating revalidation of data when the tap is suspended)*

### Revalidate on interval

Whenever you have multiple tabs opened, for the same application, the data for each tab may vary. To keep all the tabs up-to-date, SWR provides the option to automatically re-fetch data at an interval. The re-fetching happens only when the component associated with the hook is on screen.

For instance, you have opened a Todo App on multiple tabs, and on the current tab, you added a new todo. Whenever the item is added, both tabs will eventually render the same data even though the action happened on the active tab.

To enable this feature, call the `useSWR()` method, pass the `refreshInterval` as an object of `option` and set `refreshInterval` value in milliseconds

The syntax is as below:

```javascript
useSWR('/api/todos', fetcher, { refreshInterval: 1000 })
```

* `{ refreshInterval: 1000 }` tells SWR to refresh the data every 1 second.
    

### Revalidate on reconnect

In a scenario where the computer has lost connection to the internet, the data can be revalidated when the network connection is restored.

In the screenshot below, the network is offline, and when it is restored, SWR initiate a request to the API to fetch the latest data.
<img width="100%" style="width:100%" src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExMjh0ano1NHE1dXF6ZTJmemc2c3d5MWgyNnY3aXl5M3ByYmMwZTJlaiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/Vz1YZmQAZFdu0QZrnI/giphy.gif">


## Automatic Caching

A key benefit of SWR is to automatically cached data from the server and display the response from the local storage.

You can use the DevTools to verfiy if a network request is cached:

* **Check the Size column in the Network tab.** If the "Size" column says `disk cache` or `Memory cache`, then the response was served from the cache.
    


![Cached data](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q1vzenjz8g3jg04mfhj7.png)

## Global Configuration with SWR

Whenever the same `fetcher` function is needed to fetch resources, you can use the context `SWRConfig` to provide global configuration for all SWR hooks. The `SWRConfig` servers as a wrapper component allowing all child components to access the value assigned to the `SWRConfig`.

The syntax is as below:

```javascript
<SWRConfig value={options}> //options to be available to all components
  <Component/>
</SWRConfig>
```

In the example below, all SWR hooks can use the same `fetcher` function to load `JSON` data from an API endpoint.

```javascript
//main.jsx
import React from 'react'
import ReactDOM from 'react-dom'
import { SWRConfig } from 'swr'
import App from './App'
import './index.css'

//common fetcher function
const fetcher = (...args) => fetch(...args).then((res) => res.json())

ReactDOM.render(
    <React.StrictMode>
        <SWRConfig value={{ fetcher: fetcher }}>
            <App />
        </SWRConfig>
    </React.StrictMode>,
    document.getElementById('root')
)

// App.js
import useSWR, { SWRConfig } from 'swr'
 //no fetcher function needed here
function App () {
  const { data: events } = useSWR('/api/events')
  const { data: projects } = useSWR('/api/projects')
  const { data: user } = useSWR('/api/user') 
 
}
```

Instead of declaring the `fetcher` function in every file, we created it in the `main.jsx` and passed it as a value to `SWRConfig`. Now, the `App.js` and all its child components can fetch data without the need to create the `fetcher` function again to avoid redundancy.

## Error handling and revalidation

If an error is thrown in the `fetcher` , it will be returned as `error` by the hook.

The code snippet is as below:

```javascript
const fetcher = url => fetch(url).then(res => res.json())
 
// ...
const { data, error } = useSWR('/api/user', fetcher)

if(error) return <div>Error loading data </div>
```

In the event of an error, the last successfully generated data will continue to be served from the cache. On the next subsequent request, the app will retry revalidating the data.

## Summary

In this tutorial you used `useSWR` hook to fetch and paginate data. You learned how to auto-revalidate data when the tab is refocused or when offline. To further deepen your knowledge learn how to use [`react-query`](https://tanstack.com/query/v3/) or [`rtk-query`](https://redux-toolkit.js.org/rtk-query/overview) to fetch data in your app