Sobes.tech
Middle

How to implement keyboard navigation (up/down arrows) in a virtualized list using @tanstack/virtual?

sobes.tech AI

Answer from AI

To implement keyboard navigation (up/down arrows) in a virtualized list using @tanstack/virtual, you need to manage the currently highlighted index and scroll the list to the desired item when this index changes.

Main steps:

  1. Store the index of the currently highlighted item in the state.
  2. Handle keyboard events (keydown) on the list container.
  3. When pressing the down arrow, increase the index; when pressing the up arrow, decrease it, with boundary checks.
  4. Use the scrollToIndex method from the @tanstack/virtual API to scroll the list to the highlighted item.

Example in React:

import React, { useState, useRef, useEffect } from 'react';
import { useVirtual } from '@tanstack/react-virtual';

function VirtualList({ items }) {
  const parentRef = useRef(null);
  const [highlightedIndex, setHighlightedIndex] = useState(0);

  const rowVirtualizer = useVirtual({
    size: items.length,
    parentRef,
    estimateSize: () => 35,
    overscan: 5,
  });

  useEffect(() => {
    rowVirtualizer.scrollToIndex(highlightedIndex);
  }, [highlightedIndex, rowVirtualizer]);

  const onKeyDown = (e) => {
    if (e.key === 'ArrowDown') {
      e.preventDefault();
      setHighlightedIndex(i => Math.min(i + 1, items.length - 1));
    } else if (e.key === 'ArrowUp') {
      e.preventDefault();
      setHighlightedIndex(i => Math.max(i - 1, 0));
    }
  };

  return (
    <div
      ref={parentRef}
      tabIndex={0}
      onKeyDown={onKeyDown}
      style={{ height: `300px`, overflow: 'auto', outline: 'none' }}
    >
      <div
        style={{
          height: `${rowVirtualizer.totalSize}px`,
          position: 'relative',
        }}
      >
        {rowVirtualizer.virtualItems.map(virtualRow => {
          const isSelected = virtualRow.index === highlightedIndex;
          return (
            <div
              key={virtualRow.index}
              ref={virtualRow.measureRef}
              style={{
                position: 'absolute',
                top: 0,
                left: 0,
                width: '100%',
                height: `${virtualRow.size}px`,
                transform: `translateY(${virtualRow.start}px)`,
                backgroundColor: isSelected ? '#bde4ff' : 'transparent',
                padding: '8px',
                boxSizing: 'border-box',
              }}
            >
              {items[virtualRow.index]}
            </div>
          );
        })}
      </div>
    </div>
  );
}

In this example, pressing the arrow keys updates the highlighted index, and the list scrolls to it, providing convenient keyboard navigation.