typescript 48 lines · 9 steps

A cursor-based infinite scroll hook in React

A custom React hook that loads paginated data automatically as a sentinel element scrolls into view.

Explained by highlit
1import { useCallback, useEffect, useRef, useState } from "react";
2 
3interface Page<T> {
4 items: T[];
5 nextCursor: string | null;
6}
7 
8export function useInfiniteScroll<T>(
9 fetchPage: (cursor: string | null) => Promise<Page<T>>,
10) {
11 const [items, setItems] = useState<T[]>([]);
12 const [loading, setLoading] = useState(false);
13 const cursorRef = useRef<string | null>(null);
14 const hasMoreRef = useRef(true);
15 const sentinelRef = useRef<HTMLDivElement | null>(null);
16 
17 const loadMore = useCallback(async () => {
18 if (loading || !hasMoreRef.current) return;
19 setLoading(true);
20 try {
21 const page = await fetchPage(cursorRef.current);
22 setItems((prev) => [...prev, ...page.items]);
23 cursorRef.current = page.nextCursor;
24 hasMoreRef.current = page.nextCursor !== null;
25 } finally {
26 setLoading(false);
27 }
28 }, [fetchPage, loading]);
29 
30 useEffect(() => {
31 const node = sentinelRef.current;
32 if (!node) return;
33 
34 const observer = new IntersectionObserver(
35 (entries) => {
36 if (entries[0].isIntersecting) {
37 void loadMore();
38 }
39 },
40 { rootMargin: "400px" },
41 );
42 
43 observer.observe(node);
44 return () => observer.disconnect();
45 }, [loadMore]);
46 
47 return { items, loading, hasMore: hasMoreRef.current, sentinelRef };
48}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Cursor pagination pairs naturally with a ref so each fetch reads the latest position without triggering re-renders.
  2. 2IntersectionObserver with a rootMargin prefetches the next page before the user reaches the bottom.
  3. 3Storing 'more data exists' in a ref avoids stale closures and keeps the guard logic out of the render cycle.

Related explainers

Share this explainer

Here's the card — post it anywhere.

A cursor-based infinite scroll hook in React — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code