Improve Data Fetching in Next.js Applications through SWR
Tags: programming, react, nextjs
Published: 2023-05-19 19:50:04

<mark>This article was originally published </mark> [<mark>here</mark>](https://soshace.com/using-swr-for-efficient-data-fetching-in-next-js-applications/)

Fetching and rendering data from APIs is one of the core essentials of front-end development. The basic way of fetching data in JavaScript is to use local `fetch` or a third-party library like `axios`, input the right HTTP method for the request, including headers, parse the result in JSON, and render it to the DOM. This wouldn’t work well with modern web applications because of their architectures' complexity. Users will only use a web app that’s fast, reliable, and responsive; that means if they request a resource, they want it delivered in &lt;3s. As a result, developers need to use tools or libraries that improve the data-fetching experience in their applications.

In React, data fetching is a side effect, and it provides the `useEffect` Hook for performing this side effect. Data fetching in React typically would look like this:

```jsx
function Example() {
  const [data, setData] = useState(null);

  useEffect(() => {
    async function fetchData() {
      const response = await fetch('https://api.example.com/data');
      const result = await response.json();
      setData(result);
    }

    fetchData();
  }, []); // The empty array `[]` ensures the effect only runs once on mount and unmount.

  return (
    <div>
      {data ? (
        <div>{JSON.stringify(data)}</div>
      ) : (
        <div>Loading...</div>
      )}
    </div>
  );
}
```

However, when building server-rendered applications, Nextjs is preferred. Data fetching in Next.js can be done using different methods, depending on the desired rendering strategy, that is:

* When you want to fetch data at build time and generate static HTML pages, Nextjs provides `getStaticProps` for that.
    
* When you have dynamic routes and want to generate static HTML pages for each route, use `getStaticPaths`.
    
* When you need to fetch data on each request, providing a server-rendered experience, use `getServerSideProps`
    
* You can still use client-side data fetching when you don't need to pre-render the data or when you want to fetch data that depends on user interactions.
    

It is common to see Next.js applications that make use of client-side data fetching. The challenge with this technique of data fetching is that you have to render data based on user interaction, which can lead to several issues if not handled properly.

This is why Vercel created SWR (stale-while-revalidate). Without a solution like SWR, you’re likely to face difficulties managing caching, ensuring data synchronization, handling errors, and providing real-time updates. Additionally, handling loading states can become cumbersome, and you might end up with a lot of boilerplate code just for fetching, caching, and managing the data as the codebase grows. SWR addresses these challenges by providing built-in caching, automatic cache revalidation, error retries, support for real-time updates, and a Hooks-based API that simplifies data management.

In this article, I’ll introduce you to how SWR works, its key concepts, and how to use it for efficient data fetching in client-side Next.js applications.

## How SWR works

To understand how SWR works, you need to be conversant with these key concepts.

1. Caching: Caching is like storing food in the fridge. It's a way to save data so it can be quickly accessed later without needing to fetch it from the source (server) every time. This speeds up the process of retrieving data and reduces the load on the server.
    
2. Revalidation: Revalidation is like checking if the food in the fridge is still good or needs replacing with a fresh meal. In the context of data fetching, revalidation means checking if the cached data is still valid or if it needs to be updated with new data from the server. With SWR, this process happens automatically in the background, ensuring your data is always up-to-date.
    
3. Stale-While-Revalidate: Imagine you have a fridge with food inside. When you're hungry, you grab something from the fridge (cached data). At the same time, a friend starts cooking a fresh meal (fetching new data). You eat the food from the fridge while waiting for the fresh meal to be ready. Once done, the fridge is restocked with fresh food (cache revalidation).
    

SWR is a data fetching library that implements the Stale-While-Revalidate (SWR) strategy. It fetches, caches, and revalidates data in the background to provide an efficient and seamless user experience.

## What we’ll be building

To appreciate SWR, you need to build something with it. In this tutorial, we’ll build a product store with Nextjs. While building this demo app, you’ll get to learn the following:

* Fetching data using the `useSWR` hook
    
* Handling Errors and Loading States
    
* Implementing optimistic UI updates with SWR
    
* Implementing infinite scrolling in Next.js with SWR
    

### Pre-requisites

You’ll need the following:

* Nodejs ≥v16
    
* Code editor (preferably VS code)
    
* Code terminal
    
* Package manager (yarn preferably)
    
* Knowledge of JavaScript and Reactjs
    

The complete code is on [Github](https://github.com/Umoren/product-store-swr), and the demo app is [here](https://product-store-swr.vercel.app/).

### Getting Started

Run this command on your terminal to create a nextjs project.

```bash
npx create-next-app product-store; cd product-store; code .
```

The product store will have a `/product` parent route with these sub-routes: `/product/listing` and `/product/detail`. Run this command from the root directory on your terminal to create these page routes.

```bash
cd pages; mkdir product; cd product; touch [id].js upload.js
```

Go back to the root directory and create these UI components

```bash
mkdir components; cd components; touch ProductList.jsx ProductCard.jsx ErrorMessage.jsx LoadingIndicator.jsx ProductUploadForm.jsx
```

Navigate back to the root director and install these packages:

```bash
yarn add swr tailwindcss postcss autoprefixer react-infinite-scroll-component;
```

See how to configure tailwindcss [here](https://tailwindcss.com/docs/guides/nextjs).

### Fetching, Displaying, and Updating Data with SWR

Firstly, let’s create a custom React hook that’ll fetch data from the Products fakeapi endpoint using SWR. Add these lines of code to `hooks/useFetch.js`

```jsx
import useSWR from 'swr';

const fetcher = async (url) => {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error('An error occurred while fetching the data.');
  }
  return response.json();
};

const useFetch = (path) => {
  const { data, error } = useSWR(`https://fakestoreapi.com/${path}`, fetcher);

  const isLoading = !data && !error;

  return { data, error, isLoading };
};

export default useFetch;
```

Here, we define a custom React hook, `useFetch`, which leverages the `useSWR` hook for data fetching. It takes a `path` as input and constructs a full `URL` to fetch data from the Fake Store API. The fetcher function handles making the request and error checking. `useFetch` returns an object with the fetched data, potential errors, and a loading state.

Now let’s display the product list.

Navigate to `components/ProductCard` and add these lines of code.

```jsx
import Link from 'next/link';
import Image from 'next/image';

const ProductCard = ({ product }) => {
    return (
        <div className="bg-white rounded-lg shadow-md p-4">
            <Image
                src={product.image}
                alt={product.title}
                width={640}
                height={640}
                layout="responsive"
            />
            <h3 className="text-lg font-semibold mb-2">{product.title}</h3>
            <p className="text-gray-600 mb-2">{product.category}</p>
            <div className="flex justify-between items-center">
                <span className="text-lg font-bold">${product.price}</span>
                <Link
                    href={`/product/${product.id}`}
                    className="text-blue-600 hover:text-blue-800"
                >
                    View Details
                </Link>
            </div>
        </div>
    );
};

export default ProductCard;
```

This ProductCard component renders a single product card. The card includes an image, product title, category, price, and a link to the product details page. The image is set using Next.js' Image component, which automatically optimizes the image for performance. The link to the product details page is wrapped in Next.js' Link component for client-side navigation.

Navigate to `component/ProductList.jsx` and add these lines of code.

```jsx
import useFetch from './hooks/useFetch';
import ProductCard from './ProductCard';

const ProductList = () => {
  const { data: products, error, isLoading } = useFetch('products');

  if (isLoading) {
    return <div>Loading...</div>;
  }

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

  return (
    <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
      {products.map((product) => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
};

export default ProductList;
```

Here, we define the `ProductList` component, which fetches and displays a list of products. It uses the `useFetch` custom hook to fetch `product` data and `ProductCard` to render each product. The component handles `loading` and `error` states and displays the products in a grid format.

Update `pages/index.js` with these lines of code.

```jsx
import ProductList from "../components/ProductList"

export default function Home() {
  return (
    <div className='max-w-screen-xl m-auto'>
      <ProductList />
    </div>
  )
}
```

Run the dev server with this command `yarn dev`, it should start the server at `[http://localhost:3000](http://localhost:3000)`, and you should see something like this on your browser.

![browser displaying the product listing page of the demo application, featuring a grid of product cards with images, names, categories, and prices](https://res.cloudinary.com/sammy365/image/upload/v1680880231/productStore1_ias7b1.png align="left")

Great! We’ve been able to display the products. Let’s now implement the view for a single product. Update `pages/product/[id].js` with these lines of code. Here we’ll use `useSWR` directly.

```jsx
import useSWR from "swr";
import Image from "next/image";

const fetcher = async (url) => {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error("An error occurred while fetching the data.");
  }
  return response.json();
};

const ProductDetails = ({ id }) => {
  const {
    data: product,
    error,
    isLoading,
  } = useSWR(`https://fakestoreapi.com/products/${id}`, fetcher);

  if (isLoading) {
    return <div>Loading...</div>;
  }

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

  return (
    <div className="max-w-2xl mx-auto min-h-screen py-16">
      <h1 className="text-2xl font-semibold mb-4">{product.title}</h1>
      <div className="md:flex space-x-8">
        <div>
          <Image
            src={product.image}
            alt={product.title}
            width={640}
            height={640}
            layout="responsive"
          />
        </div>
        <div>
          <p className="text-gray-100 mb-2">{product.category}</p>
          <p className="text-xl font-bold mb-4">${product.price}</p>
          <p>{product.description}</p>
        </div>
      </div>
    </div>
  );
};

export async function getServerSideProps(context) {
  const { id } = context.query;
  return {
    props: {
      id,
    },
  };
}
export default ProductDetails;
```

This component handles `loading` and `error` states and renders product information. The `getServerSideProps` function retrieves the product ID from the query params, which is passed as a prop to the component for server-side rendering. A single product detail look like this:

![single product detail page on the demo application, showcasing a larger product image, name, category, price, and a detailed product description.](https://res.cloudinary.com/sammy365/image/upload/v1680880483/ProductDetails_snqnao.png align="left")

### Handling Errors and Loading States

SWR provides an `ErrorBoundary` component that can be used to catch errors that occur within its child components. This component can be used to display a fallback UI when an error occurs, such as an error message or a reload button. The Error boundary provides a way to separate an application's data-fetching and data-rendering concerns. When an error occurs during data fetching, SWR Error Boundary can intercept and handle it gracefully. This way, error messages can be displayed to the user without breaking the entire application.

```jsx
import useSWR from 'swr';
import { SWRConfig } from 'swr';
import { ErrorBoundary } from 'react-error-boundary';

function App() {
  const { data, error } = useSWR('/api/data');

  if (error) {
    throw new Error('Failed to load data');
  }

  return <div>{data}</div>;
}

function AppWrapper() {
  return (
    <ErrorBoundary fallback={<div>Failed to load data</div>}>
      <SWRConfig value={{}}>
        <App />
      </SWRConfig>
    </ErrorBoundary>
  );
}
```

In this code snippet, if an error occurs during the fetching process, it throws an error, which is caught by the `ErrorBoundary` component. The `fallback` prop of the `ErrorBoundary` component is a UI to be displayed when an error occurs. In this case, it displays a simple error message.

### Implementing loading indicators for a better user experience

Let’s go back to building our app. Go to `component/LoadingIndicator.jsx` and add these lines of code.

```jsx
const LoadingIndicator = () => {
  return (
    <div className="flex justify-center items-center">
      <div className="animate-spin rounded-full h-10 w-10 border-b-2 border-blue-500"></div>
    </div>
  );
};

export default LoadingIndicator;
```

This LoadingIndicator component will be used to represent the loading states while fetching data. Head to `components/ProductList.jsx` and modify the code to this:

```jsx
if (isLoading) {
    return <LoadingIndicator />;
  }
```

Let’s also create an ErrorMessage component that renders an error message when there are data fetching issues. Head to `components/ErrorMessage.jsx`:

```jsx
const ErrorMessage = ({ message }) => {
  return (
    <div
      className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative"
      role="alert"
    >
      <strong className="font-bold">Error: </strong>
      <span className="block sm:inline">{message}</span>
    </div>
  );
};

export default ErrorMessage;
```

Update `component/ProductList.jsx` like this:

```jsx
if (error) {
    return <ErrorMessage message={error.message} />;
  }
```

### Optimistic UI updates

When you POST a message in messaging apps, it is immediately displayed in the chat even if there’s no network connection. That’s the idea of optimistic UI updates, and this can be implemented with SWR. With SWR, you can update the local cache of the data in response to user actions while at the same time sending the update request to the server. If the server responds with an error, the local cache can be reverted back to its previous state. This way, the user gets an immediate response while still having the assurance that the data is being updated on the server.

To implement optimistic UI updates with SWR, you need to use the `mutate` function provided by the hook. The `mutate` function allows you to update the cache of the data without making a request to the server. You can pass an updater function to the `mutate` function, which receives the current data as an argument, and returns the updated data. The `mutate` function updates the local cache with the new data, and triggers a re-render of the component.

Once the update request is sent to the server, you can use the `mutate` function again to update the cache with the response from the server. If the server responds with an error, you can use the `mutate` function with the previous data to revert the cache to its previous state.

This is how to implement optimistic UI updates in our app while uploading a new product.

`pages/product/upload.js`

```jsx
import ProductUploadForm from "../../components/ProductUploadForm";
import { mutate } from "swr";
import axios from "axios";
import ErrorMessage from "../../components/ErrorMessage";

const upload = () => {
  const onUpload = async (newProduct) => {
    // Optimistically update the local data
    mutate(
      "/products",
      (products) => {
        if (Array.isArray(products)) {
          return [newProduct, ...products];
        }
        return [newProduct];
      },
      false
    );

    // Make the API call to create the product
    try {
      await axios.post("https://fakestoreapi.com/products", newProduct);

      // Revalidate the data after successful upload
      mutate("/products");
    } catch (error) {
      // Handle any errors
      <ErrorMessage message={error} />;
    }
  };

  return (
    <div>
      <h1 className="mb-4 text-2xl  p-3 font-extrabold text-gray-900 dark:text-white md:text-3xl lg:text-4xl">
        {" "}
        Upload Product
      </h1>
      <ProductUploadForm onUpload={onUpload} />
    </div>
  );
};

export default upload;
```

Check out the `ProductUploadForm.jsx` [here](https://github.com/Umoren/product-store-swr/blob/master/components/ProductUploadForm.jsx).

### Using SWR for paginated data fetching

Paginated data fetching is one of the use cases of SWR. If we're fetching a large amount of data, breaking it down into smaller chunks called pages improves performance and reduces the amount of data transferred over the network.

The `useSWRInfinite` hook implements pagination with SWR. It takes two arguments: `getKey` and `fetcher`.

The `getKey` function returns a unique key for each page based on the page index and previous page data. Returning `null` for an empty page prevents unnecessary requests.

The `fetcher` function fetches data for a given key using an HTTP client, like `axios` or `fetch`.

Once we set up the `useSWRInfinite` hook, we can use the `data` and `error` properties to render the data and handle errors, respectively. We can also use the `isLoading` property to show a loading indicator while data is being fetched.

To implement pagination, we use the `size` and `setSize` properties to control the number of pages to fetch. Incrementing the `size` value in a `loadMore` function that's called when the user reaches the end of the current page enables pagination. We also use the `hasNextPage` property to determine if more data can be fetched.

### Implementing infinite scrolling in Next.js with SWR

Update the `useFetch` hook with these lines of code:

```jsx
import useSWRInfinite from "swr/infinite";

const fetcher = async (url) => {
    const response = await fetch(url);
    if (!response.ok) {
        throw new Error('An error occurred while fetching the data.');
    }
    return await response.json();
};

const useFetch = (path, limit) => {
    const getKey = (pageIndex, previousPageData) => {
        if (previousPageData && !previousPageData.length) return null;
        const pageNumber = pageIndex + 1;
        return `https://fakestoreapi.com/${path}?_page=${pageNumber}&_limit=${limit}`;
    };

    const { data, error, size, setSize } = useSWRInfinite(getKey, fetcher);

    const loadMore = () => setSize(size + 1);

    return {
        data: data ? data.flat() : [],
        isLoading: !error && !data,
        isError: error,
        loadMore,
        hasNextPage: data && data[data.length - 1]?.length === limit,
    };
};

export default useFetch;
```

In this code snippet is a custom hook that uses the SWR library to fetch paginated data. It takes two arguments, the `path`, and `limit` . The `getKey` function returns a unique key for each page based on the page index and previous page data. The hook uses the `useSWRInfinite` function to fetch data for a given key using an HTTP client. It returns an object with `data`, `isLoading`, `isError` , `loadMore` , and `hasNextPage` properties. The `data` property is an array of fetched data, while `isLoading` is a boolean value that indicates if the data is being fetched. `isError` is a boolean value that indicates if an error occurred while fetching data. `loadMore` is a function that increments the number of pages to fetch, and `hasNextPage` is a boolean value that indicates if there's more data to be fetched. The `fetcher` function is called to fetch data from a given URL, and it throws an error if the response is not successful.

The app should work properly with infinite scrolling now:

{% embed https://youtu.be/GzPe_OeNYxY %}

## Wrapping up…

SWR is a powerful tool for efficient data fetching in client-side Next.js applications. Its built-in caching, automatic cache revalidation, error retries, support for real-time updates, and Hooks-based API make data management simpler and more streamlined. By using SWR, developers can improve the data-fetching experience for their users and ensure their applications are fast, reliable, and responsive. With Next.js, there are different methods of data fetching, depending on the desired rendering strategy. While client-side data fetching is a popular option, it can lead to several issues if not handled properly. SWR addresses these challenges and provides a more efficient way of managing data.

Overall, SWR is a great tool for building high-performance, scalable, and reliable web applications with React and Next.js.

## Resources

1. SWR Official Documentation: [**https://swr.vercel.app/docs/getting-started**](https://swr.vercel.app/docs/getting-started)
    
2. Next.js Documentation on Data Fetching:
    
    * Official guide on fetching data with Next.js: [**https://nextjs.org/docs/basic-features/data-fetching**](https://nextjs.org/docs/basic-features/data-fetching)
        
3. An Introduction To SWR: React Hooks For Remote Data Fetching:
    
    * [**https://www.smashingmagazine.com/2020/06/introduction-swr-react-hooks-remote-data-fetching/**](https://www.smashingmagazine.com/2020/06/introduction-swr-react-hooks-remote-data-fetching/)
        
4. Data fetching in React with SWR  
    [**https://dev.to/ibrahimraimi/data-fetching-in-react-with-swr-5gb0**](https://dev.to/ibrahimraimi/data-fetching-in-react-with-swr-5gb0)
    
5. GitHub Discussions and Conversations: **https://github.com/vercel/swr/discussions**\](https://github.com/vercel/swr/discussions)