CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-virtuoso

A virtual scroll React component for efficiently rendering large scrollable lists, grids, tables, and feeds

Pending
Overview
Eval results
Files

list-virtualization.mddocs/

List Virtualization

Core list virtualization component that efficiently renders large datasets by only displaying visible items plus a configurable buffer. Automatically handles variable-sized items without manual measurements.

Capabilities

Virtuoso Component

Main virtualization component for rendering lists with automatic size detection and scroll optimization.

/**
 * Main virtualization component for rendering lists with automatic size detection
 * @param props - Configuration options for the virtualized list
 * @returns JSX.Element representing the virtualized list
 */
function Virtuoso<D = any, C = any>(props: VirtuosoProps<D, C>): JSX.Element;

interface VirtuosoProps<D, C> extends ListRootProps {
  /** The data items to be rendered. If data is set, totalCount will be inferred from the length */
  data?: readonly D[];
  /** The total amount of items to be rendered */
  totalCount?: number;
  /** Set the callback to specify the contents of each item */
  itemContent?: ItemContent<D, C>;
  /** Use the components property for advanced customization of rendered elements */
  components?: Components<D, C>;
  /** Additional context available in custom components and content callbacks */
  context?: C;
  /** If specified, the component will use the function to generate the key property for each list item */
  computeItemKey?: ComputeItemKey<D, C>;
  
  /** Can be used to improve performance if the rendered items are of known size */
  fixedItemHeight?: number;
  /** By default, the component assumes the default item height from the first rendered item */
  defaultItemHeight?: number;
  
  /** If set to true, the list automatically scrolls to bottom if the total count is changed */
  followOutput?: FollowOutput;
  /** Gets called when the user scrolls to the end of the list */
  endReached?: (index: number) => void;
  /** Called when the user scrolls to the start of the list */
  startReached?: (index: number) => void;
  
  /** Called with the new set of items each time the list items are rendered due to scrolling */
  rangeChanged?: (range: ListRange) => void;
  /** Called with the new set of items each time the list items are rendered due to scrolling */
  itemsRendered?: (items: ListItem<D>[]) => void;
  
  /** Called with true / false when the list has reached the bottom / gets scrolled up */
  atBottomStateChange?: (atBottom: boolean) => void;
  /** Called with true / false when the list has reached the top / gets scrolled down */
  atTopStateChange?: (atTop: boolean) => void;
  
  /** Use when implementing inverse infinite scrolling */
  firstItemIndex?: number;
  /** Set to a value between 0 and totalCount - 1 to make the list start scrolled to that item */
  initialTopMostItemIndex?: IndexLocationWithAlign | number;
  /** Set this value to offset the initial location of the list */
  initialScrollTop?: number;
  
  /** Setting alignToBottom to true aligns the items to the bottom of the list if shorter than viewport */
  alignToBottom?: boolean;
  /** Uses the document scroller rather than wrapping the list in its own */
  useWindowScroll?: boolean;
  /** Pass a reference to a scrollable parent element */
  customScrollParent?: HTMLElement;
  
  /** Use to display placeholders if the user scrolls fast through the list */
  scrollSeekConfiguration?: false | ScrollSeekConfiguration;
  /** Set the overscan property to make the component chunk the rendering of new items on scroll */
  overscan?: number | { main: number; reverse: number };
  /** Set the increaseViewportBy property to artificially increase the viewport size */
  increaseViewportBy?: number | { top: number; bottom: number };
  
  /** Called when the list starts/stops scrolling */
  isScrolling?: (isScrolling: boolean) => void;
  /** Provides access to the root DOM element */
  scrollerRef?: (ref: HTMLElement | null | Window) => any;
  /** Pass a state obtained from getState() method to restore the list state */
  restoreStateFrom?: StateSnapshot;
  
  /** Set the amount of items to remain fixed at the top of the list */
  topItemCount?: number;
  /** Called when the total list height is changed due to new items or viewport resize */
  totalListHeightChanged?: (height: number) => void;
  /** Allows customizing the height/width calculation of Item elements */
  itemSize?: SizeFunction;
  /** Use for server-side rendering - if set, the list will render the specified amount of items */
  initialItemCount?: number;
  /** When set, turns the scroller into a horizontal list */
  horizontalDirection?: boolean;
  
