SyntaxStudy
Sign Up
React Virtualisation for Long Lists
React Beginner 1 min read

Virtualisation for Long Lists

Rendering thousands of DOM nodes at once is expensive regardless of how fast your JavaScript is. Virtualisation — also called windowing — solves this by only rendering the items currently visible in the viewport plus a small overscan buffer. As the user scrolls, items entering the viewport are created and items leaving it are destroyed or recycled. TanStack Virtual (formerly react-virtual) and react-window are the two most popular virtualisation libraries for React. Both work by calculating which items are visible based on the container scroll offset and providing you with the slice of data to render and the CSS transforms or absolute positions to apply to each item. Virtualisation is a targeted tool for large lists. It adds complexity — items must have known or estimated sizes, and animations and accessibility require extra work. Apply it specifically when profiling confirms that list rendering is the bottleneck, not as a default for every list.
Example
// Using @tanstack/react-virtual
import { useVirtualizer } from '@tanstack/react-virtual';
import { useRef } from 'react';

const ALL_ITEMS = Array.from({ length: 10_000 }, (_, i) => ({
    id:    i,
    label: `Item #${i + 1}`,
}));

function VirtualList() {
    const parentRef = useRef(null);

    const virtualizer = useVirtualizer({
        count:           ALL_ITEMS.length,
        getScrollElement: () => parentRef.current,
        estimateSize:    () => 40,   // estimated row height in px
        overscan:        5,          // extra items above/below viewport
    });

    return (
        <div
            ref={parentRef}
            style={{ height: 500, overflow: 'auto' }}
        >
            {/* Total scrollable area */}
            <div style={{ height: virtualizer.getTotalSize(), position: 'relative' }}>
                {virtualizer.getVirtualItems().map(virtualRow => (
                    <div
                        key={virtualRow.key}
                        style={{
                            position:  'absolute',
                            top:       0,
                            left:      0,
                            width:     '100%',
                            height:    virtualRow.size,
                            transform: `translateY(${virtualRow.start}px)`,
                        }}
                    >
                        {ALL_ITEMS[virtualRow.index].label}
                    </div>
                ))}
            </div>
        </div>
    );
}