Skip to content

Releases: vmariev/react-async-list

v1.0.2

Choose a tag to compare

@vmariev vmariev released this 15 Aug 12:02

Documentation and repository tooling only — no runtime changes.

Link a live CodeSandbox from the top of the README, from the quick start, and from the virtualizer guide, so every example can be tried without cloning anything.
Finish the virtualizer guide. It now shows the exported useMergedRef solving the one-node-two-owners problem, and points at example/demos/Virtualized.tsx, which pages 5000 rows while keeping about fifteen of them in the DOM.
Document the release process end to end — changeset, version bump, tag, GitHub release — including the two things that are easiest to get wrong: NPM_TOKEN needs write access to the whole @vmariev scope rather than to a list of packages, and a published version can never be replaced.
Remove the unused release script. It published outside the workflow, which skipped the provenance attestation and made a duplicate publish of the same version possible.

v1.0.1

Choose a tag to compare

@vmariev vmariev released this 15 Aug 11:34

Documentation and npm metadata only — no runtime changes. The virtualizer integration is now a first-class part of the docs, and @tanstack/react-virtual is clearly marked as a demo-only devDependency rather than a dependency of the package.

v1.0.0

Choose a tag to compare

@vmariev vmariev released this 15 Aug 10:25

A scroll container for React that loads more items as either edge comes into view — with the awkward parts of infinite scrolling already handled.

npm install @vmariev/react-async-list
<AsyncList
  style={{ height: 400 }}
  fetchDown={loadNextPage}
  isDisableFetchDown={!hasMore}
>
  {users.map((user) => (
    <UserRow key={user.id} user={user} />
  ))}
</AsyncList>

Highlights

  • Zero runtime dependencies. Only react as a peer (18 and 19). Nothing to import, no CSS file to remember — styles are injected at runtime, and shipped as @vmariev/react-async-list/styles.css if you'd rather import them yourself.
  • Bidirectional, with each direction independent: a slow fetchUp never blocks fetchDown.
  • Reverse (chat) mode — bottom-anchored, new messages don't move the view, scrolling up loads history.
  • Headless option. useAsyncList hands you the loading engine without any markup, so it drives a <table>, a grid, or a virtualizer just as happily as the component does.
  • Optional custom scrollbar that looks the same on every platform, or the browser's own, or none.
  • ESM + CJS + TypeScript declarations.

It won't flood your API

The one thing worth reading about before you use any infinite-scroll list. A list parked at an edge with a live fetcher is easy to turn into a denial-of-service attack on your own backend, and it needs no user interaction at all:

parked at the edge → fetch → returns nothing (normal, you're at the end)
                   → loading state flips → re-render → re-check
                   → still at the edge → fetch → …forever, ~3 requests/second

This library tracks a content signature per direction and refuses to ask the same question twice: a fetch that comes back empty quiets that direction until the content, the viewport, or a disable flag actually changes. Measured, not assumed — the regression suite asserts an exact request count of one across every configuration, including reverse mode, inline fetchers, and a parent re-rendering faster than the internal throttle.

How loading is triggered →

Reverse mode works on engines that disagree with Chromium

flex-direction: column-reverse changes where the scroll origin sits, and engines differ: most report scrollTop as -max … 0 with 0 at the visual bottom, others use the standard 0 … max. Hard-coding either one silently inverts every write on the engine that disagrees.

So the library doesn't assume. It probes the container once and normalises everything to a single quantity — distance from the visual top. Every reverse-mode behaviour is tested against both conventions.

The negative scrollTop problem →

Working with very long lists

This renders every child, so pair it with a real virtualizer rather than waiting for one to be built in — that's what the headless hook is for. The demo pages 5000 rows through @tanstack/react-virtual while keeping about fifteen of them in the DOM.

With a virtualizer →

Notes

The component grew out of one that had been in production inside a private codebase. Its prop names (deathZone, triggerTopPosition, isHiddenScroll, contentElementId) are still accepted as @deprecated aliases, so existing code keeps compiling while your editor points at the replacements — see Coming from the original component.

A dozen defects carried by the original are fixed here, including a rejected fetch permanently wedging a direction, head-of-line blocking between directions, and a thumb drag that outlived its component and left the whole page stuck with cursor: grabbing. The changelog has the full list.

140 tests, run in CI on Node 20 and 22.

Full documentation: https://github.com/vmariev/react-async-list#readme