Infinite Scroll with Intersection Observer
Load more items as the user scrolls using IntersectionObserver. No external libraries required.
import { useEffect, useRef, useState, useCallback } from 'react';
interface UseInfiniteScrollOpts<T> {
fetchPage: (page: number) => Promise<T[]>;
initialPage?: number;
}
export function useInfiniteScroll<T>({ fetchPage, initialPage = 1 }: UseInfiniteScrollOpts<T>) {
const [items, setItems] = useState<T[]>([]);
const [page, setPage] = useState(initialPage);
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);
const sentinelRef = useRef<HTMLDivElement | null>(null);
const loadMore = useCallback(async () => {
if (loading || !hasMore) return;
setLoading(true);
const newItems = await fetchPage(page);
if (newItems.length === 0) {
setHasMore(false);
} else {
setItems((prev) => [...prev, ...newItems]);
setPage((p) => p + 1);
}
setLoading(false);
}, [fetchPage, page, loading, hasMore]);
useEffect(() => {
const sentinel = sentinelRef.current;
if (!sentinel) return;
const observer = new IntersectionObserver(
([entry]) => { if (entry.isIntersecting) loadMore(); },
{ rootMargin: '200px' }
);
observer.observe(sentinel);
return () => observer.disconnect();
}, [loadMore]);
return { items, loading, hasMore, sentinelRef };
}
// Usage:
// const { items, loading, sentinelRef } = useInfiniteScroll({
// fetchPage: (page) => fetch(`/api/posts?page=${page}`).then(r => r.json()),
// });
// return <>{items.map(renderItem)}<div ref={sentinelRef} /></>;Use Cases
- Feed pagination
- Product listing pages
- Comment threads
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
useDebounce — Debounced Value Hook
Debounce any rapidly-changing value with a configurable delay. Useful for search inputs and resize handlers.
useIntersectionObserver Hook
Track element visibility with the Intersection Observer API for lazy loading and scroll animations.
useFetch — Generic Data Fetching Hook
Custom React hook for data fetching with loading, error, and refetch states using the Fetch API.
useLocalStorage — Persistent State Hook
Sync React state with localStorage including SSR safety, JSON serialization, and cross-tab updates.