Learn TanStack - Virtualization & Performance
Episode 11 of 24

Learn TanStack - Virtualization & Performance

This episode dissects TanStack Virtual: the windowing concept and useVirtualizer, virtual scrolling for lists and tables, measuring dynamic items with measureElement, and performance tuning such as overscan for large data sets.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Rendering a list of one hundred thousand items is a punishment for the browser: every item needs a DOM node, and every scroll forces a layout pass. TanStack Virtual solves this with windowing — only the items visible in the viewport are actually rendered.

Episode 11 dissects the windowing concept and useVirtualizer, virtual scrolling for lists and tables, measuring items with dynamic heights, and performance tuning with overscan and size estimation.

This is the pattern that saves dashboard apps and log viewers from performance degradation. In episode 9 you already touched virtualized tables; now we go deep into the mechanics.

Windowing and Virtualizer Basics

Render Only What's Visible

The Virtualizer computes which items fit in the viewport based on scroll position, then provides that range of items. The remaining space is filled with placeholders as tall as the total data:

JSVirtualizer dasar untuk list
import { useRef } from "react"
import { useVirtualizer } from "@tanstack/react-virtual"
 
function Daftar() {
  const parentRef = useRef(null)
 
  const virtualizer = useVirtualizer({
    count: 100_000,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 40,
  })
 
  const items = virtualizer.getVirtualItems()
 
  return (
    <div ref={parentRef} style={{ height: 400, overflowY: "auto" }}>
      <div style={{ height: virtualizer.getTotalSize(), position: "relative" }}>
        {items.map((item) => (
          <div
            key={item.key}
            style={{
              position: "absolute",
              top: 0,
              left: 0,
              width: "100%",
              height: item.size,
              transform: `translateY(${item.start}px)`,
            }}
          >
            Baris ke-{item.index}
          </div>
        ))}
      </div>
    </div>
  )
}

getScrollElement: () => parentRef.current points to the scroll element. getVirtualItems() returns the visible items; each item is positioned with a translateY of its start value. getTotalSize() gives the total height so the scrollbar tracks all 100,000 rows even though only a dozen or so are rendered.

Virtual Scrolling for Lists and Tables

Columns and Rows at Once

For tables, virtualization is usually applied to rows (as in episode 9) and can also apply to columns. Virtual rows take their data from table.getRowModel().rows, so sorting and filtering still run before virtualization:

JSList atau tabel dengan item virtual
const rows = table.getRowModel().rows
const virtualRows = virtualizer.getVirtualItems()
 
const totalSize = virtualizer.getTotalSize()
 
return (
  <tbody>
    {virtualRows.map((virtualRow) => {
      const row = rows[virtualRow.index]
      return (
        <tr key={row.id} style={{ height: virtualRow.size }}>
          {row.getVisibleCells().map((cell) => (
            <td key={cell.id}>
              {flexRender(cell.column.columnDef.cell, cell.getContext())}
            </td>
          ))}
        </tr>
      )
    })}
  </tbody>
)

virtualRows contains the visible rows, and each item maps to a real table row by index. The row model stays the source of truth, while the virtualizer decides what enters the DOM.

Measuring Dynamic Items

measureElement for Varying Heights

When items have different heights, static estimation isn't accurate. Use measureElement so the virtualizer measures real heights after render:

JSMengukur item dinamis
const virtualizer = useVirtualizer({
  count: items.length,
  getScrollElement: () => parentRef.current,
  estimateSize: () => 80,
  measureElement: (el) => el.getBoundingClientRect().height,
})
 
<div
  ref={virtualizer.measureElement}
  data-index={item.index}
  style={{ transform: `translateY(${item.start}px)` }}
>
  {items[item.index]}
</div>

ref={virtualizer.measureElement} is attached to each item along with data-index. After an item renders, the virtualizer measures its actual height and corrects the position of subsequent items. Scrolling becomes accurate even when every item's content differs in length.

Performance Tuning

Overscan and Realistic Estimation

Two key settings for performance:

  • overscan: the number of extra items rendered outside the viewport. Small values trim React's workload; large values prevent flash during fast scrolling.
  • estimateSize: use an estimate close to the real size so initial position calculations are accurate.
JSTuning dengan overscan
const virtualizer = useVirtualizer({
  count: 100_000,
  getScrollElement: () => parentRef.current,
  estimateSize: () => 40,
  overscan: 8,
  scrollMargin: parentRef.current?.offsetTop ?? 0,
})

overscan: 8 renders eight extra items in each direction. scrollMargin matters when the scroll area doesn't start at the top of the page — without it, item positions are wrong when there are elements above the container.

Warning

Don't attach ref={virtualizer.measureElement} to components that frequently change size, like slowly loading images. Measure with a separate ResizeObserver so the virtualizer isn't re-measured endlessly.

Conclusion

Episode 11 wrapped up virtualization: windowing with useVirtualizer that only renders visible items, virtual scrolling for lists and tables, measuring dynamic items through measureElement, and tuning with overscan and realistic estimation.

Key takeaways:

  • Windowing only renders items inside the viewport.
  • getVirtualItems provides visible items; getTotalSize sets the scroll height.
  • Virtual rows map to the row model by index.
  • measureElement measures dynamic item heights after render.
  • overscan balances scroll smoothness against render load.
  • scrollMargin corrects positions when the container isn't at the top of the page.

In the next episode, episode 12, we'll discuss authentication and secure data fetching — secure token storage, auth headers on the query client, the refresh token flow, router guards, and role-based data fetching. Your data is starting to need protection!

Learn TanStack - Virtualization & Performance | Learn TanStack