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

component-handles.mddocs/

Component Handles

All React Virtuoso components provide handle interfaces for programmatic control including scrolling operations, state management, and viewport queries. These handles are accessed through React refs and provide methods for external control of virtualization behavior.

Capabilities

VirtuosoHandle

Handle interface for the main Virtuoso list component providing comprehensive scroll control and state management.

interface VirtuosoHandle {
  /** Use this with combination with follow output if you have images loading in the list */
  autoscrollToBottom(): void;
  /** Obtains the internal size state of the component, so that it can be restored later */
  getState(stateCb: StateCallback): void;
  /** Scrolls the component with the specified amount */
  scrollBy(location: ScrollToOptions): void;
  /** Scrolls the item into view if necessary */
  scrollIntoView(location: FlatScrollIntoViewLocation): void;
  /** Scrolls the component to the specified location */
  scrollTo(location: ScrollToOptions): void;
  /** Scrolls the component to the specified item index */
  scrollToIndex(location: FlatIndexLocationWithAlign | number): void;
}

interface FlatScrollIntoViewLocation extends ScrollIntoViewLocationOptions {
  index: number;
}

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

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

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

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

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

Usage Examples:

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

function ControlledList() {
  const virtuosoRef = React.useRef<VirtuosoHandle>(null);
  const [items] = React.useState(
    Array.from({ length: 10000 }, (_, i) => `Item ${i}`)
  );

  // Scroll to specific index
  const scrollToIndex = (index: number) => {
    virtuosoRef.current?.scrollToIndex(index);
  };

  // Smooth scroll to index with alignment
  const smoothScrollToIndex = (index: number) => {
    virtuosoRef.current?.scrollToIndex({
      index,
      align: 'center',
      behavior: 'smooth'
    });
  };

  // Scroll to last item
  const scrollToEnd = () => {
    virtuosoRef.current?.scrollToIndex('LAST');
  };

  // Smart scroll into view
  const scrollIntoView = (index: number) => {
    virtuosoRef.current?.scrollIntoView({
      index,
      behavior: 'smooth',
      done: () => console.log('Scroll completed')
    });
  };

  // Force scroll to bottom (useful for chat)
  const forceScrollToBottom = () => {
    virtuosoRef.current?.autoscrollToBottom();
  };

  // Scroll by amount
  const scrollByAmount = (pixels: number) => {
    virtuosoRef.current?.scrollBy({ top: pixels, behavior: 'smooth' });
  };

  // Scroll to absolute position
  const scrollToPosition = (scrollTop: number) => {
    virtuosoRef.current?.scrollTo({ top: scrollTop });
  };

  // Get and restore state
  const [savedState, setSavedState] = React.useState<StateSnapshot | null>(null);
  
  const saveState = () => {
    virtuosoRef.current?.getState(setSavedState);
  };

  return (
    <div>
      <div style={{ marginBottom: '10px' }}>
        <button onClick={() => scrollToIndex(0)}>Go to Start</button>
        <button onClick={() => smoothScrollToIndex(500)}>Go to Middle</button>
        <button onClick={scrollToEnd}>Go to End</button>
        <button onClick={() => scrollIntoView(1000)}>Smart Scroll to 1000</button>
        <button onClick={forceScrollToBottom}>Force Bottom</button>
        <button onClick={() => scrollByAmount(200)}>Scroll Down 200px</button>
        <button onClick={() => scrollToPosition(0)}>Reset Position</button>
        <button onClick={saveState}>Save State</button>
      </div>
      
      <Virtuoso
        ref={virtuosoRef}
        style={{ height: '400px' }}
        data={items}
        restoreStateFrom={savedState}
        itemContent={(index, item) => (
          <div style={{ padding: '12px', borderBottom: '1px solid #eee' }}>
            {item} (Index: {index})
          </div>
        )}
      />
    </div>
  );
}

GroupedVirtuosoHandle

Handle interface for the GroupedVirtuoso component with additional group-aware scrolling capabilities.

interface GroupedVirtuosoHandle {
  /** Use this with combination with follow output if you have images loading in the list */
  autoscrollToBottom(): void;
  /** Obtains the internal size state of the component for restoration */
  getState(stateCb: StateCallback): void;
  /** Scrolls the component with the specified amount */
  scrollBy(location: ScrollToOptions): void;
  /** Scrolls the item into view if necessary with group context */
  scrollIntoView(location: number | ScrollIntoViewLocation): void;
  /** Scrolls the component to the specified location */
  scrollTo(location: ScrollToOptions): void;
  /** Scrolls the component to the specified item or group index */
  scrollToIndex(location: IndexLocationWithAlign | number): void;
}

type IndexLocationWithAlign = FlatIndexLocationWithAlign | GroupIndexLocationWithAlign;

interface GroupIndexLocationWithAlign extends LocationOptions {
  groupIndex: number;
}

type ScrollIntoViewLocation = FlatScrollIntoViewLocation | GroupedScrollIntoViewLocation;

