Skip to content

Infinite List Sentinel

Sentinel-based pagination with the stall fixed — the bug where a short page leaves the sentinel on screen and loading stops.

Prominimal
Category
Patterns
Added
2026-09-21
Deps
none
Updated
2026-09-21

This is a viberdy Pro component

The live preview, the props panel and the write-up are open to everyone. The full source, the AI prompt and CLI install are part of Pro.

Preview

    scroll or press load more

    Props

    12
    60
    500
    200
    No runtime dependencies
    "use client";
    
    import { useEffect, useRef, useState } from "react";
    
    export function InfiniteListSentinel({
      pageSize = 12,
      total = 60,
      latency = 500,
      prefetchPx = 200,
      load,
    }: {
      pageSize?: number;
      total?: number;
      latency?: number;
      /** How far below the fold to start loading. */
      prefetchPx?: number;
      /** Your real fetch. Omitted, a local stand-in is used. */
      load?: (offset: number, limit: number) => Promise<string[]>;
    
      // ---------------------------------------------------------------
      // Full source available with a viberdy Pro subscription.
      // https://viberdy.dev/pricing
      // ---------------------------------------------------------------
    }
    Notes

    About this pattern

    Sentinel pagination has two failure modes that are exact opposites, and shipping a fix for one usually introduces the other. The obvious one is the double fetch: two intersection callbacks can land in the same tick and both read the same stale loading flag out of the closure, so the guard has to be a ref that updates synchronously rather than a piece of state. The subtle one — and the one almost everybody ships — is the stall. IntersectionObserver reports threshold CROSSINGS, not a continuing condition, so if a page of rows happens to be short enough that the sentinel is still on screen once they render, no further callback ever arrives and loading stops dead halfway down a list that has plenty more. The fix is to ask the question again after every load: attach a fresh short-lived observer and, if the sentinel is still intersecting, fetch again. Two more things separate this from the usual version. There is a real end condition, so the list stops requesting empty pages forever. And there is always a real Load more button alongside the sentinel, because infinite scroll with no manual trigger is a trap — there is no keyboard gesture for “scroll until more appears”, and anything below the list becomes permanently unreachable. New rows are announced through a polite live region, since appending silently just makes the page longer with no indication that anything happened.

    Curator’s note

    The stall is the interesting bug: IntersectionObserver fires on crossings, so a short page that leaves the sentinel visible never fires again.

    Related

    Pairs well with

    Matched on shared tags and category — the entries most likely to be used alongside this one.

    A drag-handle-driven list that reorders with a smooth layout swap.

    dragsortablelist
    Featured
    New

    An index list that floats a velocity-tilted preview card beside the pointer.

    hoverpreviewlist
    Featured
    NewPro

    A list that dims and blurs every row except the one under the pointer.

    listhoverfocus