The most surprising thing about virtualizing large lists in React is that it doesn’t actually render most of your items.
Here’s TanStack Virtual (formerly React Virtual) in action, rendering a list of 100,000 items without breaking a sweat.
import { useVirtual } from '@tanstack/react-virtual';
import React, { useRef } from 'react';
function VirtualList({ items }) {
const parentRef = useRef();
const rowVirtualizer = useVirtual({
size: items.length,
parentRef,
estimateSize: () => 35, // Estimate the height of each row
});
return (
<div
ref={parentRef}
style={{ height: '500px', overflow: 'auto' }} // The scrollable container
>
<div
style={{
height: `${rowVirtualizer.totalSize}px`, // Total height of all items
width: '100%',
position: 'relative',
}}
>
{rowVirtualizer.virtualItems.map((virtualItem) => (
<div
key={virtualItem.key}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualItem.size}px`,
transform: `translateY(${virtualItem.start}px)`,
}}
>
{items[virtualItem.index]} {/* Render only the visible items */}
</div>
))}
</div>
</div>
);
}
// Example Usage:
const largeArray = Array.from({ length: 100000 }, (_, i) => `Item ${i + 1}`);
function App() {
return <VirtualList items={largeArray} />;
}
export default App;
This code sets up a scrollable container (parentRef) and tells useVirtual how many items there are (size) and how tall each item is expected to be (estimateSize). useVirtual then calculates which items should be visible based on the current scroll position and provides virtualItems. These are the only items actually rendered to the DOM. The outer div is given a totalSize height to ensure the browser’s scrollbar accurately reflects the full extent of the data, and the inner divs are absolutely positioned and translated to appear in the correct place.
The core problem TanStack Virtual solves is the performance degradation that occurs when a React component tries to render thousands or millions of DOM elements simultaneously. Browsers have a limit to how many elements they can efficiently manage. When you have a massive list, even if most of the items are off-screen, the sheer number of DOM nodes can cause:
- Slow initial render: The browser has to parse and create a DOM node for every single item.
- Laggy scrolling: Every scroll event might trigger a re-render or re-calculation for a large number of items, even if only a few change visibility.
- High memory consumption: Each DOM node consumes memory.
TanStack Virtual tackles this by implementing a technique called "windowing" or "virtualization." Instead of rendering everything, it only renders the items that are currently within or very near the viewport (the visible area of the scrollable container). As you scroll, it dynamically adds newly visible items to the DOM and removes items that have scrolled out of view. This dramatically reduces the number of DOM nodes at any given time, keeping performance snappy.
The key parameters you control are:
size: The total number of items in your dataset.parentRef: A React ref attached to the scrollable container element. This is how the library knows where to measure scroll position and container dimensions.estimateSize: A function that returns an estimated size (height for vertical, width for horizontal) of a single item. The library uses this to calculate how many items could fit in the viewport and to set thetotalSizeof the scrollable area. Accuracy here improves performance, but it doesn’t have to be perfect.overscan: (Optional) The number of items to render above and below the visible viewport. This helps prevent blank areas from appearing as you scroll rapidly, ensuring a smoother user experience. A typical value might be 5 or 10.
When you provide an estimateSize function that returns a fixed number, TanStack Virtual can be incredibly efficient because it knows exactly how much space each item takes up. However, if your list items have variable heights (e.g., text that wraps differently), you’ll want to use the measureElement option. This involves a callback that TanStack Virtual uses to measure the actual rendered size of an element after it’s been added to the DOM. It then caches this size and uses it for future calculations, allowing for perfect accuracy with variable-sized items, albeit with a slight performance trade-off on the first render of any given item.
The virtualItems array returned by useVirtual contains objects like { key, index, size, start }. The index is the original index in your items array, size is the calculated height/width of that specific item, and start is the offset from the top/left of the scrollable container where that item should be positioned. The key is crucial for React’s reconciliation.
A common misconception is that you need to manually manage scroll events or window resizing. TanStack Virtual handles all of that for you by hooking into the parentRef. The library also intelligently handles debouncing and throttling of scroll events to prevent excessive recalculations.
The next step after mastering basic virtualization is often handling dynamic data loading or infinite scrolling within a virtualized list.