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.
- What is SWR?
- Setting up
- Fetching the data with
useSWR
- Creating the components
- 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. 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
Install SWR package:
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
:
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
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
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:
yarn dev
Or for npm
npm run dev
Let's visit on the browser http://localhost:3000
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 }
}
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>
)
}
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
Let's visit on the browser http://localhost:3000
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!