CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-window

High-performance virtualization components for React that render large lists and grids efficiently by only showing visible items

Pending
Overview
Eval results
Files

typescript-utilities.mddocs/

TypeScript Utilities

Convenience hooks for creating properly typed refs for Grid and List components, supporting both regular refs and callback refs with full TypeScript integration.

Capabilities

List Ref Hooks

TypeScript utility hooks for creating properly typed refs for List components.

/**
 * Convenience hook to return a properly typed ref for the List component
 * @returns RefObject with ListImperativeAPI type
 */
function useListRef(): RefObject<ListImperativeAPI>;

/**
 * Convenience hook to return a properly typed callback ref for the List component
 * Use this hook when you need to share the ref with another component or hook
 * @returns Tuple of [current instance, setter function]
 */
function useListCallbackRef(): [
  ListImperativeAPI | null,
  (instance: ListImperativeAPI | null) => void
];

Usage Examples:

import React from "react";
import { List, useListRef, useListCallbackRef } from "react-window";

// Using useListRef - standard ref pattern
const ListWithStandardRef = () => {
  const listRef = useListRef();

  const scrollToMiddle = () => {
    // TypeScript knows listRef.current is ListImperativeAPI | null
    listRef.current?.scrollToRow({
      index: 500,
      align: "center",
      behavior: "smooth"
    });
  };

  return (
    <div>
      <button onClick={scrollToMiddle}>Scroll to Middle</button>
      <List
        listRef={listRef}  // Type-safe ref assignment
        rowComponent={RowComponent}
        rowCount={1000}
        rowHeight={35}
        rowProps={{}}
        style={{ height: 400, width: 300 }}
      />
    </div>
  );
};

// Using useListCallbackRef - callback ref pattern
const ListWithCallbackRef = () => {
  const [listInstance, setListRef] = useListCallbackRef();

  // Access the instance when it's available
  React.useEffect(() => {
    if (listInstance) {
      // TypeScript knows listInstance is ListImperativeAPI
      console.log("List mounted:", listInstance.element);
      
      // Auto-scroll to a specific position
      listInstance.scrollToRow({
        index: 100,
        align: "start",
        behavior: "instant"
      });
    }
  }, [listInstance]);

  return (
    <List
      listRef={setListRef}  // Type-safe callback ref
      rowComponent={RowComponent}
      rowCount={1000}
      rowHeight={35}
      rowProps={{}}
      style={{ height: 400, width: 300 }}
    />
  );
};

// Sharing refs between components
const SharedListRef = () => {
  const [listInstance, setListRef] = useListCallbackRef();

  return (
    <div>
      <ListControls listInstance={listInstance} />
      <List
        listRef={setListRef}
        rowComponent={RowComponent}
        rowCount={1000}
        rowHeight={35}
        rowProps={{}}
        style={{ height: 400, width: 300 }}
      />
    </div>
  );
};

const ListControls = ({ listInstance }: { listInstance: ListImperativeAPI | null }) => {
  const scrollToTop = () => {
    listInstance?.scrollToRow({ index: 0, align: "start", behavior: "smooth" });
  };

  const scrollToBottom = () => {
    listInstance?.scrollToRow({ index: 999, align: "end", behavior: "smooth" });
  };

  return (
    <div>
      <button onClick={scrollToTop} disabled={!listInstance}>
        Scroll to Top
      </button>
      <button onClick={scrollToBottom} disabled={!listInstance}>
        Scroll to Bottom
      </button>
    </div>
  );
};

Grid Ref Hooks

TypeScript utility hooks for creating properly typed refs for Grid components.

/**
 * Convenience hook to return a properly typed ref for the Grid component
 * @returns RefObject with GridImperativeAPI type
 */
function useGridRef(): RefObject<GridImperativeAPI>;

/**
 * Convenience hook to return a properly typed callback ref for the Grid component
 * Use this hook when you need to share the ref with another component or hook
 * @returns Tuple of [current instance, setter function]
 */
