How to use SWR in Next JS - client-side data-fetching technique

You've probably heard about SWR if you've lately worked with client-side data fetching in Next.js. It comes with useSWR, a React hook that simplifies all the difficult things in client-side data fetching (caching, revalidation, attention tracking, and so on).

how-to-use-swr-in-next-js-client-side-data-fetching-technique

Data fetching patterns are an essential component of every web framework. As a result, this aspect of every web technology has experienced constant improvements and innovations.

Next.js offers several ways for fetching data since it supports both client and server-side rendering. One way is to use SWR, which is a collection of React hooks for getting data from a remote location.

In this article, we'll look at SWR, a library that helps with caching, pagination, revalidation, and other tasks. We'll also create a client-side Next.js app that uses SWR to retrieve data from a JSON Placeholder.

What is SWR?

SWR stands for stale-while-revalidate. It's a lightweight library made by the same people that made Next.js. It's essentially a set of React Hooks that come with built-in functionality like revalidation, mutation, and caching.

SWR works in three stages:

  • it first returns the cache (stale),
  • then fetches the data from the server (revalidation),
  • and lastly returns the most recent data. By allowing you to present anything to your user while getting fresh data from the server, SWR improves your user experience.

SWR is backend agnostic, which means it can fetch data from any server that supports HTTP requests. It also offers decent TypeScript and server-side rendering capabilities.

Advantages of SWR

  • Focus Revalidation: SWR automatically revalidates data when you refocus a page or switch between tabs in the browser.
  • Fast Navigation: As soon as data is rendered from the cache, SWR immediately revalidates the data from the origin.
  • Refetch on Interval: SWR will allow you the option to retrieve data automatically, whereas prefetching will only take place of the component associated with the hook on the screen.
  • Local Mutation: Applying changes to data locally, i.e. always updated to the most recent data.
  • Dependent Fetching: You can use SWR to get data that is dependent on other data. It ensures maximum parallelism (to minimize waterfalls) and serial fetching when a piece of dynamic data is necessary for the next data fetch to take place.
  • Scalable: SWR scales extremely well because it requires very little effort to write applications that automatically and eventually converge to the most recent remote data.

Disadvantages of SWR

  • One of the biggest drawbacks of using SWR is that it may cause the user to view stale data, which can occur due to a lack of effective API implementation, an error in updating the displayed information, or a variety of other factors.
  • Apart from creating a poor user experience, this might also be the only cause of a company's failure! Consider a well-known financial firm: can they afford to have its users looking at stale data? No way, and that's why SWR must be implemented and used correctly.

Where Should You Use SWR? (And Where Not To)

SWR can be implemented in a variety of places, here are a few examples of sites categories where SWR would be a good fit:

  • Sites with live data must be updated often.
    Examples of such sites would be sports score sites and flight tracking. You should utilize the revalidate on interval option with a low interval value when creating these sites (one to five seconds).
  • Sites offering real-time updates or postings in the form of a feed.
    The news sites that feature live blogging of events such as elections are a classic example of this.
  • Sites that provide more passive data updates and are frequently left open in the background.
    Weather pages or, in the 2020s, COVID-19 case number pages are examples of these web pages. Because these pages aren't updated as regularly as the preceding two, they don't require the same level of revalidation. However, having the data refresh would improve the user experience. In these circumstances, I'd consider revalidating the date when the tab regains focus and the client reconnects to the internet; this ensures that if a user returns to the tab, expecting a tiny rise in COVID cases, they'll obtain that information promptly.
  • Sites with small pieces of data that users can interact with.
    Consider the Youtube Subscribe Button: when you click subscribe, you want to see the number change and feel like you've helped. In these circumstances, you can programmatically revalidate the data by retrieving the updated count and updating the displayed amount using SWR.
    It's worth noting that all of them may be used with or without ISR.

Of course, there are some situations where you won't want to use SWR or SWR without ISR. SWR isn't very useful if your data doesn't change much or at all, and instead clogs your network requests and consumes mobile users' bandwidth. SWR can be used on sites that need authentication, but you should utilize Server Side Rendering instead of Incremental Static Regeneration in these circumstances.

useSWR Support for TypeScript

SWR is TypeScript-friendly and comes with type safety out of the box. By default, SWR will also infer the argument types of fetcher from key, so you can have the preferred types automatically. That being said, you can specify custom types for your own arguments in the most straightforward way, as follows:

