A monorepo containing high-performance virtualized list components for React and Vue that efficiently render large datasets with dynamic sizing, lazy loading, and bi-directional scrolling support.
This monorepo contains the following packages:
| Package | Description | NPM |
|---|---|---|
| @lazy-virtual-scroll/react | Virtualized list component for React | |
| @lazy-virtual-scroll/vue | Virtualized list component for Vue 3 | |
| core | Core utilities used by both React and Vue implementations | - |
- Virtualized Rendering: Only renders the items currently visible in the viewport
- Dynamic Sizing: Automatically detects and handles items of varying heights
- Lazy Loading: Load data on-demand as the user scrolls
- Bi-directional Scrolling: Support for both vertical and horizontal scrolling
- Performance Optimized: Debounced and throttled scroll handling
- Flexible Data Structure: Support for continuous or fragmented datasets
- Background Loading: Optionally cache the whole list off the critical path and query it asynchronously, with IndexedDB storage so it never has to sit in memory
- TypeScript Support: Full type definitions included
npm install @lazy-virtual-scroll/reactimport LazyVirtualScroll from '@lazy-virtual-scroll/react';
<LazyVirtualScroll
totalItems={10000}
itemSize={50}
data={items}
onLoad={({ startIndex, endIndex }) => {
console.log(`Loading items ${startIndex} to ${endIndex}`);
}}
onHide={({ startIndex, endIndex }) => {
console.log(`Hiding items ${startIndex} to ${endIndex}`);
}}
render={(index, item) => (
<div style={{
height: '50px',
padding: '10px',
borderBottom: '1px solid #eee',
boxSizing: 'border-box'
}}>
{item ? item.text : 'Loading...'}
</div>
)}
renderLoading={(index) => (
<div style={{
height: '50px',
padding: '10px',
backgroundColor: '#f5f5f5',
boxSizing: 'border-box'
}}>
Loading item {index}...
</div>
)}
/>npm install @lazy-virtual-scroll/vue<LazyVirtualScroll
:totalItems="10000"
:itemSize="50"
:data="items"
@load="({ startIndex, endIndex }) => {
console.log(`Loading items ${startIndex} to ${endIndex}`);
}"
@hide="({ startIndex, endIndex }) => {
console.log(`Hiding items ${startIndex} to ${endIndex}`);
}"
>
<template #default="{ item, index }">
<div style="
height: 50px;
padding: 10px;
border-bottom: 1px solid #eee;
box-sizing: border-box;
">
{{ item.text }}
</div>
</template>
<template #loading="{ index }">
<div style="
height: 50px;
padding: 10px;
background-color: #f5f5f5;
box-sizing: border-box;
">
Loading item {{ index }}...
</div>
</template>
</LazyVirtualScroll>By default the components are pure views — you own the rows and pass them in,
and onLoad/@load tells you when to fetch more. That covers rendering, but
not reaching a row nobody has scrolled to, which is what search, export and
"select all" need.
Passing a data source inverts that. You supply one fetchRange function and
the source owns a row cache that fills the viewport as you scroll, optionally
walks the entire list in the background (off the critical path, rendering
nothing), and exposes the whole thing through an async API:
// React
const source = useLazyDataSource<User>({
totalItems: 1000000,
fetchRange: (startIndex, endIndex) => loadUsers(startIndex, endIndex),
useIndexedDb: true, // spill rows off the heap
background: { autoStart: true }, // index the whole list in the background
});
<LazyVirtualScroll totalItems={1000000} itemSize={50} source={source} render={...} />
// Anywhere else — search without ever holding the list in memory
for await (const { startIndex, rows } of source.scan()) {
/* ...filter each batch... */
}<!-- Vue -->
<LazyVirtualScroll :totalItems="1000000" :itemSize="50" :source="source" />Every method is async whichever backend is in use, so turning useIndexedDb on
or off never changes calling code. The feature is entirely opt-in: without a
source prop nothing about the components changes.
Full documentation: React · Vue
For complete documentation and examples, please refer to the README files for each package:
This repository includes demo applications for both React and Vue:
- React Demo:
apps/demo-react - Vue Demo:
apps/demo-vue
To run the demos:
# React demo
npx nx serve demo-react
# Vue demo
npx nx serve demo-vueThis workspace is powered by Nx.
# Build all packages
npx nx run-many -t build
# Build specific package
npx nx build @lazy-virtual-scroll/react
npx nx build @lazy-virtual-scroll/vue
npx nx build core# Test all packages
npx nx run-many -t test
# Test specific package
npx nx test @lazy-virtual-scroll/react
npx nx test @lazy-virtual-scroll/vue
npx nx test coreMIT
Learn more about code generators and inferred tasks in the docs.
To execute tasks with Nx use the following syntax:
npx nx <target> <project> <...options>
You can also run multiple targets:
npx nx run-many -t <target1> <target2>
..or add -p to filter specific projects
npx nx run-many -t <target1> <target2> -p <proj1> <proj2>
Targets can be defined in the package.json or projects.json. Learn more in the docs.
Nx comes with local caching already built-in (check your nx.json). On CI you might want to go a step further.
Run npx nx graph to show the graph of the workspace.
It will show tasks that you can run with Nx.