TanStack Table React Virtualization: Fast Large Dataset Rendering
Render tens of thousands of rows without jank using TanStack Table v8 and react-virtual — infinite scroll, custom filters, and real performance gains.

Rendering a table with a few hundred rows is trivial. Rendering one with 50,000+ rows — while keeping sorting, filtering, and smooth scrolling — is a completely different problem. TanStack Table gives you the headless logic; TanStack Virtual gives you the rendering performance. Together they let you build a data table that stays fast regardless of dataset size.
This guide builds a virtualized, infinitely-scrollable data table step by step.
Step 1: Set Up a Headless TanStack Table
TanStack Table doesn't render any markup — you define columns and it hands you row/column models to render however you want.
At this point rows could be 50 or 50,000 items — TanStack Table doesn't care, because it never touches the DOM.
Step 2: Virtualize the Rows
@tanstack/react-virtual only mounts the DOM nodes that are actually visible in the scroll container, plus a small overscan buffer.
The key mechanics: getTotalSize() gives the virtualizer's computed total scrollable height, and each row is absolutely positioned via translateY to its calculated offset. Only the rows returned by getVirtualItems() — typically 15-25 depending on viewport height — actually exist in the DOM at any moment, even if rows.length is 50,000.
Step 3: Add Infinite Scroll for Server-Paginated Data
Virtualization handles rendering; infinite scroll handles fetching more data as the user approaches the bottom. Combine the virtualizer's scroll position with useInfiniteQuery (TanStack Query):
This fetches the next 100-row page once the user scrolls within 20 rows of the currently loaded data, so new rows arrive before the user actually hits the bottom.
Step 4: Add Custom Filters Without Breaking Virtualization
Filtering happens entirely in TanStack Table's state layer — the virtualizer never needs to know about it.
Because rowVirtualizer is built from table.getRowModel().rows.length, filtering the underlying data automatically shrinks the virtualized row count — no manual synchronization required.
Performance in Practice
On a 50,000-row dataset, an un-virtualized table renders 50,000+ DOM nodes upfront — first paint alone can take several seconds and scrolling drops to single-digit frame rates. With virtualization, only ~20-30 rows ever exist in the DOM at once regardless of dataset size, so first paint stays under 100ms and scrolling holds a smooth 60fps because the browser is never asked to lay out more than a screenful of rows.
Common Pitfall: Row Height Drift Causes Scroll Jumps
If your estimateSize is a fixed value but actual row content varies in height (wrapped text, conditional badges), the virtualizer's positions will be slightly wrong until it measures real DOM heights. Always attach ref={rowVirtualizer.measureElement} to each row — it corrects the estimate after the first render and eliminates the jump.
Key Takeaways
TanStack Table's headless architecture is what makes true virtualization possible — it never dictates markup, so you can hand its row model straight to @tanstack/react-virtual and only render what's on screen. Layer infinite scroll on top with useInfiniteQuery, keep filtering in TanStack Table's state (the virtualizer picks it up automatically), and always measure real row heights to avoid scroll jitter.