import useSWR from 'swr'
const { data } = useSWR('https://www.users.com', (apiURL: string) => fetch(apiURL).then(res => res.json())

Working with SWR does not need any extra TypeScript setups. More information regarding TypeScript and SWR can be found here.

Let’s dive in and set up a new app with Next.js and the useSWR hook.

Setting up a Next.js application

To quickly set up a Next.js application, open a terminal window and run the create-next-app a command like so:

npx create-next-app next-swr-app

Navigate into the application directory and install SWR with this command:

cd next-swr-app # navigate into the project directory
npm install swr # install swr
npm run dev # run the dev server

The commands above will install the SWR packages, as well as open the project  localhost:3000  in your browser. We should have the project up and running on that port as follows:

next-js-starter-page

We've successfully set up a Next.js application, which is fantastic. Let's get this thing built, shall we?

Data fetching with SWR

We may use useSWR or useSWRInfinite to get remote data using SWR. There are, however, certain distinctions between the hooks. The first hook is only for data fetching, whereas the second allows for data retrieval and pagination. You can use useSWRInfinite to quickly add infinite scrolling or pagination to your Next.js project.

Create a components folder in the project's root directory, add a useRequest.js file, and update it with the snippet below.

// components/useRequest.js

import useSWR from 'swr';

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

const url = 'https://jsonplaceholder.typicode.com/posts';

export const useGetPosts = () => {
  const { data, error } = useSWR(url, fetcher);

  return { data, error };
};

Let's have a look at the code. We're using the useSWR() hook in the code above to get data for posts.

The useSWR hook takes two parameters and returns two results (based on the status of the request). It accepts the following:

  • A Key: a string that serves as the unique identifier for the data we are fetching. This is usually the API URL we are calling.
  • A fetcher: a function that returns the fetched data.

It returns:

  • data — the result of the request (if it was successful)
  • error — the error that occurred (if there was an error)

Create Posts.js file inside the components folder and update its contents with the following snippet.

// components/Posts.js

export default function Post({ post }) {
  const { title, body, id } = post;
  return (
    <>
      <div className="p-4 mb-4 text-sm text-blue-700 bg-blue-100 rounded-lg">
        <p className="font-medium">
          {id}. {title}
        </p>
        <p>{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.

Now, let's update the index.js file.

import Head from 'next/head';
import Post from '../components/Posts';
import { useGetPosts } from '../components/useRequest';

export default function Home() {
  const { data: posts, error } = useGetPosts();
    
  if (error) return <h1>Something went wrong!</h1>;
  if (!posts) return <h1>Loading...</h1>;

  return (
    <div>
      <Head>
        <title>Next SWR App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className="max-w-xl mx-auto">
        <h1 className="font-bold m-5">My Posts</h1>
        {posts.map((post) => (
          <Post post={post} key={post.id} />
        ))}
      </main>
    </div>
  );
}

We first import the useGetPosts hook. It returns a list of posts to display as well as an error state if any.

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.

When you return to the browser, you should see posts appear as expected:

how-to-use-swr-in-next-js-client-side-data-fetching-technique-posts

There! We've got our posts. So, our useSWR hook is up and running. Is that it, though? What makes this different from other data retrieval methods? Continue reading to learn more...

Why useSWR?

It’s a likely question to ask at this point. I wouldn't say this is a significant improvement for me yet, except for being extremely declarative. This makes this a great time to discuss some of useSWR's features.

Pagination

Consider the following scenario: instead of loading only ten posts, we want to be able to generate posts on-demand and add them to this page. This is very useful when creating an app that requires users to scroll through numerous pages with the same content. We can show this by including a Load More button at the bottom of our post's page that, when clicked, generates more posts.

It's still possible to use the useSWR hook to paginate the data, but let's use useSWRInfinite hook to achieve it.

Let's add another function in useRequest.js

// components/useRequest.js

import useSWRInfinite from 'swr/infinite';

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

const url = 'https://jsonplaceholder.typicode.com/posts';

export const usePaginatePosts = () => {
  const PAGE_LIMIT = 20;

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

// index.js
import Head from 'next/head';
import Post from '../components/Posts';
import { usePaginatePosts } from '../components/useRequest';

export default function Home() {
  const { posts, error, isLoadingMore, size, setSize, isReachingEnd } =
    usePaginatePosts();

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

  return (
    <div>
      <Head>
        <title>Next SWR App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className="max-w-xl mx-auto">
        <h1 className="font-bold m-5">My Posts</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>
      </main>
    </div>
  );
}

We begin by importing usePaginatePosts. The values returned by the hook are then used to show the posts and load fresh data. SWR will forward the request to the next page and then return the data if the load more button is pressed. The data is now paginated using the useSWRInfinite hook.

Complex use case

Let’s explore a more complex use case. You can check out the SWR docs for more details on usage and benefits.

Mutation and revalidation with useSWR

So let’s say that we had our own /comments endpoint where we can retrieve all our comments from the database and also post to the endpoint to add new comments.

We can immediately write the code to look like this:

import useSWR from "swr";  
  const address = `http://localhost:3000/api/comments`;  
  const fetcher = (...args) => fetch(...args).then((res) => res.json());
  const { data, error } = useSWR(address, fetcher);
  const addComment = async () => {
    const newComment = {
      comment: "This is a test comment",
      email: "[email protected]",
    };
    await fetcher(address, {
      method: "POST",
      body: JSON.stringify(newComment),
    });
  };

When we click a button to trigger the addComment() function, we'll use useSWR to post to the /comments endpoint, effectively adding a new comment to the database. However, our site will not know that there’s been an update and therefore we won’t see that comment on screen.

This is where the useSWR mutation comes in. We can use mutation to revalidate our page to:

  1. Check if there’s new data
  2. If there is, revalidate the page and render the new data without triggering a full page reload:
import useSWR from "swr";  
  const address = `http://localhost:3000/api/comments`;  
  const fetcher = (...args) => fetch(...args).then((res) => res.json());
  const { data, error } = useSWR(address, fetcher);
  const addComment = async () => {
    const newComment = {
      comment: 'This is a test comment',
      email: '[email protected]',
    };
    await fetcher(address, {
      method: 'POST',
      body: JSON.stringify(newComment),
    });
    mutate(address);
  };

useSWR will now revalidate the page when we update the database by posting a new comment, ensuring that we don't offer stale data.

However, useSWR will revalidate your page on focus automatically to ensure that your data is maintained up to date. You may disable revalidation on focus by passing the following option to SWR as an argument:

const { data, error } = useSWR(address, fetcher, {
    revalidateOnFocus: false
  });

Conclusion

We've covered the basics of the useSWR hook in this post. We also used Next.js to create a blog post app to illustrate the SWR features. I hope this post has given you some insight into using useSWR to fetch data in Next.js apps.

If you like the contents then please share this article

The source code can be found here.

Buy Me A Coffee