interface GroupedScrollIntoViewLocation extends ScrollIntoViewLocationOptions {
  groupIndex: number;
}

Usage Example:

import React from 'react';
import { GroupedVirtuoso, GroupedVirtuosoHandle } from 'react-virtuoso';

function ControlledGroupedList() {
  const groupedRef = React.useRef<GroupedVirtuosoHandle>(null);
  
  const groups = [
    { name: 'Group A', items: Array.from({ length: 50 }, (_, i) => `A-${i}`) },
    { name: 'Group B', items: Array.from({ length: 30 }, (_, i) => `B-${i}`) },
    { name: 'Group C', items: Array.from({ length: 40 }, (_, i) => `C-${i}`) }
  ];

  const groupCounts = groups.map(g => g.items.length);
  const allItems = groups.flatMap(g => g.items);

  // Scroll to specific group
  const scrollToGroup = (groupIndex: number) => {
    groupedRef.current?.scrollToIndex({
      groupIndex,
      align: 'start',
      behavior: 'smooth'
    });
  };

  // Scroll to specific item by flat index
  const scrollToItem = (itemIndex: number) => {
    groupedRef.current?.scrollToIndex({
      index: itemIndex,
      align: 'center'
    });
  };

  // Scroll group into view
  const scrollGroupIntoView = (groupIndex: number) => {
    groupedRef.current?.scrollIntoView({
      groupIndex,
      behavior: 'smooth'
    });
  };

  return (
    <div>
      <div style={{ marginBottom: '10px' }}>
        {groups.map((group, index) => (
          <button key={index} onClick={() => scrollToGroup(index)}>
            Go to {group.name}
          </button>
        ))}
        <button onClick={() => scrollToItem(50)}>Go to Item 50</button>
      </div>
      
      <GroupedVirtuoso
        ref={groupedRef}
        style={{ height: '400px' }}
        groupCounts={groupCounts}
        groupContent={(index) => (
          <div style={{ 
            padding: '12px', 
            backgroundColor: '#f0f0f0', 
            fontWeight: 'bold' 
          }}>
            {groups[index].name}
          </div>
        )}
        itemContent={(index, groupIndex, item) => (
          <div style={{ padding: '8px 16px', borderBottom: '1px solid #eee' }}>
            {item} (Group: {groupIndex})
          </div>
        )}
        data={allItems}
      />
    </div>
  );
}

VirtuosoGridHandle

Handle interface for the VirtuosoGrid component providing grid-specific scrolling capabilities.

interface VirtuosoGridHandle {
  /** Scrolls the grid with the specified amount */
  scrollBy(location: ScrollToOptions): void;
  /** Scrolls the grid to the specified location */
  scrollTo(location: ScrollToOptions): void;
  /** Scrolls the grid to the specified item index */
  scrollToIndex(location: GridIndexLocation): void;
}

type GridIndexLocation = FlatIndexLocationWithAlign | number;

Usage Example:

import React from 'react';
import { VirtuosoGrid, VirtuosoGridHandle } from 'react-virtuoso';

function ControlledGrid() {
  const gridRef = React.useRef<VirtuosoGridHandle>(null);
  const items = Array.from({ length: 1000 }, (_, i) => `Item ${i}`);

  const scrollToItem = (index: number) => {
    gridRef.current?.scrollToIndex({
      index,
      align: 'start',
      behavior: 'smooth'
    });
  };

  const jumpToTop = () => {
    gridRef.current?.scrollTo({ top: 0, behavior: 'smooth' });
  };

  const scrollDown = () => {
    gridRef.current?.scrollBy({ top: 300, behavior: 'smooth' });
  };

  return (
    <div>
      <div style={{ marginBottom: '10px' }}>
        <button onClick={jumpToTop}>Top</button>
        <button onClick={() => scrollToItem(100)}>Item 100</button>
        <button onClick={() => scrollToItem(500)}>Item 500</button>
        <button onClick={scrollDown}>Scroll Down</button>
      </div>
      
      <VirtuosoGrid
        ref={gridRef}
        style={{ height: '400px' }}
        data={items}
        itemContent={(index, item) => (
          <div style={{
            padding: '16px',
            border: '1px solid #ddd',
            margin: '4px',
            textAlign: 'center'
          }}>
            {item}
          </div>
        )}
      />
    </div>
  );
}

TableVirtuosoHandle

Handle interface for table virtualization components.

interface TableVirtuosoHandle {
  /** Obtains the internal size state of the component for restoration */
  getState(stateCb: StateCallback): void;
  /** Scrolls the table with the specified amount */
  scrollBy(location: ScrollToOptions): void;
  /** Scrolls the table to the specified location */
  scrollTo(location: ScrollToOptions): void;
  /** Scrolls the row into view if necessary */
  scrollIntoView(location: FlatScrollIntoViewLocation | number): void;
  /** Scrolls the table to the specified row index */
  scrollToIndex(location: FlatIndexLocationWithAlign | number): void;
}

