In the previous article, we implemented a Data table capable to display various data formats using TanStack table.
We tested our table with a dataset of 100 rows. But what if we increase this number to 1,000 or even 10,000? Eventually, we will hit the browser's rendering limitations. The ability to display and operate on large datasets is crucial for the Data Table. That's why we will implement render virtualization.
What is virtualization?
Virtualization is a technique used to improve the performance of rendering large datasets by only rendering the visible rows and a small buffer around them. This reduces the number of DOM elements and improves the overall performance and responsiveness of the table.
Here is the demo of Data table with more 30 thousands rows scrolling. We are going to use @tanstack/react-virtual library to implement this.
Implement virtual table
In order to keep code organized, we will create folder src/DataTable/features
. Here will be the place for the logic responsible for Data table features. We will implement this as React hooks.
Virtualizer React hook
Here is a custom hook using TanStack virtualizer. We also create a workaround for sticky header compatibility.
import { notUndefined, useVirtualizer } from '@tanstack/react-virtual';
import type { MutableRefObject } from 'react';
// Cell height needs to be consistent for each row
const CELL_HEIGHT = 31;
// Number of rows to render before and after viewport.
const OVERSCAN = 6;
export type Props = {
/** Total number of rows */
rowsCount: number;
/** Reference to the table container element, which has scroll */
scrollRef: MutableRefObject<HTMLElement | null>;
};
export const useVirtualRows = ({ rowsCount, scrollRef }: Props) => {
const virtualizer = useVirtualizer({
count: rowsCount,
getScrollElement: () => scrollRef.current,
estimateSize: () => CELL_HEIGHT,
overscan: OVERSCAN,
});
// This will replace "real" rows for rendering
const virtualRows = virtualizer.getVirtualItems();
/**
* This is required to fix the issue with sticky table header rendering.
* @see DataTable
*/
const [before, after] =
virtualRows.length > 0
? [
notUndefined(virtualRows[0]).start - virtualizer.options.scrollMargin,
virtualizer.getTotalSize() -
notUndefined(virtualRows[virtualRows.length - 1]).end,
]
: [0, 0];
return { virtualRows, before, after };
};
DataTable component adjustments
Now we change DataTable
component to use virtual rows.
const DataTable: FC<Props> = ({ tableData }) => {
//...
/* Virtualizer logic start */
const scrollRef = useRef<HTMLDivElement>(null);
const { rows } = table.getRowModel();
const { before, after, virtualRows } = useVirtualRows({
scrollRef,
rowsCount: rows.length,
});
/* Virtualizer logic end */
return (
//...
<tbody>
<Fragment>
{/* Fix the issue with a sticky table header and infinite scroll */}
{before > 0 && (
<tr>
<td colSpan={columns.length} style={{height: before}} />
</tr>
)}
{virtualRows.map((virtualRow) => {
// this is the "real" current row
const row = rows[virtualRow.index];
return (
<tr key={row.id}>
{row.getVisibleCells().map((cell) => /*...*/)}
</tr>
);
})}
{/* Fix the issue with a sticky table header and infinite scroll */}
{after > 0 && (
<tr>
<td colSpan={columns.length} style={{height: after}} />
</tr>
)}
</Fragment>
</tbody>
);
};
Working demo
So, now we can set rows' amount to 33333 and see what happens.
To be continued...
Top comments (0)