typescriptadvanced

Virtual Scroll List

Virtualized list that only renders visible items for large datasets with fixed row height.

typescript
import { useState, useRef, useCallback, CSSProperties, ReactNode } from 'react';

interface VirtualListProps<T> {
  items: T[];
  rowHeight: number;
  containerHeight: number;
  overscan?: number;
  renderItem: (item: T, index: number) => ReactNode;
}

export function VirtualList<T>({ items, rowHeight, containerHeight, overscan = 5, renderItem }: VirtualListProps<T>) {
  const [scrollTop, setScrollTop] = useState(0);
  const containerRef = useRef<HTMLDivElement>(null);

  const totalHeight = items.length * rowHeight;
  const startIndex = Math.max(0, Math.floor(scrollTop / rowHeight) - overscan);
  const endIndex = Math.min(items.length, Math.ceil((scrollTop + containerHeight) / rowHeight) + overscan);
  const visibleItems = items.slice(startIndex, endIndex);

  const handleScroll = useCallback(() => {
    if (containerRef.current) setScrollTop(containerRef.current.scrollTop);
  }, []);

  return (
    <div ref={containerRef} onScroll={handleScroll} style={{ height: containerHeight, overflow: 'auto' }}>
      <div style={{ height: totalHeight, position: 'relative' }}>
        {visibleItems.map((item, i) => {
          const index = startIndex + i;
          const style: CSSProperties = {
            position: 'absolute',
            top: index * rowHeight,
            height: rowHeight,
            width: '100%',
          };
          return <div key={index} style={style}>{renderItem(item, index)}</div>;
        })}
      </div>
    </div>
  );
}

Use Cases

  • Large data lists
  • Log viewers

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.