Infinite Scroll ListLoading saved progress…

Infinite Scroll List

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.

Task

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:

  1. Hold the data. Track the visible items with useState (starting at the first 10) and a loading boolean. Derive done when all 50 are shown.
  2. Watch the sentinel. In a useEffect, create an IntersectionObserver whose root is the scroll container (listRef), and observe the sentinel. When it intersects, load the next page.
  3. Load a page. Append the next 10 items (ALL.slice(0, prev.length + PAGE)) after a ~400ms setTimeout that simulates a fetch. Show Loading… while it's in flight.
  4. Guard and stop. Ignore intersections while a load is already running, and at 50 items show No more items and disconnect the observer.

Examples

  • The page loads showing "Item 1" through "Item 10". Scroll to the bottom and a "Loading…" row appears; ~400ms later "Item 11"–"Item 20" are appended.
  • Keep scrolling and pages keep arriving until "Item 50", after which the footer reads "No more items" and no further loads fire.
  • Scrolling fast doesn't fire two loads at once — the in-flight guard collapses them into one page.

Notes

  • The sentinel must live inside the scroll container. The observer's 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.
  • The in-flight guard is not optional. The sentinel can stay intersecting across several callbacks; without a guard you'd fire overlapping loads. Keep the guard in a ref so the observer callback reads it synchronously.
  • Disconnect when done. Once every item is shown, io.disconnect() in the effect cleanup so the callback stops firing against a sentinel that will never move again.
  • The layout and scroll box are already in styles.css; focus on the observer and the paging state.
Loading editor…
Loading preview…