Build a scrollable list that loads more items as you reach the bottom — the "infinite scroll" pattern behind feeds and search results. Instead of doing scroll-position math, you'll watch a sentinel element at the end of the list with an IntersectionObserver and load the next page when it comes into view.
The starter App.tsx renders the first 10 items inside a fixed-height scroll box, with an empty footer holding the sentinel. Make it load more:
items with useState (starting at the first 10) and a loading boolean. Derive done when all 50 are shown.useEffect, create an IntersectionObserver whose root is the scroll container (listRef), and observe the sentinel. When it intersects, load the next page.ALL.slice(0, prev.length + PAGE)) after a ~400ms setTimeout that simulates a fetch. Show Loading… while it's in flight.No more items and disconnect the observer.aria-busy on the focusable scroll region to loading. Put the footer message in the existing polite role="status" span so loading and completion are announced without moving focus.root is listRef, so the watched element has to be a descendant of it — that's why the sentinel sits in the footer at the end of the list.io.disconnect() in the effect cleanup so the callback stops firing against a sentinel that will never move again.tabIndex={0}, a heading label, and a visible focus ring. Do not remove them; keyboard users need to focus the region before scrolling it..list { overflow-anchor: none; } lets newly appended rows push the sentinel below the viewport instead of keeping the bottom edge pinned and stalling the observer.styles.css; focus on the observer and the paging state.The full solution is part of Premium
Walkthrough, edge cases, complexity notes, and the runnable editor unlock with a Premium subscription.
Submissions are part of Premium
Unlock community code, comments, reactions, and framework-specific approaches.
Build a scrollable list that loads more items as you reach the bottom — the "infinite scroll" pattern behind feeds and search results. Instead of doing scroll-position math, you'll watch a sentinel element at the end of the list with an IntersectionObserver and load the next page when it comes into view.
The starter App.tsx renders the first 10 items inside a fixed-height scroll box, with an empty footer holding the sentinel. Make it load more:
items with useState (starting at the first 10) and a loading boolean. Derive done when all 50 are shown.useEffect, create an IntersectionObserver whose root is the scroll container (listRef), and observe the sentinel. When it intersects, load the next page.ALL.slice(0, prev.length + PAGE)) after a ~400ms setTimeout that simulates a fetch. Show Loading… while it's in flight.No more items and disconnect the observer.aria-busy on the focusable scroll region to loading. Put the footer message in the existing polite role="status" span so loading and completion are announced without moving focus.root is listRef, so the watched element has to be a descendant of it — that's why the sentinel sits in the footer at the end of the list.io.disconnect() in the effect cleanup so the callback stops firing against a sentinel that will never move again.tabIndex={0}, a heading label, and a visible focus ring. Do not remove them; keyboard users need to focus the region before scrolling it..list { overflow-anchor: none; } lets newly appended rows push the sentinel below the viewport instead of keeping the bottom edge pinned and stalling the observer.styles.css; focus on the observer and the paging state.The full solution is part of Premium
Walkthrough, edge cases, complexity notes, and the runnable editor unlock with a Premium subscription.
Submissions are part of Premium
Unlock community code, comments, reactions, and framework-specific approaches.
Unlock the solution & editor
Runnable editor + tests
Solve in the browser with instant Jest feedback.
Detailed solutions
Walkthroughs, edge cases, and complexity notes.
Multi-framework variants
React, Vue, Vanilla, Angular — same question, different stacks.