This tutorial was originally published at https://www.devaradise.com/react-infinite-scroll-tutorial
Infinite scroll is a modern web & application design concept that loads content continuously as the user scrolling down the page. It changes the function of pagination.
Implementing infinite scroll is suitable if you have a lot of data to load and you don't want users to click the page number to see more data. It boosts user experience in your application.
As a developer, we can implement infinite scroll in any application, including a react application. React infinite scroll can be implemented in 2 ways, by manual implementation without a library and by using an infinite scroll library.
In this post, I will show and explain to you how to implement infinite scroll in a react project, with and without a library. Both ways have their own advantages and drawbacks.
Before we jump to the tutorial, make sure that you already know to initialize a react app using create-react-app boilerplate. Because I won't explain the basic react tutorial here. I assume you already understand that.
In this tutorial, we are going to use react functional components and hooks. We also using react-lab to host demo examples and this project architecture to manage the project files.
How to Implement Infinite Scroll Without Library
Implementing a react infinite scroll without a library is the best if you want to make your react project as lightweight as possible. It also best if you're going to put some customization on it.
Personally, I will choose this method to implement an infinite scroll on my react app. I don't think it has many codes and logics to write.
We only need some states, an event listener for scroll, an API calling service, and function to load data & put some logics.
Create A Component
Suppose that we will create a user list page that has infinite scroll implemented. So, we need a component to implement it like this.
import React, { useState } from "react";
export default function InfiniteScrollNoLibrary() {
const [userList, setUserList] = useState([]);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const [noData, setNoData] = useState(false);
return (
<div>
<div className="section">
{userList.map((user, i) =>
(
<div className="box m-3 user" key={i}>
<img src={user.avatar} alt={user.first_name}/>
<div className="user-details">
<strong>Email</strong>: {user.email}<br/>
<strong>First Name</strong>: {user.first_name}<br/>
<strong>Last Name</strong>: {user.last_name}<br/>
</div>
</div>
)
)}
{loading ? <div className="text-center">loading data ...</div> : "" }
{noData ? <div className="text-center">no data anymore ...</div> : "" }
</div>
</div>
);
}
I don't put business logic and event listener yet. Let me explain the states and markup first.
To manually implement infinite scroll we need at least 4 states:
-
userList
to store an array of user data from API. The default is an empty array. -
page
to count what page of user list to load. This helps us to not loading and adding the same data to the list. -
loading
to give a loading state when calling the API. -
noData
to give a no data state and stop API calling when there is no data anymore.
As you can see in the code above, userList
state will be looped using map
in the JSX markup. A ''loading ..." and "no data anymore …" text will also be added every time the loading
and noData
state has true
value.
Create A Service for API Calling
Before I add some logics to component, I create a service for calling user data first.
Actually, you can directly call an API in a component without creating a service. But, I personally prefer to separate it from the component. You can read the reason on my react project structure article.
import axios from 'axios';
export default {
getList: async function(page) {
try {
let url;
if(page!=null & page > 1) {
url ="https://reqres.in/api/users?per_page=2&page="+page;
} else {
url = "https://reqres.in/api/users?per_page=2";
}
const response = await axios.get(url);
return response.data;
} catch(error) {
throw error;
}
}
}
The getList function above accepts a page
parameter to dynamically change URL string based on page number inserted. For dummy data, I use resreq.in users API.
Adding Some Logic to Component
After creating a service, now we will use it in a component along with some logics. Have a look at the full component codes below. I will explain it after that.
import React, { useState, useEffect } from "react";
import UserService from 'services/UserService';
export default function InfiniteScrollNoLibrary() {
const [userList, setUserList] = useState([]);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const [noData, setNoData] = useState(false);
window.onscroll = () => {
if (window.innerHeight + document.documentElement.scrollTop === document.documentElement.offsetHeight) {
if(!noData) {
loadUserList(page);
}
}
}
useEffect(() => {
loadUserList(page);
}, []);
const loadUserList = (page) => {
setLoading(true);
setTimeout(() => {
UserService.getList(page)
.then((res) => {
const newPage = page + 1;
const newList = userList.concat(res.data);
setUserList(newList);
setPage(newPage);
if(res.data.length===0)
setNoData(true);
})
.catch((err) => {
console.log(err);
})
.finally(() =>{
setLoading(false);
})
}
,1500);
}
return (
<div>
<div className="section">
{userList.map((user, i) =>
(
<div className="box m-3 user" key={i}>
<img src={user.avatar} alt={user.first_name}/>
<div className="user-details">
<strong>Email</strong>: {user.email}<br/>
<strong>First Name</strong>: {user.first_name}<br/>
<strong>Last Name</strong>: {user.last_name}<br/>
</div>
</div>
)
)}
{loading ? <div className="text-center">loading data ...</div> : "" }
{noData ? <div className="text-center">no data anymore ...</div> : "" }
</div>
</div>
);
}
First, we import UserService
and useEffect
hook to the component. We will be use them later in the API calling function.
The most important codes in the component above are on the line 11 - 17.
window.onscroll = () => {
if (window.innerHeight + document.documentElement.scrollTop === document.documentElement.offsetHeight) {
if(!noData) {
loadUserList(page);
}
}
}
This is a function to listen when the user scrolls the page. Inside it, I put a logic: "If user scrolls to the bottom of the page, and noData
state is false, then load the user list".
When a user just lands to the page and not scrolled yet, we load the user list inside the useEffect
hook. So, the user data still loaded.
useEffect(() => {
loadUserList(page);
}, []);
Now, look into the loadUserList
function.
const loadUserList = (page) => {
setLoading(true);
setTimeout(() => {
UserService.getList(page)
.then((res) => {
const newList = userList.concat(res.data);
setUserList(newList);
const newPage = page + 1;
setPage(newPage);
if(res.data.length===0)
setNoData(true);
})
.catch((err) => {
console.log(err);
})
.finally(() =>{
setLoading(false);
})
}
,1500);
}
First, we set the loading state to true
to show "loading ..." text when calling the API. I use setTimeout function here just to delay API calls so I can see the loading state. You don't have to use it in your codes.
In line 4, I call the getList function in UserService and pass page
to it. If the API request success, new user data from API will be added to the current user list (line 6 - 7).
We also need to set new page
state for the next API calling when user scrolls again. You can see it on line 9 - 10.
Last, we create a condition to set noData
State. If the API response is an empty array, it means that there is no more data to be loaded. So, we set the noData
state to true
.
If the API request returns an error, catch it in the catch
section. In this example, I just console.log it. And in finally
section set the loading
state to be false
again because the request is over.
That's it. Now you can practice it on your own. To see the live demo for infinite scroll without library, you can click the link below.
How to Implement Infinite Scroll With react-infinite-scroller
If you don't want to implement react infinite scroll manually, you can still implement it using a library. There are a lot of libraries for React infinite scroll implementation out there.
Using a library for infinite scroll is the best if you want shorter code to write, and some options to easily customize it. Most of react infinite scroll libraries have more options and features than manual implementation I show you before.
In this tutorial, I use react-infinite-scroller
as it is simple and easy to implement. Without further ado, let's see how to use it in your react project.
Install and Import react-infinite-scroller
First you should install react-infinite-scroller to your project using npm
npm i react-infinite-scroller
To use it in a component, just import it like this.
import InfiniteScroll from 'react-infinite-scroller'
Using InfiniteScroll in a Component
Here is the full component codes. I explain it below.
import React, { useState } from 'react'
import InfiniteScroll from 'react-infinite-scroller'
import UserService from 'services/UserService';
export default function InfiniteScrollerWithReactInfiniteScroller() {
const [userList, setUserList] = useState([]);
const [hasMoreItems, setHasMoreItems] = useState(true);
const loadUserList = (page) => {
setTimeout(() => {
UserService.getList(page)
.then((res) => {
const newList = userList.concat(res.data);
setUserList(newList);
if(res.data.length===0) {
setHasMoreItems(false);
} else {
setHasMoreItems(true);
}
})
.catch((err) => {
console.log(err);
})
}, 1500)
}
return (
<div>
<div className="section">
<InfiniteScroll
threshold={0}
pageStart={0}
loadMore={loadUserList}
hasMore={hasMoreItems}
loader={<div className="text-center">loading data ...</div>}>
{userList.map((user, i) =>
(
<div className="box m-3 user" key={i}>
<img src={user.avatar} alt={user.first_name}/>
<div className="user-details">
<strong>Email</strong>: {user.email}<br/>
<strong>First Name</strong>: {user.first_name}<br/>
<strong>Last Name</strong>: {user.last_name}<br/>
</div>
</div>
)
)}
</InfiniteScroll>
{hasMoreItems ? "" : <div className="text-center">no data anymore ...</div> }
</div>
</div>
)
}
As you can see, we have fewer states and logics to write if we use a library. We only need userList
state to store our user data, and hasMoreItems
to be passed to <InfiniteScroll/>
. The page
and loading
state will be handled by react-infinite-scroll
.
In the loadUserList
function, we use the same UserService that I used before in the manual implementation. When the API request success, we only have to set a new user list (line 14-15) and set hasMoreItems
state (line 17-21).
Most of logic is handled by <InfiniteScroll/>
that should wrap the userList
looping.
<InfiniteScroll
threshold={0}
pageStart={0}
loadMore={loadUserList}
hasMore={hasMoreItems}
loader={<div className="text-center">loading data ...</div>}>
{userList.map((user, i) =>
(
<div className="box m-3 user" key={i}>
<img src={user.avatar} alt={user.first_name}/>
<div className="user-details">
<strong>Email</strong>: {user.email}<br/>
<strong>First Name</strong>: {user.first_name}<br/>
<strong>Last Name</strong>: {user.last_name}<br/>
</div>
</div>
)
)}
</InfiniteScroll>
As you see, there is some attribute for InfiniteScroll
i used above. Here is the explanation.
-
threshold
is the distance between the bottom of the page and the bottom of the window's viewport that triggers the loading of new list - Defaults to250
. But i set it to 0. -
pageStart
is the page number corresponding to the initial list, defaults to0
which means that for the first loading,loadMore
will be called with1
. -
loadMore(pageToLoad)
is called when the user scrolls down and we need to load a new list. The value should be a function. It will passpage
number to the value. -
hasMore
is a boolean stating whether there are more items to be loaded. Event listeners are removed iffalse
. -
loader
is loader element to be displayed while loading items - You can use InfiniteScroll.setDefaultLoader(loader); to set a default loader for all yourInfiniteScroll
components
To use more attributes, you can see the documentation here. And if you want see the live demo for infinite scroll with react-infinite-scoller, you can click the link below.
That's all. I hope this useful for you.
Happy coding!
Top comments (6)
Thanks for this article. For the version without any library it would be nice to use the intersection observer rather that the onScroll event. That would be much more efficient
Thanks for the suggestion. Do you have any reference for that?
here is an article in Vanilla JS about implementing a back to top button with Observers
it is pretty similar to fetching new scrollable items
m.signalvnoise.com/how-to-back-to-...
developer.mozilla.org/en-US/docs/W...
Not supported in IE11 though, if that is of any concern
you can find a polyfill
Yeah, or write a tiny abstraction on top of the code that is already there, that is "probably" better.