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())
);
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>
);
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.
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>
)}
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;
}
},
}
);
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>}
>
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:
- as soon as the page renders, the first 20 characters get fetched. The result is stored inside pages[0].
- 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. - 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.
- The next 20 characters are being fetched and stored inside pages[1]. Now, 40 characters will be shown on the page.
- Besides, the getNextPageParam is run again. It will confirm that there is a next Page and return 3 as the next pageParam.
- 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.
- 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. - 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.
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)
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?
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.
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
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? 😅
Hey, sorry for the late answer.
hmmmmm that depends:
I have seen this while developing the this demo-project:
I hope that clarifies!