Virtual List
Renders only the rows in view. DataGrid and LogViewer both render every row they are given, which is fine until a few thousand and then stalls the main thread.
src/components/ui/virtual-list.tsxAbout
From the component's own source, where it was written.
Renders only the rows in view.
DataGrid and LogViewer both render every row they are given, which is fine until a few thousand and then stalls the main thread on every update. This is the layer they can sit on.
Fixed row height by design. Variable heights need measurement, a cache and a correction pass for the scroll position when an estimate turns out wrong — an order of magnitude more code, and wrong estimates make the scrollbar jump under the user's hand. A fixed height keeps the maths exact.
overscan renders a few rows beyond the viewport so a fast scroll does not expose blank space before the next paint.
Install
npx astralyx-ui add virtual-list
Usage
import { VirtualList } from '@/components/ui/virtual-list'
<VirtualList
items={rows}
rowHeight={36}
renderItem={(row) => <Row row={row} />}
onEndReached={loadMore}
/>Composer
Change the props and the source below follows.
<VirtualList
items={rows}
rowHeight={36}
height={280}
renderItem={(row) => <Row row={row} />}
/>Examples
5,000 rows
<VirtualList items={rows} rowHeight={36} height={280} renderItem={…} />API
Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.
VirtualList<Item>
Renders only the rows in view.
Extends Omit<ComponentProps<'div'>, 'children' | 'height'>.
| Prop | Type | Default | Description |
|---|---|---|---|
| items | Item[] | required | Fixed height by design: variable heights need measurement, a cache and a scroll correction pass, and wrong estimates make the scrollbar jump under the pointer. |
| rowHeight | number | required | Fixed height by design: variable heights need measurement, a cache and a scroll correction pass, and wrong estimates make the scrollbar jump under the pointer. |
| renderItem | (item: Item, index: number) => ReactNode | required | The index is the real one in the full list, not the slice. |
| overscan | number | 4 | Rows rendered beyond the viewport so a fast scroll does not expose blank space. |
| height | number | string | 400 | — |
| onEndReached | () => void | — | Infinite scrolling. Guarded by item count, so it fires once per batch rather than on every scroll event near the bottom. |
| endThreshold | number | 200 | — |