Infinite scrolling with React

Helder Pinhal
Helder Pinhal
Sep 6 2024
Posted in Engineering & Technology

Optimized performance, no dependencies.

Infinite scrolling with React

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.

Keep up-to-date with the latest news