The Basic Hook
typescriptimport { useEffect, useRef, useCallback, useState } from 'react' interface UseInfiniteScrollOptions { hasNextPage: boolean fetchNextPage: () => void isFetchingNextPage: boolean } export function useInfiniteScroll({ hasNextPage, fetchNextPage, isFetchingNextPage, }: UseInfiniteScrollOptions) { const observerRef = useRef<IntersectionObserver | null>(null) const loadMoreRef = useRef<HTMLDivElement | null>(null) const handleObserver = useCallback( (entries: IntersectionObserverEntry[]) => { const [target] = entries if (target.isIntersecting && hasNextPage && !isFetchingNextPage) { fetchNextPage() } }, [hasNextPage, fetchNextPage, isFetchingNextPage] ) useEffect(() => { if (!loadMoreRef.current) return observerRef.current = new IntersectionObserver(handleObserver, { threshold: 1.0, }) observerRef.current.observe(loadMoreRef.current) return () => { if (observerRef.current) { observerRef.current.disconnect() } } }, [handleObserver]) return { loadMoreRef } }
Using the Hook
tsxfunction PostsList() { const [posts, setPosts] = useState([]) const [page, setPage] = useState(1) const [hasNextPage, setHasNextPage] = useState(true) const [loading, setLoading] = useState(false) const fetchNextPage = async () => { setLoading(true) const newPosts = await fetch(`/api/posts?page=${page}`) if (newPosts.length === 0) { setHasNextPage(false) } else { setPosts(prev => [...prev, ...newPosts]) setPage(prev => prev + 1) } setLoading(false) } const { loadMoreRef } = useInfiniteScroll({ hasNextPage, fetchNextPage, isFetchingNextPage: loading, }) return ( <div> {posts.map(post => ( <div key={post.id}>{post.title}</div> ))} {hasNextPage && ( <div ref={loadMoreRef}> {loading && <div>Loading...</div>} </div> )} </div> ) }
Key Benefits
- Performance: Uses Intersection Observer instead of scroll events
- Reusable: Works with any data fetching logic
- Accessible: Proper loading states for screen readers
- Clean: Separates concerns between data fetching and UI
This hook handles the complexity of infinite scrolling while keeping your components clean and focused.