CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-tanstack--react-virtual

Headless UI for virtualizing scrollable elements in React

Pending
Overview
Eval results
Files

react-hooks.mddocs/

React Virtualization Hooks

React-specific hooks that provide virtualized scrolling for elements and windows with automatic state management and optimal re-rendering performance.

Capabilities

useVirtualizer Hook

Creates a virtualizer instance for element-based scrolling with automatic React integration and state management.

/**
 * React hook for element-based virtualization
 * @param options - Virtualization configuration with element-specific defaults
 * @returns Virtualizer instance with React integration
 */
export function useVirtualizer<TScrollElement extends Element, TItemElement extends Element>(
  options: PartialKeys<
    VirtualizerOptions<TScrollElement, TItemElement>,
    'observeElementRect' | 'observeElementOffset' | 'scrollToFn'
  >
): Virtualizer<TScrollElement, TItemElement>;

The hook automatically provides default implementations for:

  • observeElementRect: Uses ResizeObserver for element size changes
  • observeElementOffset: Monitors scroll position changes
  • scrollToFn: Handles scrolling with element.scrollTo()

Usage Examples:

import React from 'react';
import { useVirtualizer } from '@tanstack/react-virtual';

// Basic list virtualization
function BasicList({ items }: { items: string[] }) {
  const parentRef = React.useRef<HTMLDivElement>(null);
  
  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 35,
  });

  return (
    <div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
      <div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
        {virtualizer.getVirtualItems().map((virtualItem) => (
          <div
            key={virtualItem.key}
            data-index={virtualItem.index}
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              width: '100%',
              height: `${virtualItem.size}px`,
              transform: `translateY(${virtualItem.start}px)`,
            }}
          >
            {items[virtualItem.index]}
          </div>
        ))}
      </div>
    </div>
  );
}

// Horizontal scrolling
function HorizontalList({ items }: { items: any[] }) {
  const parentRef = React.useRef<HTMLDivElement>(null);
  
  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 120,
    horizontal: true,
  });

  return (
    <div ref={parentRef} style={{ width: '400px', overflowX: 'auto' }}>
      <div style={{ width: `${virtualizer.getTotalSize()}px`, height: '100px', position: 'relative' }}>
        {virtualizer.getVirtualItems().map((virtualItem) => (
          <div
            key={virtualItem.key}
            data-index={virtualItem.index}
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              height: '100%',
              width: `${virtualItem.size}px`,
              transform: `translateX(${virtualItem.start}px)`,
            }}
          >
            Item {virtualItem.index}
          </div>
        ))}
      </div>
    </div>
  );
}

