Hooks for virtualizing scrollable elements in React with support for rows, columns, and grids
npx @tessl/cli install tessl/npm-react-virtual@3.13.0React Virtual is a React hooks library that provides efficient virtualization capabilities for scrollable elements, enabling developers to render large lists and grids with optimal performance. The library offers a single headless hook (useVirtual) that supports row, column, and grid virtualization with flexible sizing options including fixed, variable, and dynamic measurements.
npm install react-virtual or yarn add react-virtualimport { useVirtual } from "react-virtual";For CommonJS:
const { useVirtual } = require("react-virtual");import React from "react";
import { useVirtual } from "react-virtual";
function VirtualList() {
const parentRef = React.useRef();
const rowVirtualizer = useVirtual({
size: 10000,
parentRef: parentRef,
estimateSize: React.useCallback(() => 35, []),
});
return (
<div
ref={parentRef}
style={{
height: "200px",
width: "400px",
overflow: "auto",
}}
>
<div
style={{
height: `${rowVirtualizer.totalSize}px`,
width: "100%",
position: "relative",
}}
>
{rowVirtualizer.items.map((virtualRow) => (
<div
key={virtualRow.index}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`,
}}
>
Row {virtualRow.index}
</div>
))}
</div>
</div>
);
}React Virtual is built around a single, powerful hook that manages virtualization state:
The main virtualization hook that manages the rendering of large lists, grids, and columns with optimal performance.
/**
* Main virtualization hook for efficient rendering of large datasets
* @param options - Configuration object for virtualization behavior
* @returns Object containing virtual items and control functions
*/
function useVirtual(options: VirtualOptions): VirtualResult;
interface VirtualOptions {
/** Total number of items to virtualize (required) */
size: number;
/** Reference to the scrollable parent element (required) */
parentRef: React.RefObject<HTMLElement>;
/** Function to estimate size of each item (required, must be memoized with useCallback) */
estimateSize: (index: number) => number;
/** Number of items to render beyond visible area (default: 1) */
overscan?: number;
/** Enable horizontal virtualization using width/scrollLeft (default: false) */
horizontal?: boolean;
}
interface VirtualResult {
/** Array of currently visible virtual items */
items: VirtualItem[];
/** Total size of all virtualized content in pixels */
totalSize: number;
/** Function to scroll to a specific pixel offset */
scrollToOffset: (offset: number) => void;
/** Function to scroll to a specific item index */
scrollToIndex: (index: number) => void;
}
interface VirtualItem {
/** Zero-based index of the item */
index: number;
/** Starting position in pixels */
start: number;
/** Item size in pixels */
size: number;
/** Ending position in pixels */
end: number;
/** Ref callback for dynamic measurement */
measureRef: (element: HTMLElement | null) => void;
}Usage Patterns:
Row Virtualization (Vertical Lists):
const rowVirtualizer = useVirtual({
size: 10000,
parentRef: parentRef,
estimateSize: React.useCallback(() => 35, []),
overscan: 5,
});Column Virtualization (Horizontal Lists):
const columnVirtualizer = useVirtual({
horizontal: true,
size: 1000,
parentRef: parentRef,
estimateSize: React.useCallback(() => 100, []),
overscan: 5,
});Grid Virtualization (2D Virtualization):
// Use two separate hooks for rows and columns
const rowVirtualizer = useVirtual({
size: 10000,
parentRef: parentRef,
estimateSize: React.useCallback(() => 35, []),
overscan: 5,
});
const columnVirtualizer = useVirtual({
horizontal: true,
size: 10000,
parentRef: parentRef,
estimateSize: React.useCallback(() => 100, []),
overscan: 5,
});
// Render grid by mapping over both virtualizers
return (
<div ref={parentRef} style={{ height: "500px", width: "500px", overflow: "auto" }}>
<div
style={{
height: `${rowVirtualizer.totalSize}px`,
width: `${columnVirtualizer.totalSize}px`,
position: "relative",
}}
>
{rowVirtualizer.items.map((virtualRow) => (
<React.Fragment key={virtualRow.index}>
{columnVirtualizer.items.map((virtualColumn) => (
<div
key={virtualColumn.index}
style={{
position: "absolute",
top: 0,
left: 0,
width: `${virtualColumn.size}px`,
height: `${virtualRow.size}px`,
transform: `translateX(${virtualColumn.start}px) translateY(${virtualRow.start}px)`,
}}
>
Cell {virtualRow.index}, {virtualColumn.index}
</div>
))}
</React.Fragment>
))}
</div>
</div>
);Dynamic Measurement:
// For items with unknown sizes, use measureRef for runtime measurement
{rowVirtualizer.items.map((virtualRow) => (
<div
key={virtualRow.index}
ref={virtualRow.measureRef}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
transform: `translateY(${virtualRow.start}px)`,
}}
>
Content with dynamic height...
</div>
))}Imperative Scrolling:
// Scroll to specific item
const scrollToItem = (index) => {
rowVirtualizer.scrollToIndex(index);
};
// Scroll to specific offset
const scrollToTop = () => {
rowVirtualizer.scrollToOffset(0);
};The hook expects:
estimateSize to be memoized with React.useCallback() to prevent unnecessary re-calculationsparentRef to reference an element with overflow: auto or overflow: scrollsize to be a positive integer representing the total number of itemsIf these requirements aren't met, the virtualization may not work correctly or performance may be degraded.