Getting started
Receive active-virtual and render a first virtual list with useVirtualAxis.
@workspace/active-virtual 2.1.0Status unknowncontent differs from the recorded release 2.1.0baseline ce081dc3
Receive the package#
active-virtualis an owner: request it on its own with--owners active-virtual, or it comes along withactive-gridoractive-geo. Ask the maintainer of Foundation for a bundle, then follow Receiving packages:Terminalnode tools/rps/rps-apply.mjs plan --bundle <bundle-folder> --developmentnode tools/rps/rps-apply.mjs apply --bundle <bundle-folder> --expect-plan <digest> --developmentDeclare, install and verify#
JSON{ "dependencies": { "@workspace/active-virtual": "workspace:*", "react": "^19.0.0", "react-dom": "^19.0.0" }}Terminalpnpm installnode tools/rps/rps-apply.mjs verify-install --from <app-folder>Render a list#
TSX'use client'; import {useState} from 'react';import {useVirtualAxis} from '@workspace/active-virtual/react'; export function VirtualList({rows}: {rows: string[]}) { const [container, setContainer] = useState<HTMLDivElement | null>(null); const {projection, itemRef} = useVirtualAxis({ count: rows.length, estimate: 32, container, }); return ( <div ref={setContainer} style={{height: 400, overflow: 'auto'}}> <div style={{height: projection.extent, position: 'relative'}}> {projection.items.map((item) => ( <div key={item.identity} ref={itemRef(item.identity)} style={{ position: 'absolute', top: 0, left: 0, width: '100%', transform: `translateY(${item.offset}px)`, }} > {rows[item.position]} </div> ))} </div> </div> );}- Read
projectionduring render; it is current for this render's inputs. - Keep an estimate function stable (module level or
useCallback): a new function is a new input. - The container is read after commit;
nullleaves the axis unattached.
Ten thousand rows of varying heightClient
0 of 10,000 rows in the DOM
Full source examples/virtual/long-list.tsx// apps/docs/examples/virtual/long-list.tsx'use client'; import {useState} from 'react'; import {Button} from '@workspace/active-ui/components/primitives/button';import {Input} from '@workspace/active-ui/components/primitives/input';import {Label} from '@workspace/active-ui/components/primitives/label';import {useVirtualAxis} from '@workspace/active-virtual/react'; const COUNT = 10_000; /** Every seventh row wraps to a second line: the axis measures real heights. */function rowText(position: number): string { const base = `Row ${(position + 1).toLocaleString('en')}`; return position % 7 === 3 ? `${base}: a longer note that wraps to a second line in a narrow column, so this row is taller than its estimate.` : base;} /** 10,000 rows of varying height; only the rows in view (plus overscan) are in the DOM. */export default function LongList() { const [container, setContainer] = useState<HTMLDivElement | null>(null); const [target, setTarget] = useState('5000'); const {projection, controller, itemRef} = useVirtualAxis({ count: COUNT, estimate: 32, container, }); return ( <div className="flex flex-col gap-3"> <form className="flex flex-wrap items-end gap-2" onSubmit={(event) => { event.preventDefault(); const row = Number(target); if (Number.isInteger(row) && row >= 1 && row <= COUNT) controller.scrollToPosition(row - 1, {align: 'center'}); }} > <div className="flex flex-col gap-1.5"> <Label htmlFor="virtual-target">Row</Label> <Input id="virtual-target" className="w-28" inputMode="numeric" value={target} onChange={(event) => setTarget(event.target.value)} /> </div> <Button type="submit" variant="outline"> Go to row </Button> </form> <div ref={setContainer} className="bg-background h-72 overflow-auto rounded-md border" tabIndex={0} aria-label="Ten thousand rows" data-virtual-container > <div style={{height: projection.extent, position: 'relative'}}> {projection.items.map((item) => ( <div key={item.identity} ref={itemRef(item.identity)} className="border-b px-3 py-1.5 text-sm" style={{ position: 'absolute', top: 0, left: 0, width: '100%', transform: `translateY(${item.offset}px)`, }} data-position={item.position} > {rowText(item.position)} </div> ))} </div> </div> <p className="text-muted-foreground text-sm" aria-live="polite"> {projection.items.length} of {COUNT.toLocaleString('en')} rows in the DOM </p> </div> );}- Read