Skip to content
kaskaid

Infinite Loading

Fetch the next page as the grid scrolls

kaskaid never fetches anything for you. It only tells you when the last rendered item has come close enough to the end of your data that it is time to load more — you wire that signal to whatever data layer you already use (TanStack Query, SWR, Apollo, a raw loader).

Both flavors expose the same trigger. The component takes an onEndReached callback; the hook lets you call useEndReached yourself when you need custom markup around the grid.

With the component

The simplest way to implement infinite loading is to pass onEndReached directly to the component:

function () {
  const { , , ,  } = ({
    : ['items'],
    : ({  }) => (),
    : 0,
    : () => .,
  });
 
  const  = ?..(() => .) ?? [];
 
  return (
    <
      ={}
      ={() => []?. ?? 200}
      ={({  }) => < ={.} ="" ={{ : . }} />}
      ={}
      ={20}
      ={! || }
    />
  );
}

With the hook

Reach for the hook when you need custom markup around the grid — a loading row, an end-of-feed message, or a different outer element. useEndReached composes directly with useMasonry:

const {  } = ({ , : () => [] });
 
(, ., , {
  : 20,
  : ! || ,
});

onEndReached is a plain no-arg callback, so you can pass your loader (fetchNextPage, setSize, fetchMore) directly — no wrapper needed.

With TanStack Query

Flatten the pages into data, then hand useEndReached a callback that requests the next page. Gate re-firing with disabled while a page is in flight or there is no next page.

const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({
  /* … */
});
const items = useMemo(() => data?.pages.flatMap((p) => p.items) ?? [], [data]);
 
const { gridProps, getItemProps, items: virtualItems } = useMasonry({ data: items /* … */ });
 
useEndReached(virtualItems, items.length, fetchNextPage, {
  threshold: 20,
  disabled: !hasNextPage || isFetchingNextPage,
});

Choosing a threshold

items includes overscan-rendered entries, so the last rendered index already runs ahead of the visible edge. For prefetch-ahead, set threshold (or endReachedThreshold) to a value ≥ your overscan — otherwise overscan alone can outrun loaded data before the guard fires.

Batched range-loading and sparse (isItemLoaded) loading are intentionally out of scope; compose them yourself if a large overscan or slow network needs them.

Reference