  /** Implement this callback to adjust list position when total count changes */
  scrollIntoViewOnChange?: (params: {
    context: C;
    totalCount: number;
    scrollingInProgress: boolean;
  }) => ScrollIntoViewLocation | null | undefined | false | void;
  
  /** Set to customize the wrapper tag for header and footer components (default is 'div') */
  headerFooterTag?: string;
  
  /** Set to LogLevel.DEBUG to enable various diagnostics in the console */
  logLevel?: LogLevel;
  /** By default 4. Redefine to change how much away from the bottom the scroller can be */
  atBottomThreshold?: number;
  /** By default 0. Redefine to change how much away from the top the scroller can be */
  atTopThreshold?: number;
  /** When set, the resize observer will not use requestAnimationFrame to report size changes */
  skipAnimationFrameInResizeObserver?: boolean;
}

type ItemContent<D, C> = (index: number, data: D, context: C) => React.ReactNode;
type ComputeItemKey<D, C> = (index: number, item: D, context: C) => React.Key;
type ListRootProps = Omit<React.HTMLProps<HTMLDivElement>, 'data' | 'ref'>;

type ScrollIntoViewLocation = FlatScrollIntoViewLocation | GroupedScrollIntoViewLocation;

interface FlatScrollIntoViewLocation extends ScrollIntoViewLocationOptions {
  index: number;
}

interface GroupedScrollIntoViewLocation extends ScrollIntoViewLocationOptions {
  groupIndex: number;
}

interface ScrollIntoViewLocationOptions {
  align?: 'center' | 'end' | 'start';
  behavior?: 'auto' | 'smooth';
  calculateViewLocation?: CalculateViewLocation;
  done?: () => void;
}

Usage Examples:

import React from 'react';
import { Virtuoso } from 'react-virtuoso';

// Basic list with data array
function BasicList() {
  const items = Array.from({ length: 10000 }, (_, i) => ({
    id: i,
    name: `Item ${i}`,
    value: Math.random()
  }));

  return (
    <Virtuoso
      style={{ height: '400px' }}
      data={items}
      itemContent={(index, item) => (
        <div style={{ padding: '12px', borderBottom: '1px solid #eee' }}>
          <strong>{item.name}</strong>
          <div>Value: {item.value.toFixed(3)}</div>
        </div>
      )}
    />
  );
}

// List with totalCount (without data array)
function CountBasedList() {
  return (
    <Virtuoso
      style={{ height: '400px' }}
      totalCount={100000}
      itemContent={(index) => (
        <div style={{ padding: '12px' }}>
          Item {index}
        </div>
      )}
    />
  );
}

// List with infinite scrolling
function InfiniteList() {
  const [items, setItems] = React.useState(
    Array.from({ length: 100 }, (_, i) => `Item ${i}`)
  );

  const loadMore = () => {
    setItems(prev => [
      ...prev,
      ...Array.from({ length: 50 }, (_, i) => `Item ${prev.length + i}`)
    ]);
  };

  return (
    <Virtuoso
      style={{ height: '400px' }}
      data={items}
      endReached={loadMore}
      itemContent={(index, item) => (
        <div style={{ padding: '12px' }}>
          {item}
        </div>
      )}
    />
  );
}

// List with custom components
function CustomComponentsList() {
  const items = Array.from({ length: 1000 }, (_, i) => `Item ${i}`);

  return (
    <Virtuoso
      style={{ height: '400px' }}
      data={items}
      components={{
        Header: () => <div style={{ padding: '12px', fontWeight: 'bold' }}>Header</div>,
        Footer: () => <div style={{ padding: '12px', fontWeight: 'bold' }}>Footer</div>,
        EmptyPlaceholder: () => <div>No items to display</div>,
        Item: ({ children, ...props }) => (
          <div {...props} style={{ padding: '8px', margin: '4px', backgroundColor: '#f5f5f5' }}>
            {children}
          </div>
        )
      }}
      itemContent={(index, item) => item}
    />
  );
}

