Infinite scroll component in React and NextJS

Author Mileta Dulovic
Author

Mileta Dulovic

Infinite scroll is a great way to load huge lists as it provides a great UX for user when compared to traditional pagination, because user doesn't need to click page number to load more items.

Component

Component can be integrated in two ways


  1. If your app uses document to scroll, then you just need to wrap your list with InfiniteScroll and the component will do everything for you

  2. If you have a div that is scrollable, you need to assign ID to that div <div id="wrapper"/> and then pass scrollElement prop to InfiniteScroll - <InfiniteScroll scrollElement="#wrapper"/>

Logic

First we need to check for user's scroll position, and load more items. We do this by getting how much user scrolled from the top. Then we calculate how much user has until the end of the page, and we load more items.

Code

import { useState, useEffect, cloneElement } from "react";

// onBottomReach: optional callback when user reaches bottom
// scrollThreshold: load more items if user has at least 1000 pixels before the end
// scrollElement: ID of the element that is scrollable. Document is default
// perPage: items to load per page

const InfiniteScroll = ({
  onBottomReach,
  scrollThreshold = 1000,
  children,
  scrollElement,
  perPage = 10,
}) => {
  const [page, setPage] = useState(1);

  const handleScroll = (e) => {
    const target = scrollElement
      ? document.getElementById(scrollElement)
      : e?.target?.scrollingElement;

      // detects if user is at the bottom while taking scroll threshold into the account
      const bottom =
        target.scrollTop + target.clientHeight >=
        target.scrollHeight - scrollThreshold;

      if (bottom) {
        // checks if onBottomReach is passed to the component and calls it
        if (typeof onBottomReach === "function") {
          onBottomReach();
        } else {
          setPage((prevState) => prevState + 1);
        }
      }
  };

  useEffect(() => {
    // adds listener when component mounts, while making sure that the enviorement is not the server
    if (typeof window !== "undefined") {
      document?.addEventListener("scroll", handleScroll, {
      passive: true,
      capture: true,
    });
  }

  // clear the listener to avoind memory leaks
  return () =>
    document?.removeEventListener("scroll", handleScroll, { capture: true });
  }, []);

  return <>{children?.slice(0, page * perPage)?.map((item) => item)}</>;
};

export { InfiniteScroll };