Skip to content

Inspecting Row Re-renders

unstableRowRender$ emits an event every time a row renders, with the row index, section, sticky flag, and group flag. Use it to answer “which rows rendered during this scroll or update?” — disabled by default; opt in with unstableEnableRowRenderEvents$.

import * as React from 'react'

import { Button } from '@/components/ui/button'
import { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader } from '@/components/ui/data-table'
import {
  localModel,
  scrollToRow$,
  unstableEnableRowRenderEvents$,
  unstableRowRender$,
  useEngineRef,
  usePublisher,
  useRemotePublisher,
} from '@virtuoso.dev/data-table'
import { useEngine } from '@virtuoso.dev/reactive-engine-react'

import type { UnstableRowRenderEvent } from '@virtuoso.dev/data-table'

const rowRenderEvents = new EventTarget()
const rows = Array.from({ length: 80 }, (_, index) => ({
  id: `SKU-${String(index + 1).padStart(3, '0')}`,
  name: `Product ${index + 1}`,
  status: ['In stock', 'Low stock', 'Backorder'][index % 3]!,
}))

function emitRowRender(event: UnstableRowRenderEvent) {
  queueMicrotask(() => {
    rowRenderEvents.dispatchEvent(new CustomEvent<UnstableRowRenderEvent>('row-render', { detail: event }))
  })
}

function RowRenderProbe() {
  const engine = useEngine()
  const enableRowRenderEvents = usePublisher(unstableEnableRowRenderEvents$)

  React.useLayoutEffect(() => {
    enableRowRenderEvents(true)
    const unsubscribe = engine.sub(unstableRowRender$, emitRowRender)

    return () => {
      unsubscribe()
      enableRowRenderEvents(false)
    }
  }, [enableRowRenderEvents, engine])

  return null
}

function EventLog() {
  const [events, setEvents] = React.useState<UnstableRowRenderEvent[]>([])

  React.useEffect(() => {
    const recordEvent = (event: Event) => {
      setEvents((current) => [(event as CustomEvent<UnstableRowRenderEvent>).detail, ...current].slice(0, 4))
    }

    rowRenderEvents.addEventListener('row-render', recordEvent)
    return () => {
      rowRenderEvents.removeEventListener('row-render', recordEvent)
    }
  }, [])

  return (
    <div className="rounded-lg border bg-muted/30 p-3 text-xs">
      <div className="mb-1 text-sm text-muted-foreground">Captured events: {events.length}</div>
      {events.length === 0 ? (
        <span className="text-muted-foreground">No events captured</span>
      ) : (
        events.map((event, index) => (
          <div key={`${event.index}-${event.section}-${index}`} className="font-mono">
            row {event.index}, {event.section}
          </div>
        ))
      )}
    </div>
  )
}

const model = localModel({ data: rows })

export default function App() {
  const engineRef = useEngineRef()
  const scrollToRow = useRemotePublisher(scrollToRow$, engineRef)

  return (
    <div className="space-y-3">
      <div className="flex flex-wrap items-center gap-2">
        <Button onClick={() => scrollToRow({ index: 0, behavior: 'smooth' })} variant="outline">
          Top
        </Button>
        <Button onClick={() => scrollToRow({ index: 48, align: 'center', behavior: 'smooth' })} variant="outline">
          Row 49
        </Button>
      </div>

      <DataTable className="rounded-xl" computeRowKey={({ data }) => data.id} model={model} engineRef={engineRef} style={{ height: 320 }}>
        <RowRenderProbe />
        <DataTableColumn field="name">
          <DataTableColumnHeader>Product</DataTableColumnHeader>
          <DataTableCell className="font-medium">{({ cellValue }) => String(cellValue)}</DataTableCell>
        </DataTableColumn>
        <DataTableColumn field="status">
          <DataTableColumnHeader>Status</DataTableColumnHeader>
          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>
        </DataTableColumn>
      </DataTable>

      <EventLog />
    </div>
  )
}
import * as React from 'react'

import { unstableEnableRowRenderEvents$, unstableRowRender$, usePublisher } from '@virtuoso.dev/data-table'
import { useEngine } from '@virtuoso.dev/reactive-engine-react'

function RowRenderProbe() {
  const engine = useEngine()
  const enableRowRenderEvents = usePublisher(unstableEnableRowRenderEvents$)

  React.useLayoutEffect(() => {
    enableRowRenderEvents(true)
    const unsubscribe = engine.sub(unstableRowRender$, (event) => {
      console.log(event.index, event.section, event.sticky, event.group)
    })

    return () => {
      unsubscribe()
      enableRowRenderEvents(false)
    }
  }, [enableRowRenderEvents, engine])

  return null
}

Mount the probe as a child of the table so it can access the engine:

<DataTable model={model}>
  <RowRenderProbe />
  {/* columns */}
</DataTable>

The unstable prefix is literal — event timing and payload details may change between versions. Use this for diagnostics, profiling, and tests, never as product behavior.