Follow Output

Automatically scroll to bottom when new items are added, perfect for chat interfaces and live feeds.

/**
 * Configure automatic scrolling behavior when new items are added
 */
type FollowOutput = FollowOutputCallback | FollowOutputScalarType;
type FollowOutputCallback = (isAtBottom: boolean) => FollowOutputScalarType;
type FollowOutputScalarType = 'auto' | 'smooth' | boolean;

Usage Example:

// Always follow output with smooth scrolling
<Virtuoso
  followOutput="smooth"
  data={messages}
  itemContent={(index, message) => <div>{message.text}</div>}
/>

// Conditional follow output
<Virtuoso
  followOutput={(isAtBottom) => {
    // Only auto-scroll if user is already at bottom
    return isAtBottom ? 'smooth' : false;
  }}
  data={messages}
  itemContent={(index, message) => <div>{message.text}</div>}
/>

Scroll Seek Configuration

Display placeholders during fast scrolling to improve performance.

interface ScrollSeekConfiguration {
  /** Callback to determine if the list should enter scroll seek mode */
  enter: ScrollSeekToggle;
  /** Callback to determine if the list should exit scroll seek mode */
  exit: ScrollSeekToggle;
  /** Called during scrolling in scroll seek mode - use to display a hint where the list is */
  change?: (velocity: number, range: ListRange) => void;
}

type ScrollSeekToggle = (velocity: number, range: ListRange) => boolean;

interface ScrollSeekPlaceholderProps {
  height: number;
  index: number;
  type: 'group' | 'item';
  groupIndex?: number;
}

Usage Example:

<Virtuoso
  scrollSeekConfiguration={{
    enter: (velocity) => Math.abs(velocity) > 200,
    exit: (velocity) => Math.abs(velocity) < 30,
  }}
  components={{
    ScrollSeekPlaceholder: ({ height, index }) => (
      <div style={{ height, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        Loading item {index}...
      </div>
    )
  }}
  data={items}
  itemContent={(index, item) => <div>{item}</div>}
/>

Performance Optimization

/** Calculates the height of el, which will be the Item element in the DOM */
type SizeFunction = (el: HTMLElement, field: 'offsetHeight' | 'offsetWidth') => number;

interface StateSnapshot {
  ranges: SizeRange[];
  scrollTop: number;
}

interface SizeRange {
  startIndex: number;
  endIndex: number;
  size: number;
}

type StateCallback = (state: StateSnapshot) => void;

Usage Example:

function OptimizedList() {
  const [state, setState] = React.useState<StateSnapshot | null>(null);
  
  return (
    <Virtuoso
      // Fixed height for better performance
      fixedItemHeight={50}
      // Reduce overscan for memory efficiency
      overscan={{ main: 5, reverse: 5 }}
      // Restore previous state
      restoreStateFrom={state}
      // Custom size calculation
      itemSize={(el) => el.getBoundingClientRect().height}
      data={items}
      itemContent={(index, item) => <div>{item}</div>}
    />
  );
}

Types

interface ListItem<D> {
  data?: D;
  index: number;
  offset: number;
  size: number;
}

interface ListRange {
  startIndex: number;
  endIndex: number;
}

interface Item<D> {
  data?: D;
  index: number;
  offset: number;
  size: number;
}

enum LogLevel {
  DEBUG,
  INFO,
  WARN,
  ERROR
}

interface LocationOptions {
  align?: 'center' | 'end' | 'start';
  behavior?: 'auto' | 'smooth';
  offset?: number;
}

interface FlatIndexLocationWithAlign extends LocationOptions {
  index: 'LAST' | number;
}

type IndexLocationWithAlign = FlatIndexLocationWithAlign;

Install with Tessl CLI

npx tessl i tessl/npm-react-virtuoso

docs

component-handles.md

grid-virtualization.md

grouped-lists.md

index.md

list-virtualization.md

table-virtualization.md

tile.json