Data fetching in Next.js — How To Use SWR
Tags: nextjs, react, beginners
Published: 2020-11-04 17:07:15

Next.js offers several ways for fetching data since it supports both client and server-side rendering. One is by using SWR, which is a set of React hooks for remote data fetching.

In this tutorial, we will be looking at SWR, a library that makes things easier, such as caching, pagination, revalidation, and so on. We will also build a Next app (client-side) that retrieves data from [JSON Placeholder](https://jsonplaceholder.typicode.com/) using SWR.

- [What is SWR?](#what-is-swr)
- [Setting up](#setting-up)
- [Fetching the data with `useSWR`](#fetching-the-data-with-useswr)
- [Creating the components](#creating-the-components)
- [Paginating the data with `useSWRInfinite`](#paginating-the-data-with-useswrinfinite)

Let's get started!

## What is SWR?

SWR stands for `stale-while-revalidate`. It's a lightweight library created by the same team behind [Next.js](https://nextjs.org/). It allows fetching, caching, or refetching data in realtime with React Hooks. SWR proceeds in three steps: first, it returns the cache (stale), then fetch the data from the server (revalidation), and finally come with the up-to-date data. This way, SWR increases your user experience by letting you show something to your user while retrieving the new data from the server.

SWR is backend agnostic, meaning that you can use it to retrieve data from any server that supports HTTP requests. It has as well good support for TypeScript and server-side rendering.

That said, we can get hands dirty and setting up a new Next.js app to use the SWR features.

## Setting up

To set up a new app, we'll go for Create Next App.
Begin by opening your command-line interface (CLI) and running this command:

```bash
	npx create-next-app next-swr-app
```

Install SWR package:
```bash
	npm install swr
```

Next, structure the folder like so:

```
├── components
| └── Post.js
├── pages
| └── index.js
├── useRequest.js
└── package.json
```

Let's break down the file structure:

- `Post.js` is the component responsible for the display of the post object.
- `index.js` is the home page of our app.
- `useRequest.js` is a custom hook that helps fetch the data using SWR.

With this folder structure in place, we can start retrieving the remote data from JSON Placeholder in the next section.

## Fetching the data with `useSWR`

To fetch remote data with SWR, we can use either `useSWR` or `useSWRInfinite`. However, there are some differences between the hooks. The first is used for just data fetching, while the second hook enables retrieving and paginating data. You can use `useSWRInfinite` to add infinite scroll or pagination in your Next.js app in no-time.

Now, let's explore the file `useRequest.js`:

```js
import useSWR from "swr"

const fetcher = url => fetch(url).then(res => res.json())
const baseUrl = "https://jsonplaceholder.typicode.com"

export const useGetPosts = path => {
  if (!path) {
    throw new Error("Path is required")
  }

  const url = baseUrl + path

  const { data: posts, error } = useSWR(url, fetcher)

  return { posts, error }
}
```

Using this custom hook for fetching data is optional. You can alternatively use the SWR hooks directly in your components.

The `fetcher` function enables us to send the HTTP request to the server and then parse the response data to JSON. The `fetch` method comes from the `unfetch` package that ships with Next.js.

Next, we use the `useGetPosts` function to send the query with the `useSWR` hook. It expects to receive as arguments the `url` of the server and a `fetcher` function to execute the query. Once the data is retrieved, we return the fetched `posts` and an `error` state.

With this custom hook ready to use, we can now create the components to display the posts fetched.

## Creating the components

- `components/Post.js`

```js
export default function Post({ post }) {
  const { title, body, id } = post
  return (
    <div className="Card">
      <h1 className="Card--title">
        {id}. {title}
      </h1>
      <p className="Card--body">{body}</p>
    </div>
  )
}
```

As you can see, we have a simple component that receives the `post` to display as a parameter. Then, we use destructuring to pull out the elements from the object in order to show the post.

- `App.js`

```js
import { useGetPosts } from "../useRequest"
import Post from "../components/Post"

export default function IndexPage() {
  const { posts, error } = useGetPosts("/posts")

  if (error) return <h1>Something went wrong!</h1>
  if (!posts) return <h1>Loading...</h1>

  return (
    <div className="container">
      <h1>My Posts</h1>
      {posts.map(post => (
        <Post post={post} key={post.id} />
      ))}
    </div>
  )
}
```

Here, we start by importing the `useGetPosts` hook and then pass in the path as an argument to perform the request. It returns the `posts` to show and an error state.

After that, we use the `Post` component to display the array of data. If any error occurs, we handle it accordingly with the `error` provided by SWR.

With this step forward, we can check if everything works in the browser. To do so, open the project on the CLI and run the following command:

```shell
  yarn dev
```

Or for `npm`

```shell
  npm run dev
```

Let's visit on the browser `http://localhost:3000`

![app-preview-1](https://dev-to-uploads.s3.amazonaws.com/i/z4pkyos7s4x7zedkgtax.PNG)

Great! Our data are successfully fetched from the server using the `useSWR` hook.

As we said earlier, SWR provides another hook that allows paginating data easily. Let's update our app with `useSWRInfinite`.

## Paginating the data with `useSWRInfinite`

It's still possible to use the `useSWR` hook to paginate the data, but I don't recommend that since it's extra code and SWR already offers `useSWRInfinite` to do it.

- `useRequest.js`

```js
import { useSWRInfinite } from "swr"

const fetcher = url => fetch(url).then(res => res.json())
const baseUrl = "https://jsonplaceholder.typicode.com"

export const usePaginatePosts = path => {
  if (!path) {
    throw new Error("Path is required")
  }

  const url = baseUrl + path
  const PAGE_LIMIT = 5

  const { data, error, size, setSize } = useSWRInfinite(
    index => `${url}?_page=${index + 1}&_limit=${PAGE_LIMIT}`,
    fetcher
  )

  const posts = data ? [].concat(...data) : []
  const isLoadingInitialData = !data && !error
  const isLoadingMore =
    isLoadingInitialData ||
    (size > 0 && data && typeof data[size - 1] === "undefined")
  const isEmpty = data?.[0]?.length === 0
  const isReachingEnd =
    isEmpty || (data && data[data.length - 1]?.length < PAGE_LIMIT)

  return { posts, error, isLoadingMore, size, setSize, isReachingEnd }
}
```

The `useSWRInfinite` hook expects as an argument a function that returns the request key, a `fetcher` function, and options. The request key (`index`) is what SWR uses to know what data (page) to retrieve. The initial value of the request key is `0`, so we have to increment it by `1` upon each request. The second argument to define on the URL is `PAGE_LIMIT`, which is the number of items to fetch per request.

`useSWRInfinite` returns more values than that. I removed the data that I don't need here. Let's explain what these variables do:

- `posts` is the array of the data fetched from the server.
- `isLoadingInitialData` checks if there is still data to retrieve.
- `isLoadingMore` checks if we're currently retrieving data.
- `isEmpty` checks whether the array of data is empty or not.
- `isReachingEnd` checks if the page limit is reached or not.

Next, we return the values in order to use them in our components.

- `App.js`

```js
import { usePaginatePosts } from "../useRequest"

import Post from "../components/Post"

export default function IndexPage() {
  const {
    posts,
    error,
    isLoadingMore,
    size,
    setSize,
    isReachingEnd,
  } = usePaginatePosts("/posts")

  if (error) return <h1>Something went wrong!</h1>
  if (!posts) return <h1>Loading...</h1>

  return (
    <div className="container">
      <h1>My Posts with useSWRInfinite</h1>
      {posts.map(post => (
        <Post post={post} key={post.id} />
      ))}
      <button
        disabled={isLoadingMore || isReachingEnd}
        onClick={() => setSize(size + 1)}
      >
        {isLoadingMore
          ? "Loading..."
          : isReachingEnd
          ? "No more posts"
          : "Load more"}
      </button>
    </div>
  )
}
```

Here, we first import `usePaginatePosts` and then pass in the API endpoint as an argument. Next, we use the values returned by the hook to display the posts and to load new data. Once the `load more` button clicked, SWR will send the request to the next page and then return the data. With this in place, the data now are paginated using the `useSWRInfinite` hook.

With this step, we can test if the pagination works by running this command on the CLI:

```shell
  yarn dev
```

Let's visit on the browser `http://localhost:3000`

![app-preview-2](https://dev-to-uploads.s3.amazonaws.com/i/pxxy7od241uu4zikdw20.gif)
And that's it! Our app is looking good!

We are done using the SWR library on the client-side with Next.js. You can find the finished project on this [CodeSandbox](https://codesandbox.io/s/next-swr-example-st40f).

You can find other great content like this on [my blog](https://www.ibrahima-ndaw.com) or follow me [on Twitter](https://twitter.com/ibrahima92_) to get notified.

Thanks for reading!