interface GroupedTableVirtuosoHandle {
  /** Obtains the internal size state of the component for restoration */
  getState(stateCb: StateCallback): void;
  /** Scrolls the table with the specified amount */
  scrollBy(location: ScrollToOptions): void;
  /** Scrolls the table to the specified location */
  scrollTo(location: ScrollToOptions): void;
  /** Scrolls the item into view if necessary with group context */
  scrollIntoView(location: ScrollIntoViewLocationOptions): void;
  /** Scrolls the table to the specified item index */
  scrollToIndex(location: IndexLocationWithAlign | number): void;
}

Usage Example:

import React from 'react';
import { TableVirtuoso, TableVirtuosoHandle } from 'react-virtuoso';

function ControlledTable() {
  const tableRef = React.useRef<TableVirtuosoHandle>(null);
  const data = Array.from({ length: 1000 }, (_, i) => ({
    id: i + 1,
    name: `Row ${i + 1}`,
    value: Math.random() * 100
  }));

  const scrollToRow = (index: number) => {
    tableRef.current?.scrollToIndex({
      index,
      align: 'center',
      behavior: 'smooth'
    });
  };

  const scrollRowIntoView = (index: number) => {
    tableRef.current?.scrollIntoView({
      index,
      behavior: 'smooth',
      done: () => console.log(`Scrolled to row ${index}`)
    });
  };

  const saveTableState = () => {
    tableRef.current?.getState((state) => {
      console.log('Table state:', state);
      // Save state to localStorage or state management
    });
  };

  return (
    <div>
      <div style={{ marginBottom: '10px' }}>
        <button onClick={() => scrollToRow(0)}>First Row</button>
        <button onClick={() => scrollToRow(500)}>Middle Row</button>
        <button onClick={() => scrollRowIntoView(999)}>Last Row</button>
        <button onClick={saveTableState}>Save State</button>
      </div>
      
      <TableVirtuoso
        ref={tableRef}
        style={{ height: '400px' }}
        data={data}
        fixedHeaderContent={() => (
          <tr>
            <th style={{ padding: '12px' }}>ID</th>
            <th style={{ padding: '12px' }}>Name</th>
            <th style={{ padding: '12px' }}>Value</th>
          </tr>
        )}
        itemContent={(index, item) => (
          <>
            <td style={{ padding: '12px' }}>{item.id}</td>
            <td style={{ padding: '12px' }}>{item.name}</td>
            <td style={{ padding: '12px' }}>{item.value.toFixed(2)}</td>
          </>
        )}
      />
    </div>
  );
}

Advanced Scroll Control

Custom Scroll Calculation

Fine-tune scroll behavior with custom view location calculation.

type CalculateViewLocation = (params: CalculateViewLocationParams) => IndexLocationWithAlign | null | number;

interface CalculateViewLocationParams {
  itemBottom: number;
  itemTop: number;
  locationParams: {
    align?: 'center' | 'end' | 'start';
    behavior?: 'auto' | 'smooth';
  } & ({ groupIndex: number } | { index: number });
  viewportBottom: number;
  viewportTop: number;
}

Usage Example:

// Custom scroll behavior that only scrolls if item is completely out of view
const customScrollBehavior: CalculateViewLocation = ({
  itemTop,
  itemBottom,
  viewportTop,
  viewportBottom,
  locationParams
}) => {
  // Only scroll if item is completely out of view
  const completelyAbove = itemBottom < viewportTop;
  const completelyBelow = itemTop > viewportBottom;
  
  if (completelyAbove) {
    return { ...locationParams, align: 'start' };
  } else if (completelyBelow) {
    return { ...locationParams, align: 'end' };
  }
  
  // Item is at least partially visible, don't scroll
  return null;
};

// Use in scrollIntoView
virtuosoRef.current?.scrollIntoView({
  index: targetIndex,
  calculateViewLocation: customScrollBehavior,
  behavior: 'smooth'
});

State Management

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

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

Usage Example:

function StatefulVirtuoso() {
  const [savedState, setSavedState] = React.useState<StateSnapshot | null>(null);
  const virtuosoRef = React.useRef<VirtuosoHandle>(null);

  // Save state before navigation
  const handleNavigation = () => {
    virtuosoRef.current?.getState((state) => {
      localStorage.setItem('virtuoso-state', JSON.stringify(state));
    });
  };

  // Restore state on mount
  React.useEffect(() => {
    const stored = localStorage.getItem('virtuoso-state');
    if (stored) {
      setSavedState(JSON.parse(stored));
    }
  }, []);

  return (
    <Virtuoso
      ref={virtuosoRef}
      restoreStateFrom={savedState}
      data={items}
      itemContent={(index, item) => <div>{item}</div>}
    />
  );
}

Types

interface ScrollToOptions {
  top?: number;
  left?: number;
  behavior?: 'auto' | 'smooth';
}

enum LogLevel {
  DEBUG,
  INFO,
  WARN,
  ERROR
}

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