React Data Grid Virtualization

Virtualization

Row virtualization renders only the visible window of rows, so the React Data Grid stays fast with 100,000 rows and beyond — sorting, filtering and selection run across the full dataset.

On this page

Virtualization keeps the DOM small no matter how large the dataset is: only the rows currently in view (plus a small buffer) are rendered as real elements. Reach for it whenever you bind more rows than the browser can comfortably paint at once — a few thousand and up. It is on by default (virtualization) and is mutually exclusive with pagination.

100,000 rows, virtualized

Only the visible window of rows exists in the DOM — scroll, sort, filter and select across the full dataset. This live demo runs on 100,000 generated rows.

import { CDataGrid } from '@coreui/react-data-grid'
import { useMemo } from 'react'

const firstNames = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy']
const lastNames = ['Smith', 'Jones', 'Brown', 'Taylor', 'Wilson', 'Davies', 'Evans', 'Thomas']
const roles = ['admin', 'editor', 'viewer']
const statuses = ['active', 'pending', 'banned']

export const DataGridVirtualExample = () => {
  const items = useMemo(
    () =>
      Array.from({ length: 100_000 }, (_, i) => {
        const name = `${firstNames[i % firstNames.length]} ${lastNames[i % lastNames.length]}`
        return {
          id: i + 1,
          name,
          email: `${name.toLowerCase().replace(' ', '.')}${i}@example.com`,
          role: roles[i % roles.length],
          status: statuses[i % statuses.length],
          score: (i * 37) % 1000
        }
      }),
    []
  )

  return (
    <CDataGrid
      columns={[
        { key: 'id', label: '#', width: 90 },
        { key: 'name', label: 'Name', width: 200 },
        { key: 'email', label: 'Email', width: 260 },
        { key: 'role', label: 'Role', width: 110 },
        { key: 'status', label: 'Status', width: 110 },
        { key: 'score', label: 'Score', width: 90 }
      ]}
      items={items}
      itemKey={item => String(item.id)}
      columnFilters
      globalFilter
      rowSelection
    />
  )
}
import { CDataGrid } from '@coreui/react-data-grid'
import { useMemo } from 'react'

const firstNames = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy']
const lastNames = ['Smith', 'Jones', 'Brown', 'Taylor', 'Wilson', 'Davies', 'Evans', 'Thomas']
const roles = ['admin', 'editor', 'viewer']
const statuses = ['active', 'pending', 'banned']

export const DataGridVirtualExample = () => {
  const items = useMemo(
    () =>
      Array.from({ length: 100_000 }, (_, i) => {
        const name = `${firstNames[i % firstNames.length]} ${lastNames[i % lastNames.length]}`
        return {
          id: i + 1,
          name,
          email: `${name.toLowerCase().replace(' ', '.')}${i}@example.com`,
          role: roles[i % roles.length],
          status: statuses[i % statuses.length],
          score: (i * 37) % 1000
        }
      }),
    []
  )

  return (
    <CDataGrid
      columns={[
        { key: 'id', label: '#', width: 90 },
        { key: 'name', label: 'Name', width: 200 },
        { key: 'email', label: 'Email', width: 260 },
        { key: 'role', label: 'Role', width: 110 },
        { key: 'status', label: 'Status', width: 110 },
        { key: 'score', label: 'Score', width: 90 }
      ]}
      items={items}
      itemKey={item => String(item.id)}
      columnFilters
      globalFilter
      rowSelection
    />
  )
}

How it works

The grid measures the scroll viewport and renders only the rows that intersect it. Two props tune the behavior:

  • rowHeight — the estimated row height in px (default 44) the virtualizer uses to size the scroll area and decide how many rows fit.
  • overscan — extra rows rendered above and below the visible window (default 10) to smooth fast scrolling. Raise it if you see blank rows while flinging; lower it to shave DOM nodes.

Fixed row height is assumed in this release — dynamic per-row heights are on the roadmap. For datasets larger than browser memory, hand paging to your backend with server-side data.

See the Performance guide for tuning advice.