This project uses the Intersection Observer API to implement infinite scrolling in a clean and performant way.
useState→ Manage products, pagination, and loading stateuseEffect→ Handle side effects (API calls + observer setup)useRef→ Reference the loader elementIntersectionObserver→ Detect when user reaches bottom
- Initial render → empty product list
- Loader element is observed using
IntersectionObserver - When loader becomes visible:
- API request is triggered
- New products are appended
- Process repeats until no more data
👉 https://dummyjson.com/products
Supports pagination via:
limit→ number of items per requestskip→ offset
const [products, setProducts] = useState([]);
const [page, setPage] = useState(0);
const [hasMore, setHasMore] = useState(true);const loaderRef = useRef(null);const fetchProducts = async () => {
const response = await fetch(
`https://dummyjson.com/products?limit=${productsPerPage}&skip=${page * productsPerPage}`
);
const data = await response.json();
if (data.products.length < productsPerPage) {
setHasMore(false);
} else {
setProducts(prev => [...prev, ...data.products]);
setPage(prev => prev + 1);
}
};const onIntersection = (items) => {
const loaderItem = items[0];
if (loaderItem.isIntersecting && hasMore) {
fetchProducts();
}
};
const observer = new IntersectionObserver(onIntersection);useEffect(() => {
if (observer && loaderRef.current) {
observer.observe(loaderRef.current);
}
return () => {
if (observer) observer.disconnect();
};
}, [hasMore, page]);{products.map(product => (
<CardComponent
key={product.id}
title={product.title}
description={product.description}
image={product.thumbnail}
price={product.price}
/>
))}{hasMore && <div ref={loaderRef}>Loading more products ...</div>}| Feature | Scroll Event | Intersection Observer |
|---|---|---|
| Performance | ❌ Heavy | ✅ Efficient |
| Accuracy | ❌ Manual calc | ✅ Automatic |
| Code Complexity | ❌ Higher | ✅ Cleaner |
- ✅ Lazy loading data
- ✅ Avoid unnecessary API calls
- ✅ Cleanup observer on unmount
- ✅ Append data instead of replacing
- ✅ Stop fetching when no more data
- Add loading spinner/skeleton UI
- Add error handling (try/catch)
- Debounce API calls
- Use useCallback for fetch function
- Add API caching
If asked:
👉 "Why use IntersectionObserver instead of scroll?"
Answer:
It improves performance by avoiding continuous scroll event listeners and only triggers when the target element becomes visible in the viewport.