Mar 31, 2020

Introducing useInfiniteScroll: a simple React hook for infinite scroll experience

by Lukáš Mladý and Vitor Buzinaro

Project link: https://github.com/closeio/use-infinite-scroll

We’re excited to introduce the newest addition to our frontend-related open source projects. useInfiniteScroll is a super simple React hook for creating an infinite scroll experience based on the IntersectionObserver API.

Check the live DEMO.

We are taking advantage of this tiny library in our Pipeline View feature (Trello-like board), where there are multiple columns, each scrollable separately. In the Pipeline View, we also use another library that we open sourced called react-custom-scroller, which allows use to have cross-browser and cross-device scrollbars.

Summary

  • Extremely lightweight (less than 1KB minzipped).
  • It uses the IntersectionObserver API, so it doesn't need to listen to scroll events, which are known to cause performance issues.
  • No other 3rd-party dependencies.

Installation

yarn add @closeio/use-infinite-scroll

API

The API of useInfiniteScroll hook is pretty straightforward.

It takes an options object:

type UseInfiniteScrollOptions = {
  // The observer will disconnect when there are no more items to load.
  hasMore: boolean;

  // Pass true when you're re-fetching the list and want to resets the scroller
  // to page 0.
  // Defaults to `false`.
  reset?: string;

  // When scrolling, the distance in pixels from the bottom to switch the page.
  // Defaults to `250` (in px).
  distance?: number;
};

And it returns a tuple:

type UseInfiniteScrollResult = [
  number, // The current page (starting at 0)
  RefObject<T>, // React ref to a loader (spinner) element
  RefObject<T> // React ref to the scroll container element
];

Example

import React from 'react';
import useInfiniteScroll from '@closeio/use-infinite-scroll';

export default function MyComponent() {
  const [items, setItems] = useState([]);
  const [hasMore, setHasMore] = useState(false);
  const [page, loaderRef, scrollerRef] = useInfiniteScroll({ hasMore });

  useEffect(async () => {
    const data = await myApiCall({ page });
    setHasMore(data.hasMore);
    setItems((prev) => [...prev, data.items]);
  }, [page]);

  return (
    <div ref={scrollerRef}>
      {items.map((item) => (
        <div key={item.id}>{item.name}</div>
      ))}
      {hasMore && <div ref={loaderRef}>Loading…</div>}
    </div>
  );
}

What it looks like

useInfiniteScroll in practice