import React from 'react' import ReactDOM from 'react-dom/client' import './index.css' import { ColumnDef, flexRender, getCoreRowModel, getSortedRowModel, Row, Table, useReactTable, } from '@tanstack/react-table' import { useVirtualizer, VirtualItem, Virtualizer, } from '@tanstack/react-virtual' import { makeData, Person } from './makeData' //This is a dynamic row height example, which is more complicated, but allows for a more realistic table. //See https://tanstack.com/virtual/v3/docs/examples/react/table for a simpler fixed row height example. function App() { const columns = React.useMemo<ColumnDef<Person>[]>( () => [ { accessorKey: 'id', header: 'ID', size: 60, }, { accessorKey: 'firstName', cell: (info) => info.getValue(), }, { accessorFn: (row) => row.lastName, id: 'lastName', cell: (info) => info.getValue(), header: () => <span>Last Name</span>, }, { accessorKey: 'age', header: () => 'Age', size: 50, }, { accessorKey: 'visits', header: () => <span>Visits</span>, size: 50, }, { accessorKey: 'status', header: 'Status', }, { accessorKey: 'progress', header: 'Profile Progress', size: 80, }, { accessorKey: 'createdAt', header: 'Created At', cell: (info) => info.getValue<Date>().toLocaleString(), size: 250, }, ], [], ) // The virtualizer will need a reference to the scrollable container element const tableContainerRef = React.useRef<HTMLDivElement>(null) const [data, setData] = React.useState(() => makeData(50_000)) const refreshData = React.useCallback(() => { setData(makeData(50_000)) }, []) const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), debugTable: true, }) // All important CSS styles are included as inline styles for this example. This is not recommended for your code. return ( <div className="app"> {process.env.NODE_ENV === 'development' ? ( <p> <strong>Notice:</strong> You are currently running React in development mode. Virtualized rendering performance will be slightly degraded until this application is built for production. </p> ) : null} ({data.length} rows) <button onClick={refreshData}>Refresh Data</button> <div className="container" ref={tableContainerRef} style={{ overflow: 'auto', //our scrollable table container position: 'relative', //needed for sticky header height: '800px', //should be a fixed height }} > {/* Even though we're still using sematic table tags, we must use CSS grid and flexbox for dynamic row heights */} <table style={{ display: 'grid' }}> <thead style={{ display: 'grid', position: 'sticky', top: 0, zIndex: 1, }} > {table.getHeaderGroups().map((headerGroup) => ( <tr key={headerGroup.id} style={{ display: 'flex', width: '100%' }} > {headerGroup.headers.map((header) => { return ( <th key={header.id} style={{ display: 'flex', width: header.getSize(), }} > <div {...{ className: header.column.getCanSort() ? 'cursor-pointer select-none' : '', onClick: header.column.getToggleSortingHandler(), }} > {flexRender( header.column.columnDef.header, header.getContext(), )} {{ asc: ' 🔼', desc: ' 🔽', }[header.column.getIsSorted() as string] ?? null} </div> </th> ) })} </tr> ))} </thead> <TableBody table={table} tableContainerRef={tableContainerRef} /> </table> </div> </div> ) } interface TableBodyProps { table: Table<Person> tableContainerRef: React.RefObject<HTMLDivElement> } function TableBody({ table, tableContainerRef }: TableBodyProps) { const { rows } = table.getRowModel() // Important: Keep the row virtualizer in the lowest component possible to avoid unnecessary re-renders. const rowVirtualizer = useVirtualizer<HTMLDivElement, HTMLTableRowElement>({ count: rows.length, estimateSize: () => 33, //estimate row height for accurate scrollbar dragging getScrollElement: () => tableContainerRef.current, //measure dynamic row height, except in firefox because it measures table border height incorrectly measureElement: typeof window !== 'undefined' && navigator.userAgent.indexOf('Firefox') === -1 ? (element) => element?.getBoundingClientRect().height : undefined, overscan: 5, }) return ( <tbody style={{ display: 'grid', height: `${rowVirtualizer.getTotalSize()}px`, //tells scrollbar how big the table is position: 'relative', //needed for absolute positioning of rows }} > {rowVirtualizer.getVirtualItems().map((virtualRow) => { const row = rows[virtualRow.index] as Row<Person> return ( <TableBodyRow key={row.id} row={row} virtualRow={virtualRow} rowVirtualizer={rowVirtualizer} /> ) })} </tbody> ) } interface TableBodyRowProps { row: Row<Person> virtualRow: VirtualItem rowVirtualizer: Virtualizer<HTMLDivElement, HTMLTableRowElement> } function TableBodyRow({ row, virtualRow, rowVirtualizer }: TableBodyRowProps) { return ( <tr data-index={virtualRow.index} //needed for dynamic row height measurement ref={(node) => rowVirtualizer.measureElement(node)} //measure dynamic row height key={row.id} style={{ display: 'flex', position: 'absolute', transform: `translateY(${virtualRow.start}px)`, //this should always be a `style` as it changes on scroll width: '100%', }} > {row.getVisibleCells().map((cell) => { return ( <td key={cell.id} style={{ display: 'flex', width: cell.column.getSize(), }} > {flexRender(cell.column.columnDef.cell, cell.getContext())} </td> ) })} </tr> ) } const rootElement = document.getElementById('root') if (!rootElement) throw new Error('Failed to find the root element') ReactDOM.createRoot(rootElement).render( <React.StrictMode> <App /> </React.StrictMode>, )
import React from 'react' import ReactDOM from 'react-dom/client' import './index.css' import { ColumnDef, flexRender, getCoreRowModel, getSortedRowModel, Row, Table, useReactTable, } from '@tanstack/react-table' import { useVirtualizer, VirtualItem, Virtualizer, } from '@tanstack/react-virtual' import { makeData, Person } from './makeData' //This is a dynamic row height example, which is more complicated, but allows for a more realistic table. //See https://tanstack.com/virtual/v3/docs/examples/react/table for a simpler fixed row height example. function App() { const columns = React.useMemo<ColumnDef<Person>[]>( () => [ { accessorKey: 'id', header: 'ID', size: 60, }, { accessorKey: 'firstName', cell: (info) => info.getValue(), }, { accessorFn: (row) => row.lastName, id: 'lastName', cell: (info) => info.getValue(), header: () => <span>Last Name</span>, }, { accessorKey: 'age', header: () => 'Age', size: 50, }, { accessorKey: 'visits', header: () => <span>Visits</span>, size: 50, }, { accessorKey: 'status', header: 'Status', }, { accessorKey: 'progress', header: 'Profile Progress', size: 80, }, { accessorKey: 'createdAt', header: 'Created At', cell: (info) => info.getValue<Date>().toLocaleString(), size: 250, }, ], [], ) // The virtualizer will need a reference to the scrollable container element const tableContainerRef = React.useRef<HTMLDivElement>(null) const [data, setData] = React.useState(() => makeData(50_000)) const refreshData = React.useCallback(() => { setData(makeData(50_000)) }, []) const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), debugTable: true, }) // All important CSS styles are included as inline styles for this example. This is not recommended for your code. return ( <div className="app"> {process.env.NODE_ENV === 'development' ? ( <p> <strong>Notice:</strong> You are currently running React in development mode. Virtualized rendering performance will be slightly degraded until this application is built for production. </p> ) : null} ({data.length} rows) <button onClick={refreshData}>Refresh Data</button> <div className="container" ref={tableContainerRef} style={{ overflow: 'auto', //our scrollable table container position: 'relative', //needed for sticky header height: '800px', //should be a fixed height }} > {/* Even though we're still using sematic table tags, we must use CSS grid and flexbox for dynamic row heights */} <table style={{ display: 'grid' }}> <thead style={{ display: 'grid', position: 'sticky', top: 0, zIndex: 1, }} > {table.getHeaderGroups().map((headerGroup) => ( <tr key={headerGroup.id} style={{ display: 'flex', width: '100%' }} > {headerGroup.headers.map((header) => { return ( <th key={header.id} style={{ display: 'flex', width: header.getSize(), }} > <div {...{ className: header.column.getCanSort() ? 'cursor-pointer select-none' : '', onClick: header.column.getToggleSortingHandler(), }} > {flexRender( header.column.columnDef.header, header.getContext(), )} {{ asc: ' 🔼', desc: ' 🔽', }[header.column.getIsSorted() as string] ?? null} </div> </th> ) })} </tr> ))} </thead> <TableBody table={table} tableContainerRef={tableContainerRef} /> </table> </div> </div> ) } interface TableBodyProps { table: Table<Person> tableContainerRef: React.RefObject<HTMLDivElement> } function TableBody({ table, tableContainerRef }: TableBodyProps) { const { rows } = table.getRowModel() // Important: Keep the row virtualizer in the lowest component possible to avoid unnecessary re-renders. const rowVirtualizer = useVirtualizer<HTMLDivElement, HTMLTableRowElement>({ count: rows.length, estimateSize: () => 33, //estimate row height for accurate scrollbar dragging getScrollElement: () => tableContainerRef.current, //measure dynamic row height, except in firefox because it measures table border height incorrectly measureElement: typeof window !== 'undefined' && navigator.userAgent.indexOf('Firefox') === -1 ? (element) => element?.getBoundingClientRect().height : undefined, overscan: 5, }) return ( <tbody style={{ display: 'grid', height: `${rowVirtualizer.getTotalSize()}px`, //tells scrollbar how big the table is position: 'relative', //needed for absolute positioning of rows }} > {rowVirtualizer.getVirtualItems().map((virtualRow) => { const row = rows[virtualRow.index] as Row<Person> return ( <TableBodyRow key={row.id} row={row} virtualRow={virtualRow} rowVirtualizer={rowVirtualizer} /> ) })} </tbody> ) } interface TableBodyRowProps { row: Row<Person> virtualRow: VirtualItem rowVirtualizer: Virtualizer<HTMLDivElement, HTMLTableRowElement> } function TableBodyRow({ row, virtualRow, rowVirtualizer }: TableBodyRowProps) { return ( <tr data-index={virtualRow.index} //needed for dynamic row height measurement ref={(node) => rowVirtualizer.measureElement(node)} //measure dynamic row height key={row.id} style={{ display: 'flex', position: 'absolute', transform: `translateY(${virtualRow.start}px)`, //this should always be a `style` as it changes on scroll width: '100%', }} > {row.getVisibleCells().map((cell) => { return ( <td key={cell.id} style={{ display: 'flex', width: cell.column.getSize(), }} > {flexRender(cell.column.columnDef.cell, cell.getContext())} </td> ) })} </tr> ) } const rootElement = document.getElementById('root') if (!rootElement) throw new Error('Failed to find the root element') ReactDOM.createRoot(rootElement).render( <React.StrictMode> <App /> </React.StrictMode>, )