CtrlK
BlogDocsLog inGet started
Tessl Logo

with-tanstack-virtual

Virtualize final React Table row or column models with TanStack Virtual. Load for useVirtualizer counts, scroll elements, stable keys, data-index measurement, dynamic heights, sticky headers/columns, grid/flex geometry, or infinite fetching; Virtual is renderer composition, not a Table feature.

65

Quality

80%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

Fix and improve this skill with Tessl

tessl review fix ./packages/react-table/skills/with-tanstack-virtual/SKILL.md
SKILL.md
Quality
Evals
Security

This skill builds on @tanstack/table-core#core, getting-started, and table-state. Build the Table model first, then virtualize its final rendered rows or visible columns.

Setup

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

function VirtualBody({ table }: { table: any }) {
  const scrollRef = useRef<HTMLDivElement>(null)
  const rows = table.getRowModel().rows
  const virtualizer = useVirtualizer({
    count: rows.length,
    getScrollElement: () => scrollRef.current,
    estimateSize: () => 36,
    getItemKey: (index) => rows[index].id,
    overscan: 5,
  })
  return (
    <div ref={scrollRef} style={{ height: 400, overflow: 'auto' }}>
      <div style={{ height: virtualizer.getTotalSize(), position: 'relative' }}>
        {virtualizer.getVirtualItems().map((item) => (
          <div
            key={rows[item.index].id}
            data-index={item.index}
            ref={virtualizer.measureElement}
            style={{
              position: 'absolute',
              transform: `translateY(${item.start}px)`,
              width: '100%',
            }}
          >
            {rows[item.index].getAllCells().map((cell: any) => (
              <span
                key={cell.id}
                style={{
                  display: 'inline-block',
                  width: cell.column.getSize(),
                }}
              >
                <table.FlexRender cell={cell} />
              </span>
            ))}
          </div>
        ))}
      </div>
    </div>
  )
}

Core Patterns

Keep the virtualizer near its render loop

const rows = table.getRowModel().rows
const rowVirtualizer = useVirtualizer({
  count: rows.length,
  getScrollElement: () => container.current,
  estimateSize: () => 36,
})

This limits unrelated parent updates and keeps count, measurement, and rendered items together.

Use Table sizes in renderer CSS

<td style={{ width: cell.column.getSize() }}>
  <table.FlexRender cell={cell} />
</td>

Table calculates size state; the renderer must apply it.

Common Mistakes

HIGH Registering Virtual as a feature

Wrong:

const features = tableFeatures({ rowVirtualizer: useVirtualizer(options) })

Correct:

const rows = table.getRowModel().rows
const virtualizer = useVirtualizer({
  count: rows.length,
  getScrollElement: () => scrollRef.current,
  estimateSize: () => 36,
})

Virtual controls rendering geometry and is not a Table feature or row model.

Source: docs/framework/react/guide/virtualization.md

HIGH Virtualizing raw input data

Wrong:

const virtualizer = useVirtualizer({
  count: data.length,
  getScrollElement,
  estimateSize,
})

Correct:

const rows = table.getRowModel().rows
const virtualizer = useVirtualizer({
  count: rows.length,
  getScrollElement,
  estimateSize,
})

Raw data ignores filtering, sorting, expansion, grouping, and pagination already applied by Table.

Source: examples/react/virtualized-rows

HIGH Measuring against incomplete identity

Wrong:

<tr ref={virtualizer.measureElement}>{row.id}</tr>

Correct:

<tr key={row.id} data-index={item.index} ref={virtualizer.measureElement}>
  {row.id}
</tr>

Dynamic measurement needs the virtual index, and stable row identity prevents measurements moving to the wrong row.

Source: examples/react/virtualized-rows

HIGH Omitting positioning geometry

Wrong:

<tbody>{virtualizer.getVirtualItems().map(renderRow)}</tbody>

Correct:

<tbody
  style={{
    display: 'grid',
    height: virtualizer.getTotalSize(),
    position: 'relative',
  }}
>
  {virtualizer.getVirtualItems().map(renderRow)}
</tbody>

Virtual only computes positions; the renderer must provide total spacer size and position items using each virtual start.

Source: examples/react/virtualized-rows

API Discovery

Inspect node_modules/@tanstack/react-table/dist/index.d.ts for Table render APIs and installed node_modules/@tanstack/react-virtual/dist/ for the exact virtualizer options. Copy layout contracts from the maintained example matching rows, columns, or infinite loading.

Repository
TanStack/table
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.