DEV Community

Cover image for Quickly implement infinite scroll with Next.js and react-query
Elisabeth Leonhardt
Elisabeth Leonhardt

Posted on • Edited on

Quickly implement infinite scroll with Next.js and react-query

We have all been at a point in our lives where we just needed a quick and dirty guide on how to implement infinite scroll so we could get this prototype ready or this ticket closed. I have been there, and on that faithful day I did only seem to find very long articles and videos I didn't have time for. Here is a quick way - enjoy!

1. get the project set up

You have two options here: clone the project repository or start with a blank Next.js project. If you go with a fresh project, install react-query and configure a QueryClientProvider in _app.js like shown in the documentation. React-query looks scary at first but believe me, it will save you tons of time later.

2. fetch the first 20 items and display them on the page

Create a new page in your blank project or go inside the infiniteCSR.js file in the repo and read along. First, we want only some data on the page, then we are going to make it infinite. Let's get started by getting the first 20 characters from the Rick and Morty API with the useInfiniteQuery hook:

import { useInfiniteQuery } from "react-query";

  const { data, status, fetchNextPage, hasNextPage } = useInfiniteQuery(
    "infiniteCharacters",
    async ({ pageParam = 1}) =>
      await fetch(
        `https://rickandmortyapi.com/api/character/?page=${pageParam}`
      ).then((result) => result.json())
  );
Enter fullscreen mode Exit fullscreen mode

useInfiniteQuery takes a string that identifies the query and a function to fetch the results from the API of your choice. You can absolutely declare the function somewhere else and use axios for fetching, this is just an example. Be careful though to pass the pageParam in, we will need it!

When you print out the data returned by the useInfiniteQuery hook, you should see 2 arrays, one called pages and the other one pageParams. The data react-query fetches for us will be in the pages array, so we have to map over it to display our data:

return (
    <div>
      <h1>
        Rick and Morty with React Query and Infinite Scroll - Client Side
        Rendered
      </h1>
      <div className='grid-container'>
        {data?.pages.map((page) => (
              <>
                {page.results.map((character) => (
                  <article key={character.id}>
                    <img
                      src={character.image}
                      alt={character.name}
                      height={250}
                      loading='lazy'
                      width={"100%"}
                    />
                    <div className='text'>
                      <p>Name: {character.name}</p>
                      <p>Lives in: {character.location.name}</p>
                      <p>Species: {character.species}</p>
                      <i>Id: {character.id} </i>
                    </div>
                  </article>
                ))}
              </>
            ))}
      </div>
    </div>
  );
Enter fullscreen mode Exit fullscreen mode

and voilá - we already can see a few Ricks! Like the Adjudicator Rick from the Citadel of Ricks... But before you start googling which chapter this Rick was part of, let's continue with step 3.

The character grid with 20 characters

3. setup the infinite component to have potentially infinite cards

I promised this would be easy! So let's npm install react-infinite-scroll-component which is the one infinite scroll library that has never let me down. We are going to import it and then we can wrap our grid-container in the InfiniteScroll component like so:

import InfiniteScroll from "react-infinite-scroll-component";

      {status === "success" && (
        <InfiniteScroll
          dataLength={data?.pages.length * 20}
          next={() => console.log("fetching more data")}
          hasMore={true}
          loader={<h4>Loading...</h4>}
        >
          <div className='grid-container'>
            {data?.pages.map((page) => (
              ...
            ))}
          </div>
        </InfiniteScroll>
      )}
Enter fullscreen mode Exit fullscreen mode

Let's take a look a this:

  • first, I added some conditional rendering so we only display the infinite scroll if we really have data to show
  • The InfiniteScroll component takes some props: the first one is the length of the data we are displaying
  • the next prop takes in a function to be called if to load more data when the hasMore prop is true.
  • the loader is optional and should obviously have nicer styles, but I am going to focus on the infinite loading functionality for now

By now, you should be able to scroll to the bottom of the page and see the message "fetching more data" in the console. This means, our component detects that there is more data to be fetched and we just have to properly set up the next function to make the infinite-scroll work!

4. Actually fetching more data

The useInfiniteQuery hook does accept a third optional parameter which is an object. Inside that object, we can write the getNextPageParam function, which takes in the last fetched page and the pages array we already know. Inside this function, you will have to evaluate whether there is another page. The return value will be passed in as the pageParam into your fetch function so you will have to compute that and return it.

