DOM

useInfiniteScroll

Triggers loading near the scroll (MDN, opens in a new tab) end.

Demo

  • mock item #01
  • mock item #02
  • mock item #03
  • mock item #04
  • mock item #05
  • mock item #06
  • mock item #07
  • mock item #08
loaded
8 / 32
isFetching
false

scroll the page toward its bottom to load more

Usage

use-infinite-scroll.tsxtsx
import { useCallback, useState } from "react";
import { useInfiniteScroll } from "hookli";

const page = (start: number) =>
  Array.from({ length: 10 }, (_, i) => `Item ${start + i + 1}`);

export function Demo() {
  const [items, setItems] = useState(() => page(0));

  const fetchMoreData = useCallback(
    () =>
      new Promise<void>((resolve) => {
        setItems((prev) => [...prev, ...page(prev.length)]);
        resolve();
      }),
    [],
  );

  const isFetching = useInfiniteScroll(fetchMoreData);

  return (
    <div>
      <ul>
        {items.map((item) => (
          <li key={item}>{item}</li>
        ))}
      </ul>
      {isFetching && <p>Loading more…</p>}
    </div>
  );
}

API

useInfiniteScroll.d.tsts
useInfiniteScroll(fetchMoreData: () => Promise<void>): boolean

Parameters

NameTypeDefaultDescription
fetchMoreData() => Promise<void>Called when the window scroll comes within 500px of the document bottom. Must return a promise — the hook stays in the fetching state until it resolves.

Returns

NameTypeDescription
isFetchingbooleanTrue while a triggered fetchMoreData promise is pending; blocks re-triggering until it resolves.

Hook

The implementation, straight from hookli. Copy it, or install the package.

src/hooks/useInfiniteScroll.hook.tstsx
import { useEffect, useState } from "react";

type FetchMoreData = () => Promise<void>;

export const useInfiniteScroll = (fetchMoreData: FetchMoreData): boolean => {
  const [isFetching, setIsFetching] = useState(false);

  useEffect(() => {
    const handleScroll = () => {
      const isNearBottom =
        window.innerHeight + window.scrollY >=
        document.body.offsetHeight - 500;

      if (isNearBottom && !isFetching) {
        setIsFetching(true);
        fetchMoreData().then(() => setIsFetching(false));
      }
    };

    window.addEventListener("scroll", handleScroll);
    return () => {
      window.removeEventListener("scroll", handleScroll);
    };
  }, [fetchMoreData, isFetching]);

  return isFetching;
};

View source on GitHub

Related hooks