loading

v33.3.1

select option

useTableVirtualizer hook

Virtualize NewTable for better performance

useTableVirtualizer is a headless utility for virtualizing instances of NewTable. It generates the necessary props in order to fully virtualize instances of NewTable with minimal extra code required.

Demo

Demo table with 10.000 rows. See it in Playroom

Core concepts

Virtualization is the process of selectively rendering a portion of the visible user interface to avoid the performance cost of rendering elements that will not be visible to the user.

For NewTable, this is achieved using two separate tools that work together: the useTableVirtualizer hook, and the NewTable.VirtualWrapper component.

Limitations

Supported row and types

The following NewTable row types are not supported:

  • NewTable.Twiddle
  • NewTable.Foot

Frozen columns

Tables with frozen columns (cells with freeze) only make sense in tables with horizontal scrolling. To ensure your table is able to scroll horizontally, use absolute (px) column widths that add up to higher than the horizontal size of your container.

NewTable.Row with preset="checkbox"

Checkbox cells (<NewTable.Row preset="checkbox">) have a built-in default width of 40px. This is problematic for virtualized tables if you're defining your column widths as percentages. When using checkbox cells, you'll have to pick a width (usually around 5-10%) that doesn't clip the checkbox in different screen sizes, or use absolute (px) column widths.

Tip: assigning the width values for the different columns to an object and referencing that object in both the head and body props will make adjusting these widths a much less frustrating experience.

Most other ink components and table cell types should work out of the box. If you find any issues or you'd like to take a crack at adding support to Twiddle or Footer rows, please reach out in #ink!

Prerequisites

Your table will need to have:

  • Fixed column widths with a CSS measurement unit such as a % of 100%, or a fixed px value.
    • Use px if your table has horizontal scroll.
    • Use % if your widths add up to 100%.
  • A fixed height value in px.

Tutorial

In order to virtualize an existing instance of NewTable, perform the following steps. This guide will assume your table renders one row per item in an array of objects called data - adjust accordingly.

1. Set fixed column widths

For each table cell element (NewTable.HeadCell and NewTable.Cell), give it a fixed width represented as a percentage. Make sure widhts add up to 100%. Since not all table rows are rendered to the DOM simultaneously, the browser can't calculate column widths proportional to their content.

<NewTable.Head>
<NewTable.Row>
- <NewTable.HeadCell>ID</NewTable.HeadCell>
- <NewTable.HeadCell>Stakeholder</NewTable.HeadCell>
- <NewTable.HeadCell>Security</NewTable.HeadCell>
+ <NewTable.HeadCell width="20%">ID</NewTable.HeadCell>
+ <NewTable.HeadCell width="40%">Stakeholder</NewTable.HeadCell>
+ <NewTable.HeadCell width="40%">Security</NewTable.HeadCell>
</NewTable.Row>
</NewTable.Head>
<NewTable.Body>
{data.map(stakeholder => (
<NewTable.Row>
- <NewTable.Cell>{stakeholder.id}</NewTable.Cell>
- <NewTable.Cell>{stakeholder.name}</NewTable.Cell>
- <NewTable.Cell>{stakeholder.security}</NewTable.Cell>
+ <NewTable.Cell width="20%">{stakeholder.id}</NewTable.Cell>
+ <NewTable.Cell width="40%">{stakeholder.name}</NewTable.Cell>
+ <NewTable.Cell width="40%">{stakeholder.security}</NewTable.Cell>
</NewTable.Row>
))}
</NewTable.Body>

2. Add virtual wrapper

Wrap NewTable with NewTable.VirtualWrapper. If your NewTable already had a height prop, move the prop to NewTable.VirtualWrapper. If not, you'll have to set one, as virtualization relies on the presence of a scrolling container.

NewTable.VirtualWrapper is the container and context provider that will allow NewTable and its child components to know they're being rendered in a virtualized environment and adjust their styles accordingly.

return (
+ <NewTable.VirtualWrapper height="800px">
- <NewTable height="800px">
+ <NewTable>
{...}
</NewTable>
+ </NewTable.VirtualWrapper>
)

At this stage you'll start getting runtime exceptions thrown from some of NewTable's child components. We'll address those in the next step!

3. Instantiate the virtualizer hook

The virtualizer interface contains functions that generates the necessary NewTable.VirtualWrapper, NewTable.Body, and NewTable.Row props that allow virtualization to work, getWrapperProps, getBodyProps, and getRowProps, respectively.

const virtualizer = useTableVirtualizer(data)

By mapping over .getVirtualItems, we're able to tell the the component which rows are visible and should be rendered. We're also changing the .map callback from an implicit-return arrow function and add braces so we can use virtualizer.getRowItem to retrieve that row's actual data array element.

Note: If your NewTable has a density value different from the default "normal", you should provide it as e.g.:

const virtualizer = useTableVirtualizer(data, { density: "higher" })

All done:

return (
- <NewTable.VirtualWrapper height="800px">
+ <NewTable.VirtualWrapper {...virtualizer.getWrapperProps()} height="800px">
<NewTable>
@@ ... @@
- <NewTable.Body>
+ <NewTable.Body {...virtualizer.getBodyProps()}>
- {data.map(stakeholder => (
+ {virtualizer.getVirtualItems().map(virtualRow => {
- <NewTable.Row>
+ const stakeholder = virtualizer.getRowItem(virtualRow);
+ return (
+ <NewTable.Row {...virtualizer.getRowProps(virtualRow)}>
@@ ... @@
</NewTable.Row>
+ );
})}
</NewTable.Body>
</NewTable>
</NewTable.VirtualWrapper>

API

const useTableVirtualizer: <DataItem>(
data: DataItem[],
virtualizerOptions?: Options | undefined,
): Virtualizer

useTableVirtualizer is built with @tanstack/react-virtual and supports almost all of its arguments as its optional second argument virtualizerOptions, with the exception of the following (as they're provided by us):

  • count
  • estimateSize
  • getScrollElement

Note: horizontal virtualization is not supported.

Extra options

The following options come from the @tanstack/react-virtual API are not required, but might be useful:

overscan

Sets the number of rows to render off-screen. This is useful if your table rows are "popping in" while scrolling. Developers should experiment with different values. Make note of how many rows are visibe at any given time on your table, and set a number between 0.5x and 1.5x that amount as a start.

The default value for overscan is 5.

See the overscan docs here.

initialRect and observeElementRect

These options are useful mostly in testing and SSR environments. The JSDom environment in which Jest/React Testing Library tests runs does not support DOM features that the virtualizer relies on. This will cause your scrolling container (NewTable.Body) to render with a height of 0 unless a pair of { width, height } dimensions is provided as the initialRect argument, and/or as a value to the observeElementRect callback. For a practical example, see the useTableVirtualizer tests here.

See the initialRect docs here and the observeElementRect docs here.

Virtualizer

The virtualizer return value from useTableVirtualizer includes all original attributes from @tanstack/react-virtual's API, plus the following ink-specific methods:

MethodDescription
getWrapperPropsCall and destructure its returned object on <NewTable.VirtualWrapper>.
getBodyPropsCall and destructure its returned object on <NewTable.Head>.
getRowPropsCall with virtualRow and destructure its returned object on each <NewTable.Row>.
getRowItemCall with virtualRow to retrieve the virtual row's corresponding data item.

Is this page helpful?