Complete guide to infinite scrolling in React

Infinite scrolling is a UX pattern where new content loads as users scroll down a page. Common in social feeds, timelines, and news apps, it’s a smooth way to explore large datasets without using pagination buttons. In this guide, you’ll learn how to build infinite scroll in React using both a third-party library and a custom hook.
Key Takeaways
- Learn 2 practical ways to implement infinite scrolling in React
- Use a custom hook with IntersectionObserver or a third-party package
- Handle loading states, pagination, and empty results
What is infinite scroll?
Infinite scroll loads new data as the user scrolls near the end of a list. It removes the need for pagination and creates a continuous browsing experience. You’ll see it in products like Instagram, Twitter, and Reddit.
Method 1: Using a package (react-infinite-scroll-component
)
The easiest way to implement infinite scroll in React is with a well-tested package.
Step 1: Install
npm install react-infinite-scroll-component
Step 2: Basic usage example
import InfiniteScroll from 'react-infinite-scroll-component';
function Feed() {
const [items, setItems] = useState([]);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const fetchMoreData = async () => {
const res = await fetch(`/api/posts?page=${page}`);
const newItems = await res.json();
if (newItems.length === 0) setHasMore(false);
else {
setItems(prev => [...prev, ...newItems]);
setPage(prev => prev + 1);
}
};
return (
<InfiniteScroll
dataLength={items.length}
next={fetchMoreData}
hasMore={hasMore}
loader={<h4>Loading...</h4>}
endMessage={<p>No more results</p>}
>
{items.map(item => <div key={item.id}>{item.title}</div>)}
</InfiniteScroll>
);
}
Pros
- Quick to set up
- Built-in loading and end states
Cons
- Less control over scroll logic
- Adds package dependency
Method 2: Custom hook with IntersectionObserver
This method gives you full control and no extra dependencies.
Step 1: Create a hook
function useInfiniteScroll(callback, ref) {
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) callback();
});
if (ref.current) observer.observe(ref.current);
return () => observer.disconnect();
}, [callback, ref]);
}
Step 2: Use in a component
function Feed() {
const [items, setItems] = useState([]);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const sentinelRef = useRef(null);
const loadMore = async () => {
if (!hasMore) return;
const res = await fetch(`/api/posts?page=${page}`);
const newItems = await res.json();
if (newItems.length === 0) setHasMore(false);
else {
setItems(prev => [...prev, ...newItems]);
setPage(prev => prev + 1);
}
};
useInfiniteScroll(loadMore, sentinelRef);
return (
<div>
{items.map(item => <div key={item.id}>{item.title}</div>)}
<div ref={sentinelRef} style={{ height: 1 }} />
</div>
);
}
Pros
- Fully customizable
- Works with any scroll logic or API
Cons
- Requires more setup
- IntersectionObserver not supported in IE11
Real-world patterns and tips
- Debounce fetch calls to avoid overloading APIs
- Show a spinner or skeleton loader for better UX
- Add retry logic for network errors
- Handle edge cases like zero results or empty pages
if (items.length === 0 && !hasMore) {
return <p>No posts found.</p>;
}
Performance considerations
- Use libraries like
react-window
for virtualization when rendering thousands of items - Memoize item components to prevent re-renders
- Clean up observers properly to avoid memory leaks
useEffect(() => {
const observer = new IntersectionObserver(...);
return () => observer.disconnect();
}, []);
Conclusion
Infinite scroll is a common feature in modern UIs. Whether you prefer the simplicity of a package or the control of a custom hook, React makes both approaches accessible. Always consider performance and loading feedback when implementing infinite scroll.
FAQs
Using a library like `react-infinite-scroll-component` gives you a fast and convenient setup.
Set a `hasMore` flag to false when the API returns an empty array.
It can, if you're rendering too many items. Use virtualization tools like `react-window` to manage large lists.
Use `IntersectionObserver` to trigger data loading when a bottom `div` (sentinel) comes into view.