React useInfiniteScroll Hook
Infinite scroll feels simple until you wire IntersectionObserver, page tracking, and the request lifecycle together. This snippet covers a sentinel-ref hook that fires a load callback when the bottom marker scrolls into view, a paginated variant that tracks the page state for you, and an end-of-feed guard that stops calling the callback once the server says there is no more data.
354 views
1
function useInfiniteScroll(onLoad, options = {}) {
const sentinelRef = useRef(null);
const cbRef = useRef(onLoad);
useEffect(() => { cbRef.current = onLoad; }, [onLoad]);
useEffect(() => {
if (typeof IntersectionObserver === 'undefined') return undefined;
const node = sentinelRef.current;
if (!node) return undefined;
const observer = new IntersectionObserver((entries) => {
for (const entry of entries) {
if (entry.isIntersecting) cbRef.current();
}
}, { rootMargin: options.rootMargin || '200px', threshold: options.threshold || 0 });
observer.observe(node);
return () => observer.disconnect();
}, [options.rootMargin, options.threshold]);
return sentinelRef;
}
function useRef(v) { return { current: v }; }
function useEffect(fn) { fn(); }
const sentinel = useInfiniteScroll(() => console.log('load more'));
console.log('sentinel ref:', sentinel.current);The hook hands back a ref for the consumer to attach to a sentinel element near the end of the list. An IntersectionObserver watches that node, and when it crosses the viewport (with a 200px head start so loading begins before the user hits bottom), the callback fires. Storing the callback in a ref keeps the observer stable across renders so it does not detach and reattach when parent state changes. This is conceptually simpler than scroll-event math and works correctly even when the list is inside a scrollable container.
2 more snippets in this entry are available for premium members.
Upgrade to Premium