React cookbook · Intermediate

Render 10,000 rows smoothly with virtualization

Display a very long list or table without freezing the browser.

Approach

Render only the visible rows using TanStack Virtual: a scroll container with a fixed height, an inner element sized to the total height, and absolutely positioned rows for the virtual items. Use measureElement for variable row heights.

Solution

import { useRef } from "react";
import { useVirtualizer } from "@tanstack/react-virtual";

export function LogList({ rows }: { rows: LogLine[] }) {
  const parentRef = useRef<HTMLDivElement>(null);
  const virtualizer = useVirtualizer({
    count: rows.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 32,
    overscan: 10,
  });

  return (
    <div ref={parentRef} style={{ height: 600, overflow: "auto" }} role="list" aria-label="Log lines">
      <div style={{ height: virtualizer.getTotalSize(), position: "relative" }}>
        {virtualizer.getVirtualItems().map((item) => {
          const row = rows[item.index];
          return (
            <div
              key={row.id}
              role="listitem"
              data-index={item.index}
              ref={virtualizer.measureElement}
              style={{ position: "absolute", top: 0, left: 0, width: "100%", transform: `translateY(${item.start}px)` }}
            >
              <time>{row.time}</time> {row.message}
            </div>
          );
        })}
      </div>
    </div>
  );
}

Pitfalls

  • Virtualizing short lists (complexity without benefit).
  • Breaking Ctrl+F search and screen reader navigation — consider pagination for content users need to search.
  • Using index keys when rows can be inserted.

Variations

  • Combine with useInfiniteQuery to load more as the user scrolls.
  • TanStack Table + Virtual for large data grids.

Further reading: TanStack Virtual

Reference: https://tanstack.com/virtual/latest/docs/introduction

More React recipes