Mileta Dulovic
HomeBlogReact

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 };
More in this category

How to cancel requests with Apollo GraphQL

If you need to cancel requests with Apollo Graphql in React, and do not know how to do it, you came to the right place.

Lazy loading images with Intersection Observer in React

Images can really be a bottleneck on the website. If you do not optimize your images users will have bad time and crawlers will not crawl you as fast as you want.

React: Pitch & anti-pitch

Weighing the Advantages and Disadvantages of React for Modern Web Development.

Get rid of YouTube unskippable ADS with simple JavaScript

If you hate waiting 5 seconds to skip YouTube ads, let's fix that with few lines of JavaScript code.

JavaScript Loops performance

Yet another post that iterates over items to measure performance of different loops in JavaScript.