npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-virtual-list

v0.1.1

Published

Virtualized list for React Native and react-native-web that keeps its scroll position when item heights are only known after they render

Readme

react-native-virtual-list

A virtualized list for React Native and react-native-web that keeps its scroll position when item heights are only known after they render.

npm install react-native-virtual-list
import { VirtualList } from 'react-native-virtual-list'

<VirtualList
  data={messages}
  estimatedItemHeight={72}
  keyExtractor={(item) => item.id}
  renderItem={({ item }) => <Message {...item} />}
  onStartReached={loadOlder}
/>

Should you use this

Probably not, and it is worth being straight about that before you install anything.

If you are on React Native and you want the fastest list with the most users behind it, use FlashList. It has 1.8 million weekly downloads, a team at Shopify behind it, per type recycling pools, and it enables maintainVisibleContentPosition by default. Legend List is also good and is built with chat in mind.

Neither of those is going to be beaten here, and this does not try to.

The case for this one is narrow. Two things:

react-native-web does not implement maintainVisibleContentPosition at all. Not partially, not with caveats. The string does not appear anywhere in react-native-web 0.21.2. On native, the platform holds your scroll position when you insert content above the viewport, and it does it by measuring real frames rather than trusting any estimate, which is why it cannot be fooled by a bad guess. On web you get none of that, so every list has to hold position itself. This one does, by tracking an item and its offset rather than a scroll position.

Item positions here cannot go out of order. Offsets are prefix sums over a Fenwick tree rather than an array of positions that gets partially rebuilt. That rules out a specific failure where a partially corrected position table stops being sorted and the binary search over it returns an item nowhere near the one you asked for. That is not hypothetical, it is Shopify/flash-list#2307, where asking for item 250 renders item 333. There is a test in this repo that reproduces that mechanism and shows it cannot happen here.

If you are on native only, and FlashList works for you, use FlashList.

Props

| Prop | Meaning | | --- | --- | | data | The array | | renderItem | ({ item, index }) => element | | keyExtractor | (item, index) => string, worth setting | | estimatedItemHeight | A starting guess, corrected by measurement. Default 100 | | overscan | Rows to keep mounted either side of the viewport. Default 3 | | onStartReached | Fires once when the top is reached, rearms when you scroll away | | onEndReached | Same at the bottom | | maintainVisiblePosition | Passes maintainVisibleContentPosition to the scroll view. Default true | | ListEmptyComponent | Rendered when data is empty |

Ref

ref.current.scrollToIndex(4823, { align: 'center', animated: true })
ref.current.scrollToEnd()
ref.current.prepend(50)          // older messages arrived at the top
ref.current.getController()      // the layout underneath, if you need it

prepend is explicit rather than inferred from data growing, because only you know which end changed, and guessing wrong is what makes a list jump.

Using the layout on its own

The positioning is independent of React and is exported if you want to build your own list on it.

import { ListLayout } from 'react-native-virtual-list'

const layout = new ListLayout({ count: 1_000_000, estimate: 72 })

layout.offsetOf(999_999)          // O(log n)
layout.indexAt(41_530_112)        // O(log n)
layout.setHeight(4823, 118)       // O(log n)
layout.visibleRange(offset, viewportHeight, overscan)

const anchor = layout.captureAnchor(scrollOffset)
// heights change, items get prepended, whatever happens
layout.offsetForAnchor(anchor)    // the anchored item has not moved

What is tested

56 automated tests, a manual pass on a physical iOS device, and a browser pass on react-native-web.

43 unit tests covering the layout and the controller, including differential fuzzing of the tree against a naive O(n) implementation, the anchor holding its screen position across 40 random measurement trials and 30 prepend trials, and a run at a million items checking that offsets stay ordered, indexAt inverts offsetOf, and the total does not drift from the sum of the heights.

13 render tests driving the real component through react-test-renderer with the layout events a host would send: that only the visible rows mount, that a hundred thousand rows still mount fewer than fifteen, that measurement corrects positions, that scrollToIndex lands on the right row when nothing has been measured, and that prepending does not move the anchored row.

A manual pass on a physical iOS device, covering scrolling, prepending and scrollToIndex. That is what the automated tests cannot reach: real scroll momentum and native layout commit ordering.

A browser pass on react-native-web, which is the case the library exists for. A real app under Vite with react-native aliased to react-native-web, 1000 rows of varying height with the estimate deliberately wrong, scrolled into the middle and then prepended 200 rows at a time, four times over, to 2000 rows. A probe reads the real getBoundingClientRect().top of a tracked row before and after each prepend, independently of anything the library reports about itself. Every round the scroll position was adjusted by exactly the amount the content grew, and the tracked row held its screen position to the pixel.

That pass is the reason this is 0.1.1 rather than 0.1.0. In 0.1.0 web prepending was broken: the layout maths was right, and the component never wrote the corrected offset back to the scroll view, so the list jumped by the full height of the inserted content. On native the platform hides that, because maintainVisibleContentPosition does the correction itself. On web nothing does. The fix applies the correction on web only, since doing it on both would compensate twice.

Cost

| | 1M items | 5M items | | --- | --- | --- | | Build | 11 ms | 52 ms | | visibleRange | 0.84 us | 1.15 us | | setHeight | 0.33 us | 0.29 us | | Prepend 50 | 0.09 ms | 0.02 ms | | Index memory | 12 MB | 58 MB |

13 bytes per item slot. At a million items the index is 12 MB, which is usually less than the data being listed.

What it does not do

No recycling. Rows mount and unmount rather than being reused with new props. FlashList's recycling pools are faster for long fast scrolls through uniform rows. This trades that for simpler behaviour and no stale state in reused rows.

Android has not been run. iOS is covered on real hardware and web is covered in a browser, but nothing here has been exercised on Android. It should behave like iOS, because both go through the platform's own maintainVisibleContentPosition, but that is reasoning rather than evidence.

No columns, no masonry, no sticky headers. Single column vertical lists only.

It cannot make an estimate correct. The position of a row nobody has rendered is unknown. What is guaranteed is that unknown positions stay consistent and ordered, not that they are right.

License

MIT