Infinite scrolling has become a common feature in many apps and websites, especially on social media platforms that aim to keep users engaged by offering endless content. While mindless scrolling can be a distraction, building your own infinite scroll functionality is a rewarding experience. Even though many libraries can help you implement infinite scroll, it's wise to be selective about adding external dependencies to your projects. Too many libraries can bloat your app, and you risk relying on outdated or unmaintained code. While this is rarely a concern with infinite scrolling, the principle still applies.
Fortunately, building infinite scrolling yourself is straightforward.
Getting Started
To begin, we'll use a Next.js app as an example, though this technique can work in any framework. Before starting, ensure you have Node.js installed.
Run the Next.js CLI tool to create a boilerplate app and let's get started.
npx create-next-app@latest
Fetching Data
Let's assume we have an API endpoint that provides paginated data like this:
// GET /api/items?page=
[
{
"position": 1,
"content": "..."
}
]
We'll keep things simple by using the Fetch API to load data. We'll also set up a React effect that fetches data when a new page is requested.
import { useCallback, useEffect, useState } from "react";
// Define the structure of the data returned by the API.
type Item = {
position: number;
content: string;
};
// The infinite scrolling component.
export function InfiniteScrollingList() {
const [loading, setLoading] = useState(true);
const [page, setPage] = useState<number>(1);
const [items, setItems] = useState<Item[]>([]);
useEffect(function loadData() {
setLoading(true);
fetch(`/api/items?page=${page}`)
.then((res) => res.json())
.then((data) => setItems((prevItems) => [...prevItems, ...data]))
.catch((error) => console.error(error))
.finally(() => setLoading(false));
}, [page]);
return (
// Render your component here
);
}
Adding Infinite Scrolling
To implement infinite scrolling, we need to add a scroll event listener to the window.
This will trigger the loading of the next page when the user reaches a certain point near the bottom of the page.
Since we already set up an effect that listens for changes in the page
state, new data will load automatically when the page
value updates.
Remember to clean up event listeners when your component unmounts to prevent memory leaks.
const handleScroll = useCallback(() => {
if (document.body.scrollHeight - 300 < window.scrollY + window.innerHeight) {
setPage((prevState) => prevState + 1);
}
}, []);
useEffect(function setupScrollListener() {
const listener = handleScroll;
window.addEventListener("scroll", listener);
return () => window.removeEventListener("scroll", listener);
}, [handleScroll]);
To clarify:
scrollHeight
measures the total height of the content, including the portion not currently visible on the screen.scrollY
tracks how far the page has been scrolled vertically.innerHeight
represents the height of the window's visible area.
Optimizing the Scroll Event
Since scrolling triggers handleScroll
repeatedly, this could lead to performance issues as the function may be called many times in a short span.
To avoid this, we can debounce the scroll handler to limit how often it runs.
While you could use a library for this, writing a debounce function yourself is simple:
function debounce<T extends (...args: any[]) => void>(func: T, delay: number) {
let timeoutId: ReturnType<typeof setTimeout>;
return function (...args: Parameters<T>) {
if (timeoutId) clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func(...args);
}, delay);
};
}
Now, update the scroll handler to use this debounce function:
// Replace this:
const listener = handleScroll;
// With this:
const listener = debounce(handleScroll, 500);
Conclusion
And that's it! With these steps, you now have a basic infinite scrolling implementation. You can see it in action below, and the full code is available in this GitHub gist.
As always, we hope you liked this article, and if you have any question, we are available via our Support Channel.