Infinite Scroll
Load feed (or list) data in pages as the user scrolls, instead of loading everything upfront.
The problem I keep seeing
Long feeds (social timelines, product grids, activity logs) can’t load all items at once: slow initial load, huge DOM, and wasted bandwidth. You need pagination. The UX question is whether to use a “Load more” button or to load automatically when the user scrolls near the bottom—infinite scroll.
Infinite scroll is familiar from Twitter, Instagram, and many dashboards. The implementation pitfalls: cursor vs offset pagination, avoiding duplicate or missing items when the list changes, and (if the list is very long) combining with virtualization so you don’t mount thousands of DOM nodes.
Naive approach
Fetch the whole list with a large limit. Works only for small datasets.
First improvement
Paginate: fetch page 1, then page 2, etc., and append to state. Use a “Load more” button so the user explicitly requests the next page. This fixes memory and DOM size; the only downside is the extra click for “infinite” feel.
Why this helps: You only render what you’ve loaded. Backend can use LIMIT/OFFSET or cursor-based pagination.
Remaining issues
- Offset pagination:
page=2can skip or duplicate items if the list changes between requests (e.g. new item inserted at top). Cursor-based (e.g.after=id_xyz) is stable. - No automatic load on scroll: Users expect scrolling to the bottom to load more. You need a sentinel element and
IntersectionObserver(or a library that does it). - Caching and deduplication: React Query’s
useInfiniteQueryhandles page caching and gives youfetchNextPageandhasNextPageout of the box.
Production pattern
Use useInfiniteQuery with cursor-based pagination. The API returns items and nextCursor; getNextPageParam tells React Query how to request the next page. Add a sentinel at the bottom and call fetchNextPage when it becomes visible.
When I use this
- Use: Feeds, timelines, product grids, activity logs—any long list where the user scrolls to consume content. Prefer cursor-based pagination for feeds that update in real time.
- Consider “Load more” instead: When you need a stable scroll position for accessibility (e.g. “Back to top” or predictable focus). Infinite scroll can make it hard to reach footer or repeat a “load more” action.
- Combine with virtualization: If the list has thousands of items in the DOM (even loaded in chunks), use a virtualized list so only visible rows are mounted.
A11y
Gotchas
- Root margin: With
IntersectionObserver, userootMargin: '200px'(or similar) so the next page starts loading before the user actually hits the bottom. - Stale closures: Pass
fetchNextPage,hasNextPage, andisFetchingNextPageinto the effect deps so the observer uses current values. - Duplicate keys: Flatten pages with
flatMap; ensure each item has a stablekey(id). If the API can return the same item in two pages during rebalancing, dedupe by id.