There are a lot of React pre-made components out there that promise infinite scroll functionality. The problem with that is that sometimes they are not tailored to fit your specific needs, especially if you are using the newly featured React-Hooks to create functional components all across your application. These pre-made components work well with class components, but they have not been updated to work with the state-of-the-art functional hook-components.
React Hooks are a game-changer for frontend development; it is so easy to work with the lifecycle of components that it blows your mind, so it just makes sense to go this way even if that means you won't be able to use some packages that would make your life a little bit easier. React Hooks will make your life a whole lot easier anyway. So let's get started.
Since you most likely didn't find this article just surfing around, I will assume that you already have a component using React Hooks. Also, that you have some information that you need to update every time the user reaches the bottom of the page (or is close enough anyway), and that you have a container/parent component where you will display it.
The first thing that we will need is to add an event listener to the window of "scroll" that will then call a handleScroll function. Since this is an event that will take place only when the component gets mounted, we will use a useEffect hook:
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
The empty brackets after the callback tell it to just run once when the component gets mounted. Now that the window can know when we are scrolling, we need our event handler to know if we are at the bottom of the page. To do that, we need a boolean variable to tell us if that is true and a function to change that value of that variable. The first thing that we are going to do is to create a useState hook to add the boolean variable:
const [isBottom, setIsBottom] = useState(false);
We need it to be a hook since it needs to be part of the state so that it can maintain its value between lifecycles and renders, and we set the default value to false because we assume the user is not at the bottom when the component gets mounted. Next, we need a function to calculate whether or not we are at the bottom of the page.
This is a little bit tricky since every browser is different, but in general, we need to get the top of the body:
const scrollTop = (document.documentElement
&& document.documentElement.scrollTop)
|| document.body.scrollTop;
Then, we need to know how much a user would need to scroll to get to the bottom:
const scrollHeight = (document.documentElement
&& document.documentElement.scrollHeight)
|| document.body.scrollHeight;
And then we can ask if the user is really at the bottom (or near it) and set the new state:
if (scrollTop + window.innerHeight + 50 >= scrollHeight) {
setIsBottom(true);
}
Where 50 is how far from the bottom, in pixels, we start requesting for the new information. Here is where you can change the value to whatever distance you would like to fetch/add data to the component/state. Our complete function now looks like this:
function handleScroll() {
const scrollTop = (document.documentElement
&& document.documentElement.scrollTop)
|| document.body.scrollTop;
const scrollHeight = (document.documentElement
&& document.documentElement.scrollHeight)
|| document.body.scrollHeight;
if (scrollTop + window.innerHeight + 50 >= scrollHeight){
setIsBottom(true);
}
}
Now that we have used our Hook to set the new state for our isBottom state, we need to create a Hook that will trigger on any update of this variable, so let's do that:
useEffect(() => {
if (isBottom) {
addItems();
}
}, [isBottom]);
So we are using a useEffect Hook that updates with every change of the isBottom state. We are setting this with the brackets after the callback function. Anything inside the brackets is what the Hook will listen to, to know it is time to update.
Inside we will only care if we are at the bottom of the page to trigger the fetching/adding of new items if the change of state turned it into false, that means that we got far from the bottom and we don't want to trigger the function.
And that is pretty much it! You now have a fully functional infinite scroll built from scratch. Now you can create the addItems function to work exactly as you need it. The only thing to consider is that after doing whatever you need to do, you need to reset the state of isBottom to false.
In my case, I had a list of stocks (over 14,000), and I wanted to display 30 by 30, so I set the state of my variable here and then reset the isBottom. This is what my addItems function looks like:
const addItems = () => {
if (state.stocks.length !== 0) {
setStocksToDisplay(prevState => ({
page: prevState.page + 1,
stocksToDisplay: prevState.stocksToDisplay.concat(
state.stocks.slice(
(prevState.page + 1) * 30,
(prevState.page + 1) * 30 + 30,
),
),
}));
setIsBottom(false);
}
};
The only important thing of this function for you is the reset to false part of the isBottom function, but I wanted to show you a complete implementation. I hope you find this useful, and if you have any other questions, I am always available through Linkedin, Twitter, or Github.
See you on the other side!
Happy coding!