In my case, working with the Rick and Morty API, I am taking advantage of the lastPage.info.next property to know whether there will be another page and what I want as the pageParam for the next API call.

  const { data, status, fetchNextPage, hasNextPage } = useInfiniteQuery(
    "infiniteCharacters",
    async ({ pageParam = 1 }) =>
      await fetch(
        `https://rickandmortyapi.com/api/character/?page=${pageParam}`
      ).then((result) => result.json()),
    {
      getNextPageParam: (lastPage, pages) => {
        if (lastPage.info.next) {
          return pages.length + 1;
        }
      },
    }
  );
Enter fullscreen mode Exit fullscreen mode

Now that react-query already knows how to evaluate whether there will be a next page and what the pageParam will be, we can hook this functionality up with our InfiniteScroll component:

        <InfiniteScroll
          dataLength={data?.pages.length * 20}
          next={fetchNextPage}
          hasMore={hasNextPage}
          loader={<h4>Loading...</h4>}
        >
Enter fullscreen mode Exit fullscreen mode

and there you go! Enjoy an infinity of Rick and Morty Characters and maybe an infinitely strong urge to rewatch some chapters....

5. Wait - but how...?

I have to admit that it took me some time to understand what react-query does here, so let's break it down:

  1. as soon as the page renders, the first 20 characters get fetched. The result is stored inside pages[0].
  2. After the first fetch, the function getNextPageParam is run. It will realize that there is a subsequent page and return pages.length + 1, so 2. Since 2 is a truthy value, hasNextPage will be set to true.
  3. Now you scroll until you hit the bottom of the InfiniteScroll Container. At this point, hasNextPage is true, so the InfiniteScroll component calls the fetchNextPage function.
  4. The next 20 characters are being fetched and stored inside pages[1]. Now, 40 characters will be shown on the page.
  5. Besides, the getNextPageParam is run again. It will confirm that there is a next Page and return 3 as the next pageParam.
  6. now you scroll.... that's all there is to it. While you are scrolling, the pages array fills up, and react query updates the hasNextPage value and the pageParam for you.
  7. at one point, you will reach the last page, which is page number 42. As soon as you reach it, the getNextPageParam function is run, but nothing is returned. Therefore, hasNextPage is set to false and the API is not called again. You have reached the end of all 826 characters: the last one is the butter robot.
  8. At this point you can add a component that tells your user that this is the end of your infinite scroll, just to make sure he knows.

The end of your infinite scroll

If you need SSR with that, make sure to check out the repo and article on pagination with SSR: You can use the same principles with infinite scroll.

I hope this saved you some time! If so (or if not) let me know in the comments! Have a nice rest of your week 😆

Top comments (5)

Collapse
 
usavas profile image
savas

Hello! Thank you for this great article.

I would like to ask you how come this loads 20 items per page? I mean it is not written in the documentation, either. Is it the default?

Collapse
 
frontenddeveli profile image
Elisabeth Leonhardt

Hi: that is the default of the Rick and Morty API: if you don't change the size param, they return 20 items. The amount of items returned will always depend on the API you are using and if it offers a parameter to change it, you can of course change it.

Collapse
 
devdoubleh profile image
Ismoiljon

Hi, there is this blog open yet ?
What if there is no next key in response data?
I mean how do we call next page without the following key? Thanks

Collapse
 
minhhunghuynh1106 profile image
igdev

Hi you, thanks for your article. But I have a issue, how can I update my list character when mutated like adding a new character? 😅

Collapse
 
frontenddeveli profile image
Elisabeth Leonhardt

Hey, sorry for the late answer.
hmmmmm that depends:

  1. one possibility is to make a refetch call in your code after the mutation. You can destructure the refetch function from the useQuery hook. There is also a remove method, to completely remove the data from the cache.
  2. react-query has some pretty aggressive refetch policies. By default, it will refetch stale data on window refocus for example. That means, if a new character is added, it should already show up if you leave the window and refocus.

I have seen this while developing the this demo-project:

  1. I would scroll down a few pages, let's say 5 pages.
  2. I would click in my IDE to see some code, but not edit anything
  3. When clicking again in the browser, I could see new requests for page 1 - 5 in the network tab, updating the stale data.

I hope that clarifies!