// Dynamic sizing with measurement
function DynamicList({ items }: { items: { content: string; height?: number }[] }) {
  const parentRef = React.useRef<HTMLDivElement>(null);
  
  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: (index) => items[index]?.height ?? 50,
    overscan: 3,
  });

  return (
    <div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
      <div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
        {virtualizer.getVirtualItems().map((virtualItem) => (
          <div
            key={virtualItem.key}
            data-index={virtualItem.index}
            ref={(node) => virtualizer.measureElement(node)}
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              width: '100%',
              transform: `translateY(${virtualItem.start}px)`,
            }}
          >
            <div style={{ padding: '8px' }}>
              {items[virtualItem.index]?.content}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

useWindowVirtualizer Hook

Creates a virtualizer instance for window-based scrolling, ideal for full-page virtualization scenarios.

/**
 * React hook for window-based virtualization
 * @param options - Virtualization configuration with window-specific defaults
 * @returns Virtualizer instance configured for window scrolling
 */
export function useWindowVirtualizer<TItemElement extends Element>(
  options: PartialKeys<
    VirtualizerOptions<Window, TItemElement>,
    | 'getScrollElement'
    | 'observeElementRect'  
    | 'observeElementOffset'
    | 'scrollToFn'
  >
): Virtualizer<Window, TItemElement>;

The hook automatically provides default implementations for:

  • getScrollElement: Returns the window object
  • observeElementRect: Uses window resize events
  • observeElementOffset: Monitors window scroll position
  • scrollToFn: Handles scrolling with window.scrollTo()
  • initialOffset: Uses current window.scrollY position

Usage Examples:

import React from 'react';
import { useWindowVirtualizer } from '@tanstack/react-virtual';

// Full-page virtualization
function WindowVirtualizedList({ items }: { items: string[] }) {
  const virtualizer = useWindowVirtualizer({
    count: items.length,
    estimateSize: () => 100,
    overscan: 5,
  });

  return (
    <div style={{ paddingTop: '200px' }}>
      <div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
        {virtualizer.getVirtualItems().map((virtualItem) => (
          <div
            key={virtualItem.key}
            data-index={virtualItem.index}
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              width: '100%',
              height: `${virtualItem.size}px`,
              transform: `translateY(${virtualItem.start}px)`,
            }}
          >
            <div style={{ padding: '16px', border: '1px solid #ccc' }}>
              {items[virtualItem.index]}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

// Infinite scroll with window virtualization  
function InfiniteScrollList() {
  const [items, setItems] = React.useState<string[]>(
    Array.from({ length: 100 }, (_, i) => `Item ${i}`)
  );
  const [isLoading, setIsLoading] = React.useState(false);

  const virtualizer = useWindowVirtualizer({
    count: items.length,
    estimateSize: () => 80,
    overscan: 10,
  });

  const virtualItems = virtualizer.getVirtualItems();
  const lastItem = virtualItems[virtualItems.length - 1];

  React.useEffect(() => {
    if (!lastItem || isLoading) return;
    
    if (lastItem.index >= items.length - 1) {
      setIsLoading(true);
      // Simulate loading more items
      setTimeout(() => {
        setItems(prev => [
          ...prev,
          ...Array.from({ length: 50 }, (_, i) => `Item ${prev.length + i}`)
        ]);
        setIsLoading(false);
      }, 1000);
    }
  }, [lastItem?.index, items.length, isLoading]);

  return (
    <div>
      <div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
        {virtualItems.map((virtualItem) => (
          <div
            key={virtualItem.key}
            data-index={virtualItem.index}
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              width: '100%',
              height: `${virtualItem.size}px`,
              transform: `translateY(${virtualItem.start}px)`,
              padding: '8px',
              borderBottom: '1px solid #eee',
            }}
          >
            {items[virtualItem.index]}
          </div>
        ))}
      </div>
      {isLoading && <div style={{ padding: '20px', textAlign: 'center' }}>Loading...</div>}
    </div>
  );
}

Configuration Options

Both hooks accept the same base VirtualizerOptions with certain defaults provided:

Required Options

export interface RequiredVirtualizerOptions {
  /** Total number of items to virtualize */
  count: number;
  /** Function to estimate the size of each item */
  estimateSize: (index: number) => number;
}

Common Optional Options

export interface CommonVirtualizerOptions {
  /** Enable horizontal scrolling instead of vertical */
  horizontal?: boolean;
  /** Number of items to render outside the visible area */
  overscan?: number;
  /** Padding at the start of the scroll area */
  paddingStart?: number;
  /** Padding at the end of the scroll area */
  paddingEnd?: number;
  /** Number of lanes for grid layout */
  lanes?: number;
  /** Gap between items in pixels */
  gap?: number;
  /** Enable debug logging */
  debug?: boolean;
  /** Enable right-to-left layout */
  isRtl?: boolean;
  /** Custom function to extract keys from indices */
  getItemKey?: (index: number) => Key;
  /** Custom function to extract visible range */
  rangeExtractor?: (range: Range) => Array<number>;
  /** Custom element measurement function */
  measureElement?: (element: TItemElement, entry: ResizeObserverEntry | undefined, instance: Virtualizer<TScrollElement, TItemElement>) => number;
}

Performance Considerations

Optimal Re-rendering

Both hooks use React's state management to trigger re-renders only when necessary:

  • Automatic batching of scroll updates using flushSync for smooth scrolling
  • Memoized calculations to prevent unnecessary recalculations
  • Efficient range calculation to minimize DOM operations

Memory Management

  • Virtual items are created and destroyed as needed
  • Element references are automatically cleaned up
  • ResizeObserver connections are managed automatically

Best Practices

  1. Use stable estimateSize functions: Avoid creating new functions on every render
  2. Implement proper key extraction: Use getItemKey for items with stable identifiers
  3. Optimize item rendering: Use React.memo for item components when appropriate
  4. Handle dynamic content: Use measureElement ref callback for accurate sizing of dynamic content

Install with Tessl CLI

npx tessl i tessl/npm-tanstack--react-virtual

docs

index.md

react-hooks.md

types-utilities.md

virtualizer-engine.md

tile.json