function useGridCallbackRef(): [
  GridImperativeAPI | null,
  (instance: GridImperativeAPI | null) => void
];

Usage Examples:

import React from "react";
import { Grid, useGridRef, useGridCallbackRef } from "react-window";

// Using useGridRef - standard ref pattern
const GridWithStandardRef = () => {
  const gridRef = useGridRef();

  const scrollToCenter = () => {
    // TypeScript knows gridRef.current is GridImperativeAPI | null
    gridRef.current?.scrollToCell({
      rowIndex: 50,
      columnIndex: 25,
      rowAlign: "center",
      columnAlign: "center",
      behavior: "smooth"
    });
  };

  const scrollToCorner = () => {
    gridRef.current?.scrollToCell({
      rowIndex: 0,
      columnIndex: 0,
      rowAlign: "start",
      columnAlign: "start",
      behavior: "smooth"
    });
  };

  return (
    <div>
      <button onClick={scrollToCenter}>Scroll to Center</button>
      <button onClick={scrollToCorner}>Scroll to Corner</button>
      <Grid
        gridRef={gridRef}  // Type-safe ref assignment
        cellComponent={CellComponent}
        cellProps={{}}
        columnCount={50}
        columnWidth={100}
        rowCount={100}
        rowHeight={50}
        style={{ height: 400, width: 500 }}
      />
    </div>
  );
};

// Using useGridCallbackRef - callback ref pattern
const GridWithCallbackRef = () => {
  const [gridInstance, setGridRef] = useGridCallbackRef();

  React.useEffect(() => {
    if (gridInstance) {
      // TypeScript knows gridInstance is GridImperativeAPI
      console.log("Grid mounted:", gridInstance.element);
      
      // Auto-scroll to a specific cell
      gridInstance.scrollToCell({
        rowIndex: 25,
        columnIndex: 12,
        rowAlign: "smart",
        columnAlign: "smart",
        behavior: "instant"
      });
    }
  }, [gridInstance]);

  return (
    <Grid
      gridRef={setGridRef}  // Type-safe callback ref
      cellComponent={CellComponent}
      cellProps={{}}
      columnCount={50}
      columnWidth={100}
      rowCount={100}
      rowHeight={50}
      style={{ height: 400, width: 500 }}
    />
  );
};

// Advanced grid navigation with shared ref
const GridWithNavigation = () => {
  const [gridInstance, setGridRef] = useGridCallbackRef();
  const [currentPosition, setCurrentPosition] = React.useState({ row: 0, col: 0 });

  const navigate = (direction: 'up' | 'down' | 'left' | 'right') => {
    if (!gridInstance) return;

    const { row, col } = currentPosition;
    let newRow = row;
    let newCol = col;

    switch (direction) {
      case 'up':
        newRow = Math.max(0, row - 1);
        break;
      case 'down':
        newRow = Math.min(99, row + 1);
        break;
      case 'left':
        newCol = Math.max(0, col - 1);
        break;
      case 'right':
        newCol = Math.min(49, col + 1);
        break;
    }

    setCurrentPosition({ row: newRow, col: newCol });
    
    gridInstance.scrollToCell({
      rowIndex: newRow,
      columnIndex: newCol,
      rowAlign: "smart",
      columnAlign: "smart",
      behavior: "smooth"
    });
  };

  return (
    <div>
      <div>Position: {currentPosition.row}, {currentPosition.col}</div>
      <GridNavigation onNavigate={navigate} />
      <Grid
        gridRef={setGridRef}
        cellComponent={CellComponent}
        cellProps={{ currentPosition }}
        columnCount={50}
        columnWidth={100}
        rowCount={100}
        rowHeight={50}
        style={{ height: 400, width: 500 }}
      />
    </div>
  );
};

const GridNavigation = ({ onNavigate }: { 
  onNavigate: (direction: 'up' | 'down' | 'left' | 'right') => void;
}) => (
  <div>
    <button onClick={() => onNavigate('up')}>↑</button>
    <button onClick={() => onNavigate('down')}>↓</button>
    <button onClick={() => onNavigate('left')}>←</button>
    <button onClick={() => onNavigate('right')}>→</button>
  </div>
);

