QUI React Table

Virtualized Rows

(0 rows)
ID
First Name
Last Name
Age
Visits
Status
Profile Progress
Created At
import {useEffect, useMemo, useRef, useState} from "react"
import {useVirtualizer} from "@tanstack/react-virtual"
import {ArrowDown} from "lucide-react"
import {clsx} from "@qui/base"
import {QIcon} from "@qui/react"
import {
ColumnDef,
flexRender,
getCoreRowModel,
getSortedRowModel,
QTable,
QTbody,
QTd,
QTh,
QThead,
QTr,
Row,
useReactTable,
} from "@qui/react-table"
import {makeData, Person} from "./make-data"
// This is a dynamic row height example, which is more complicated, but allows for a
// more realistic table.
export default function VirtualizedRows() {
const columns = useMemo<ColumnDef<Person>[]>(
() => [
{
accessorKey: "id",
header: "ID",
size: 60,
},
{
accessorKey: "firstName",
cell: (info) => info.getValue(),
header: "First Name",
},
{
accessorFn: (row) => row.lastName,
cell: (info) => info.getValue(),
header: "Last Name",
id: "lastName",
},
{
accessorKey: "age",
header: "Age",
size: 80,
},
{
accessorKey: "visits",
header: "Visits",
size: 80,
},
{
accessorKey: "status",
header: "Status",
},
{
accessorKey: "progress",
header: "Profile Progress",
minSize: 120,
},
{
accessorKey: "createdAt",
cell: (info) => info.getValue<Date>().toLocaleString(),
header: "Created At",
size: 200,
},
],
[],
)
const [data, setData] = useState<Person[]>([])
useEffect(() => {
setData(makeData(30000))
}, [])
const table = useReactTable({
columns,
data,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
})
const {rows} = table.getRowModel()
// The virtualizer needs to know the scrollable container element
const tableContainerRef = useRef<HTMLDivElement>(null)
const rowVirtualizer = useVirtualizer({
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 (
<div className="overflow-x-auto">
({data.length} rows)
<div
ref={tableContainerRef}
className="relative mt-2 h-[800px] overflow-auto"
>
{/*
* Even though we're still using semantic table tags, we must use
* CSS grid and flexbox for dynamic row heights
*/}
<QTable className="grid table-fixed border-0">
<QThead className="sticky top-0 z-10 grid bg-[var(--q-background-1)]">
{table.getHeaderGroups().map((headerGroup) => (
<QTr
key={headerGroup.id}
className="flex w-full"
style={{borderBottom: "solid 1px var(--q-border-1-subtle)"}}
>
{headerGroup.headers.map((header) => {
if (header.isPlaceholder) {
return (
<QTh
key={header.id}
colSpan={header.colSpan}
style={{width: header.getSize()}}
/>
)
}
const sorted = header.column.getIsSorted()
return (
<QTh
key={header.id}
colSpan={header.colSpan}
onClick={header.column.getToggleSortingHandler()}
style={{width: header.getSize()}}
>
<div className="inline-flex h-full items-center gap-2">
{flexRender(
header.column.columnDef.header,
header.getContext(),
)}
{sorted ? (
<QIcon
className={clsx(
"transition-[transform] ease-in-out",
{
"rotate-180": sorted === "asc",
},
)}
icon={ArrowDown}
size="s"
/>
) : null}
</div>
</QTh>
)
})}
</QTr>
))}
</QThead>
<QTbody
className="relative grid"
style={{
height: `${rowVirtualizer.getTotalSize()}px`, // tells scrollbar how big the table is
}}
>
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
const row = rows[virtualRow.index] as Row<Person>
return (
<QTr
key={row.id}
ref={(node) => rowVirtualizer.measureElement(node)} // measure dynamic row height
className="absolute flex w-full border-l-[1px]"
data-index={virtualRow.index} // needed for dynamic row height measurement
style={{
borderColor: "var(--q-border-1-subtle)",
transform: `translateY(${virtualRow.start}px)`, // this should always be a `style` as it changes on scroll
}}
>
{row.getVisibleCells().map((cell) => {
return (
<QTd
key={cell.id}
className="flex"
style={{
width: cell.column.getSize(),
}}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</QTd>
)
})}
</QTr>
)
})}
</QTbody>
</QTable>
</div>
</div>
)
}