List Virtualization (Windowing)
When dealing with large datasets—such as tables with thousands of rows, infinite-scrolling feeds, or massive dropdowns—rendering every single DOM node is a massive performance bottleneck. The browser struggles to calculate layouts, paint the screen, and manage memory for thousands of elements.
List Virtualization (also known as windowing) solves this by only rendering the elements that are currently visible in the user's viewport, plus a small buffer (overscan) above and below.
How It Works
Imagine an array of 10,000 items. If the container can only fit 10 items on screen at once:
- Virtualization calculates the total scroll height (e.g., 10,000 items × 50px = 500,000px).
- A single massive empty
divis rendered to create the scrollbar. - Only the ~10 visible items (plus a few extra for smooth scrolling) are rendered into the DOM, positioned absolutely at their correct offset.
- As the user scrolls, the items are dynamically swapped out, keeping the DOM node count strictly capped.
Libraries
1. react-window (The Lightweight Classic)
Created by Brian Vaughn (React Core Team), react-window is a minimalistic, high-performance library for rendering flat lists and grids.
import { FixedSizeList as List } from 'react-window';
const Row = ({ index, style }) => (
<div style={style} className="border-b px-4 py-2">
Row {index}
</div>
);
function VirtualizedList() {
return (
<List
height={400} // Viewport height
itemCount={10000} // Total items
itemSize={50} // Height of each item
width={'100%'} // Viewport width
>
{Row}
</List>
);
}
2. @tanstack/react-virtual (The Modern Headless Approach)
From Tanner Linsley (creator of React Query), TanStack Virtual is a "headless" utility. It doesn't give you components; it gives you a hook. This provides maximum flexibility for integrating with existing UI components, tables, and grids.
import { useVirtualizer } from '@tanstack/react-virtual';
import { useRef } from 'react';
function HeadlessVirtualList() {
const parentRef = useRef<HTMLDivElement>(null);
const rowVirtualizer = useVirtualizer({
count: 10000,
getScrollElement: () => parentRef.current,
estimateSize: () => 50, // Can be dynamic!
});
return (
<div
ref={parentRef}
style={{ height: '400px', overflow: 'auto' }}
>
<div
style={{
height: `${rowVirtualizer.getTotalSize()}px`,
width: '100%',
position: 'relative',
}}
>
{rowVirtualizer.getVirtualItems().map((virtualItem) => (
<div
key={virtualItem.key}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualItem.size}px`,
transform: `translateY(${virtualItem.start}px)`,
}}
>
Row {virtualItem.index}
</div>
))}
</div>
</div>
);
}
Dynamic Sizing
One of the hardest problems in virtualization is when items have unknown or varying heights (e.g., text blocks of different lengths).
react-windowrequiresVariableSizeListand manual height recalculation.@tanstack/react-virtualexcels here by offering dynamic measurement out-of-the-box throughestimateSizeandmeasureElement, updating the cache as elements render.
Summary
Virtualization is essential for data-heavy applications. If you find your React app lagging when rendering large lists, don't try to optimize the React components themselves—implement windowing first. The difference in performance is day and night.