Custom Hook Patterns

Building custom hooks with the provided utilities for common patterns.

// Custom hook for keyboard navigation in lists
const useListKeyboardNavigation = (
  listRef: RefObject<ListImperativeAPI>,
  itemCount: number
) => {
  const [currentIndex, setCurrentIndex] = React.useState(0);

  React.useEffect(() => {
    const handleKeyDown = (event: KeyboardEvent) => {
      switch (event.key) {
        case 'ArrowUp':
          event.preventDefault();
          const newUpIndex = Math.max(0, currentIndex - 1);
          setCurrentIndex(newUpIndex);
          listRef.current?.scrollToRow({
            index: newUpIndex,
            align: "smart",
            behavior: "smooth"
          });
          break;
          
        case 'ArrowDown':
          event.preventDefault();
          const newDownIndex = Math.min(itemCount - 1, currentIndex + 1);
          setCurrentIndex(newDownIndex);
          listRef.current?.scrollToRow({
            index: newDownIndex,
            align: "smart",
            behavior: "smooth"
          });
          break;
          
        case 'Home':
          event.preventDefault();
          setCurrentIndex(0);
          listRef.current?.scrollToRow({
            index: 0,
            align: "start",
            behavior: "smooth"
          });
          break;
          
        case 'End':
          event.preventDefault();
          setCurrentIndex(itemCount - 1);
          listRef.current?.scrollToRow({
            index: itemCount - 1,
            align: "end",
            behavior: "smooth"
          });
          break;
      }
    };

    window.addEventListener('keydown', handleKeyDown);
    return () => window.removeEventListener('keydown', handleKeyDown);
  }, [currentIndex, itemCount, listRef]);

  return currentIndex;
};

// Usage of custom hook
const KeyboardNavigableList = () => {
  const listRef = useListRef();
  const currentIndex = useListKeyboardNavigation(listRef, 1000);

  return (
    <div>
      <div>Current Item: {currentIndex}</div>
      <List
        listRef={listRef}
        rowComponent={({ index, style }) => (
          <div 
            style={{
              ...style,
              backgroundColor: index === currentIndex ? '#e6f3ff' : 'transparent'
            }}
          >
            Item {index}
          </div>
        )}
        rowCount={1000}
        rowHeight={35}
        rowProps={{}}
        style={{ height: 400, width: 300 }}
      />
    </div>
  );
};

// Custom hook for grid cell selection
const useGridCellSelection = (
  gridRef: RefObject<GridImperativeAPI>,
  rowCount: number,
  columnCount: number
) => {
  const [selectedCell, setSelectedCell] = React.useState<{row: number, col: number} | null>(null);

  const selectCell = (row: number, col: number) => {
    setSelectedCell({ row, col });
    gridRef.current?.scrollToCell({
      rowIndex: row,
      columnIndex: col,
      rowAlign: "smart",
      columnAlign: "smart",
      behavior: "smooth"
    });
  };

  const clearSelection = () => setSelectedCell(null);

  return { selectedCell, selectCell, clearSelection };
};

TypeScript Integration Benefits

The utility hooks provide several TypeScript benefits:

  1. Type Safety: Eliminates the need for type assertions or any types
  2. IntelliSense: Full autocompletion for imperative API methods
  3. Error Prevention: Compile-time checks for proper API usage
  4. Refactoring Safety: Changes to imperative APIs are caught at compile time
// Without utility hooks (manual typing required)
const manualListRef = useRef<ListImperativeAPI>(null);

// With utility hook (automatic typing)
const typedListRef = useListRef();

// Both provide the same functionality, but the utility hook
// eliminates the need to import and specify the type manually

Install with Tessl CLI

npx tessl i tessl/npm-react-window

docs

grid-virtualization.md

imperative-apis.md

index.md

list-virtualization.md

typescript-utilities.md

tile.json