Infinite scroll is a pattern where new data loads automatically when scrolling down. The user does not need to click "Show more".
Why it matters: Infinite scroll creates a seamless browsing experience similar to social media feeds (Twitter, Instagram). It reduces the number of page loads, keeps users engaged longer, and is especially effective for content-heavy applications like news feeds, product catalogs, and search results.
How it works step-by-step:
1import { useState, useEffect, useRef, useCallback } from "react";23function InfiniteList() {4 const [items, setItems] = useState([]);5 const [page, setPage] = useState(1);6 const [loading, setLoading] = useState(false);7 const [hasMore, setHasMore] = useState(true);8 const observerRef = useRef(null);9 const lastItemRef = useRef(null);1011 // Load data12 useEffect(() => {13 setLoading(true);14 fetch(`/api/items?page=${page}&limit=20`)15 .then(res => res.json())16 .then(newItems => {17 setItems(prev => [...prev, ...newItems]);18 setHasMore(newItems.length === 20);19 setLoading(false);20 });21 }, [page]);2223 // Intersection Observer to track the last element24 const lastItemCallback = useCallback((node) => {25 if (loading) return;26 if (observerRef.current) observerRef.current.disconnect();2728 observerRef.current = new IntersectionObserver(entries => {29 if (entries[0].isIntersecting && hasMore) {30 setPage(p => p + 1);31 }32 });3334 if (node) observerRef.current.observe(node);35 }, [loading, hasMore]);3637 return (38 <div>39 {items.map((item, i) => (40 <div41 key={item.id}42 ref={i === items.length - 1 ? lastItemCallback : null}43 >44 {item.name}45 </div>46 ))}47 {loading && <Spinner />}48 {!hasMore && <p>No more data</p>}49 </div>50 );51}
Performance considerations:
Common pitfalls:
hasMore before setting a new page leads to fetching empty data.setItems(prev => [...prev, ...newItems]) is correct — never overwrite the previous array.Libraries:
useInfiniteQuery.