Data fetching in Next.js — How To Use SWR

Ibrahima Ndaw - Nov 4 '20 - - Dev Community

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 using SWR.

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. 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:

    npx create-next-app next-swr-app
Enter fullscreen mode Exit fullscreen mode

Install SWR package:

    npm install swr
Enter fullscreen mode Exit fullscreen mode

Next, structure the folder like so:

├── components
| └── Post.js
├── pages
| └── index.js
├── useRequest.js
└── package.json
Enter fullscreen mode Exit fullscreen mode

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:

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 }
}
Enter fullscreen mode Exit fullscreen mode

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
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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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
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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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:

  yarn dev
Enter fullscreen mode Exit fullscreen mode

Or for npm

  npm run dev
Enter fullscreen mode Exit fullscreen mode

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

app-preview-1

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
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 }
}
Enter fullscreen mode Exit fullscreen mode

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
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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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:

  yarn dev
Enter fullscreen mode Exit fullscreen mode

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

app-preview-2
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.

You can find other great content like this on my blog or follow me on Twitter to get notified.

Thanks for reading!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .