← Back to Exercises

Virtualized List

List with 100,000 items

Rendering 100,000 items without virtualization would crash the browser. This list only renders what you see.

1Item 1
2Item 2
3Item 3
4Item 4
5Item 5
6Item 6
7Item 7
8Item 8
9Item 9
10Item 10
11Item 11
12Item 12
13Item 13
14Item 14
"use client";
import { useState } from 'react';
import styles from './VirtualizedList.module.css';

const VirtualizedList = ({ items, itemHeight, containerHeight, renderItem }) => {
    const [scrollTop, setScrollTop] = useState(0);
    const totalHeight = items.length * itemHeight;

    // Calculate which items should be visible
    const startIndex = Math.floor(scrollTop / itemHeight);
    // Add a buffer of items to render to prevent flickering
    const buffer = 5;
    const visibleItemCount = Math.ceil(containerHeight / itemHeight);

    const startNode = Math.max(0, startIndex - buffer);
    const endNode = Math.min(
        items.length - 1,
        startIndex + visibleItemCount + buffer
    );

    const visibleItems = [];
    for (let i = startNode; i <= endNode; i++) {
        visibleItems.push({
            index: i,
            item: items[i],
            style: {
                position: 'absolute',
                top: `${i * itemHeight}px`,
                height: `${itemHeight}px`,
                width: '100%',
            },
        });
    }

    const onScroll = (e) => {
        setScrollTop(e.currentTarget.scrollTop);
    };

    return (
        <div
            className={styles.container}
            style={{ height: `${containerHeight}px` }}
            onScroll={onScroll}
        >
            <div className={styles.content} style={{ height: `${totalHeight}px` }}>
                {visibleItems.map(({ index, item, style }) => (
                    <div key={index} style={style}>
                        {renderItem(item, index)}
                    </div>
                ))}
            </div>
        </div>
    );
};

export default function VirtualizedListDemo() {
    // Generate a large list of items
    const items = Array.from({ length: 100000 }, (_, i) => `Item ${i + 1}`);

    return (
        <div className={styles.demoWrapper}>
            <div style={{ marginBottom: '1rem' }}>
                <h3>List with 100,000 items</h3>
                <p style={{ color: 'var(--text-secondary)' }}>
                    Rendering 100,000 items without virtualization would crash the browser.
                    This list only renders what you see.
                </p>
            </div>

            <VirtualizedList
                items={items}
                itemHeight={50}
                containerHeight={400}
                renderItem={(item, index) => (
                    <div className={styles.item}>
                        <span style={{
                            display: 'inline-flex',
                            alignItems: 'center',
                            justifyContent: 'center',
                            width: '30px',
                            height: '30px',
                            borderRadius: '50%',
                            background: 'rgba(79, 70, 229, 0.1)',
                            color: 'var(--primary)',
                            marginRight: '1rem',
                            fontSize: '0.75rem',
                            fontWeight: 'bold'
                        }}>
                            {index + 1}
                        </span>
                        {item}
                    </div>
                )}
            />
        </div>
    );
}