Row Selection
InfiniteTable offers support for both single and multiple row selection. For selecting cells, see the Cell Selection page. Configure the selection mode on the DataSource component
<DataSource selectionMode="single-row" />
// can be "single-row", "multi-row", "multi-cell" or false COPY
Multiple row selection allows people to select rows just like they would in their MacOS Finder app, by clicking desired rows and using the cmd/shift keys as modifiers.
The DataGrid also offers support for checkbox selection, which is another easy way of interacting with grid rows, especially when grouped or nested data is used.
Row selection (both single and multiple) is driven by the
rowSelection prop, which will contain primary keys of the selected rows.The value or values you specify for row selection are primary keys of the rows in the DataGrid.
Row selection is defined on the
DataSource component, so that's where you specify your rowSelection prop (or the uncontrolled version of it, namely defaultRowSelection and also the callback prop of onRowSelectionChange).You can explicitly specify the
selectionMode as "single-row" or "multi-row" (or false) but it will generally be derived from the value of your rowSelection/defaultRowSelection prop.Single Row Selection#
This is the most basic row selection - in this case the
rowSelection prop (or the uncontrolled variant defaultRowSelection) will be the primary key of the selected row (a string or a number or null for no selection).<DataSource<DATA_TYPE> primaryKey="id" data={[...]} defaultRowSelection={4} > <InfiniteTable {...} /> </DataTable>
COPY
Single row selection example - click a row to see selection change. You can also use your keyboard - press the spacebar to select/deselect a row.
View Mode
Fork Forkimport { InfiniteTable, DataSource } from '@infinite-table/infinite-react'; import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react'; import * as React from 'react'; const columns: InfiniteTablePropColumns<Developer> = { country: { field: 'country', }, firstName: { field: 'firstName', }, stack: { field: 'stack', }, age: { field: 'age' }, id: { field: 'id' }, salary: { field: 'salary', type: 'number', }, canDesign: { field: 'canDesign' }, }; export default function App() { return ( <DataSource<Developer> data={dataSource} defaultRowSelection={3} primaryKey="id" > <InfiniteTable<Developer> debugId="default-single-row-selection-example" columns={columns} columnDefaultWidth={150} /> </DataSource> ); } const dataSource = () => { return fetch('https://data.infinite-table.com' + '/developers100') .then((r) => r.json()) .then((data: Developer[]) => data); }; type Developer = { id: number; firstName: string; lastName: string; country: string; city: string; currency: string; email: string; preferredLanguage: string; stack: string; canDesign: 'yes' | 'no'; hobby: string; salary: number; age: number; };
Row selection is changed when the user clicks a row. Clicking a row selects it and clicking it again keeps the row selected. For deselecting the row with the mouse use
cmd/ctrl + click.With single row selection and grouped data, clicking a group row doesn't change the selection - only normal rows can be selected.
Keybord support#
You can also use your keyboard to select a row, as by default,
keyboardSelection is true. Using your keyboard, navigate to the desired row and then press the spacebar to select it. Pressing the spacebar again on the selected row will deselect it. Pressing the spacebar on a group row doesn't change the selection.Both
cell and row keyboardNavigation are available and you can use either of them to perform row selection.Controlled single row selection#
Row selection can be used as a controlled or uncontrolled property. For the controlled version, make sure you also define your
onRowSelectionChange callback prop to update the selection.This example uses
onRowSelectionChange callback prop to update the controlled rowSelectionView Mode
Fork Forkimport { InfiniteTable, DataSource } from '@infinite-table/infinite-react'; import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react'; import * as React from 'react'; import { useState } from 'react'; const columns: InfiniteTablePropColumns<Developer> = { country: { field: 'country', }, firstName: { field: 'firstName', }, stack: { field: 'stack', }, age: { field: 'age' }, id: { field: 'id' }, salary: { field: 'salary', type: 'number', }, canDesign: { field: 'canDesign' }, }; export default function App() { const [rowSelection, setRowSelection] = useState<number | null | string>(3); return ( <> <p style={{ color: 'var(--infinite-cell-color)', padding: 10, }} > Current row selection: <code style={{ display: 'inline-block' }}> <pre> {JSON.stringify(rowSelection)}.</pre> </code> </p> <DataSource<Developer> data={dataSource} rowSelection={rowSelection} onRowSelectionChange={setRowSelection} primaryKey="id" > <InfiniteTable<Developer> debugId="controlled-single-row-selection-example" columns={columns} columnDefaultWidth={150} /> </DataSource> </> ); } const dataSource = () => { return fetch('https://data.infinite-table.com' + '/developers100') .then((r) => r.json()) .then((data: Developer[]) => data); }; type Developer = { id
Multi Row Selection#
You can configure multiple selection for rows so users can interact with it through clicking around or via a checkbox selection column.
Choosing how users select rows#
The
multiRowSelectionWith prop decides whether clicking a row selects it:'checkbox'(the default) - while a visible column shows a selection checkbox (seerenderSelectionCheckBox), users select rows with the checkboxes and the spacebar. Clicking a row only makes it the active row (or cell) - it doesn't change the selection. When no visible column shows a selection checkbox, it works like'click'.'click'- clicking a row selects it, withcmd/ctrlandshiftas modifier keys. The selection checkboxes, if any, work as well.
<DataSource selectionMode="multi-row" {...}> <InfiniteTable multiRowSelectionWith="click" columns={columnsWithACheckboxColumn} /> </DataSource>
COPY
Both values work the same with or without grouping, and with tree data. In both cases, users can also select rows with the keyboard, and all the gestures (clicks, checkbox clicks and the spacebar) share the same starting point for
shift ranges.Using your mouse and keyboard to select rows#
When there is no selection checkbox (or with
multiRowSelectionWith="click"), users select rows via click or click + cmd/ctrl and shift keys, just like they are used to in their native Finder/Explorer applications.Mouse interactions#
For selecting with the mouse, the following gestures are supported (we tried to exactly replicate the logic/behaviour from MacOS Finder app, so most people should find it really intuitive):
- clicking a row (with no modifier keys) will select that row, while clearing any existing selection
- click +
cmd/ctrlmodifier key will toggle the selection for the clicked row while keeping any other existing selection. So if the row was not selected, it's being added to the current selection, while if the row was already selected, it's being removed from the selection - click +
shiftmodifier key will perform a multi selection, starting from the last selection click where theshiftkey was not used.
When the data is grouped, a group row is selected as a whole: clicking it selects all the rows in the group (and in its nested groups), and
cmd/ctrl + click toggles the whole group. Clicking the expand/collapse icon of a group row doesn't change the selection.Use your mouse to select multiple rows. Expect click and click +
cmd/ctrl/shift modifier keys to behave just like they are in the MacOS Finder app.View Mode
Fork Forkimport { InfiniteTable, DataSource } from '@infinite-table/infinite-react'; import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react'; import * as React from 'react'; const columns: InfiniteTablePropColumns<Developer> = { country: { field: 'country', }, firstName: { field: 'firstName', }, stack: { field: 'stack', }, age: { field: 'age' }, id: { field: 'id' }, salary: { field: 'salary', type: 'number', }, canDesign: { field: 'canDesign' }, }; export default function App() { return ( <DataSource<Developer> data={dataSource} selectionMode="multi-row" primaryKey="id" > <InfiniteTable<Developer> debugId="default-selection-mode-multi-row-example" columns={columns} columnDefaultWidth={150} /> </DataSource> ); } const dataSource = () => { return fetch('https://data.infinite-table.com' + '/developers100') .then((r) => r.json()) .then((data: Developer[]) => data); }; type Developer = { id: number; firstName: string; lastName: string; country: string; city: string; currency: string; email: string; preferredLanguage: string; stack: string; canDesign: 'yes' | 'no'; hobby: string; salary: number; age: number; };
Selecting a range of rows with groups#
When the data is grouped, a
shift range covers the rows as they are displayed, from the starting row to the clicked row, in either direction:- normal rows are selected;
- a collapsed group row selects its whole group, including its nested groups;
- an expanded group row follows its children - only its rows inside the range are selected, so the group shows as selected when all its rows are in the range, and as partly selected when only some of them are. This is also the case when you
shift+ click an expanded group row: its rows are below it, so going down to it doesn't select any of them; - the starting row keeps what its own click selected - when it's a group row, its whole group stays selected;
- a new
shift+ click from the same starting row replaces the range selected by the previous one (rows outside the range keep their selection), just like in the Finder app.
So a wider range never selects fewer rows.
For example, with the rows grouped by language and the
Go group collapsed:TypeScript
TS 1
TS 2 <- click
TS 3
Go (collapsed - Go 1, Go 2)
Rust
Rust 1
Rust 2 <- shift + click
Rust 3
clicking
TS 2 and then shift + clicking Rust 2 selects TS 2, TS 3, all of Go (Go 1 and Go 2), Rust 1 and Rust 2, but not Rust 3. TypeScript and Rust are partly selected, while Go is selected.A partly selected group or tree row is painted with a lighter selection background, halfway between the row background and the selected row background. A selection checkbox on that row shows the indeterminate state. The same applies when some rows of the group are hidden by a filter and
multiRowSelectionTarget is 'all'.If you then
shift + click the Rust group row instead, the range ends just above the rows of Rust, so it selects TS 2, TS 3 and all of Go, while Rust is not selected. And clicking the Rust group row first, then shift + clicking TS 3, selects TS 3, all of Go and all of Rust - the starting group stays selected as a whole.The same rules apply to
shift + spacebar and to shift + clicking a selection checkbox.Click
TS 2, then shift + click Rust 2 - Go is collapsed, so all its rows are selected. Then shift + click the Rust group row - the range now ends above the rows of Rust.Switch
multiRowSelectionWith to 'checkbox' to select with the checkboxes in the group column instead.View Mode
Fork Forkimport { InfiniteTable, DataSource, GroupRowsState, InfiniteTableApi, InfiniteTablePropMultiRowSelectionWith, } from '@infinite-table/infinite-react'; import type { InfiniteTablePropColumns, DataSourcePropGroupBy, } from '@infinite-table/infinite-react'; import * as React from 'react'; type Developer = { id: number; name: string; language: string; }; const data: Developer[] = [ { id: 1, name: 'TS 1', language: 'TypeScript' }, { id: 2, name: 'TS 2', language: 'TypeScript' }, { id: 3, name: 'TS 3', language: 'TypeScript' }, { id: 4, name: 'Go 1', language: 'Go' }, { id: 5, name: 'Go 2', language: 'Go' }, { id: 6, name: 'Rust 1', language: 'Rust' }, { id: 7, name: 'Rust 2', language: 'Rust' }, { id: 8, name: 'Rust 3', language: 'Rust' }, ]; const columns: InfiniteTablePropColumns<Developer> = { name: { field: 'name' }, language: { field: 'language' }, }; const groupBy: DataSourcePropGroupBy<Developer> = [{ field: 'language' }]; // the Go group starts collapsed const defaultGroupRowsState = new GroupRowsState({ expandedRows: true, collapsedRows: [['Go']], }); export default function App() { const [selectWith, setSelectWith] = React.useState<InfiniteTablePropMultiRowSelectionWith>('click'); const [api, setApi] = React.useState<InfiniteTableApi<Developer> | null>( null, ); const [selected, setSelected] = React.useState<string[]>([]); return ( <> <div style={{ color: 'var(--infinite-cell-color)', padding: 10, display: 'flex', gap: 20, alignItems: 'center', }} > <label> multiRowSelectionWith:{' '} <select value={selectWith} onChange={(e) => setSelectWith( e.target.value as InfiniteTablePropMultiRowSelectionWith, ) } > <option value="click">click</option> <option value="checkbox">checkbox</option> </select> </label> <span>Selected: {selected.join(', ') || 'none'}</span> </div> <DataSource<Developer> data={data} primaryKey="id" selectionMode="multi-row" groupBy={groupBy} defaultGroupRowsState={defaultGroupRowsState} onRowSelectionChange={(rowSelection) => { const ids = api?.rowSelectionApi.getSelectedPrimaryKeys(rowSelection); setSelected( data.filter((d) => ids?.includes(d.id)).map((d) => d.name), ); }} > <InfiniteTable<Developer> columns={columns} columnDefaultWidth={150} groupRenderStrategy="single-column" multiRowSelectionWith={selectWith} onReady={({ api }) => setApi(api)} /> </DataSource> </>
Keyboard interactions#
By default
keyboardSelection is enabled, so you can use the spacebar key to select multiple rows. Using the spacebar key is equivalent to doing a mouse click, so expect the combination of spacebar + cmd/ctrl/shift modifier keys to behave just like clicking + the same modifier keys. This also works for group rows.Use spacebar + optional
cmd/ctrl/shift modifier keys just like you would do clicking + the same modifier keys.View Mode
Fork Forkimport { InfiniteTable, DataSource } from '@infinite-table/infinite-react'; import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react'; import * as React from 'react'; import { useState } from 'react'; const columns: InfiniteTablePropColumns<Developer> = { country: { field: 'country', }, firstName: { field: 'firstName', }, stack: { field: 'stack', }, age: { field: 'age' }, id: { field: 'id' }, salary: { field: 'salary', type: 'number', }, canDesign: { field: 'canDesign' }, }; export default function App() { const [keyboardSelection, setKeyboardSelection] = useState(true); return ( <> <div style={{ color: 'var(--infinite-cell-color)', padding: 10, }} > Keyboard selection is now{' '} <b>{keyboardSelection ? 'enabled' : 'disabled'}</b>. <button style={{ border: '1px solid tomato', display: 'block', padding: 5, marginTop: 10, }} onClick={() => setKeyboardSelection((x) => !x)} > Click to toggle keyboard selection </button> </div> <DataSource<Developer> data={dataSource} selectionMode="multi-row" primaryKey="id" > <InfiniteTable<Developer> debugId="default-selection-mode-multi-row-keyboard-toggle-example" keyboardSelection={keyboardSelection} columns={columns} columnDefaultWidth={150} /> </DataSource> </> ); } const dataSource = () => { return fetch('https://data.infinite-table.com' + '/developers100') .then((r) => r.json()) .then((data: Developer[]) => data); }; type Developer = { id
For selecting all the rows in the table, you can use
cmd/ctrl + A keyboard shortcut.Using a selection checkbox#
Selection multiple rows is made easier when there is a checkbox column and even-more-so when there is grouping.
With the default
multiRowSelectionWith ('checkbox'), once a visible column shows a selection checkbox, clicking a row no longer selects it - users select rows with the checkboxes and the spacebar. Use multiRowSelectionWith="click" to keep selecting rows by clicking them as well.Configuring checkbox selection is as easy as specifying
renderSelectionCheckBox on any of the columns in the grid. renderSelectionCheckBox can either be the boolean true or a render function that allows the customization of the selection checkbox.const columns: InfiniteTablePropColumns<Developer> = { id: { field: 'id', defaultWidth: 80, }, country: { // show the selection checkbox for this column renderSelectionCheckBox: true, field: 'country', }, firstName: { field: 'firstName', }, };
COPY
Any column can show a selection checkbox if
column.renderSelectionCheckBox is set to true.There is nothing prevening you from providing multiple checkbox columns.
Use the selection checkboxes to select rows. You can also use the spacebar key (+ optional shift modifier) to modify the selection
View Mode
Fork Forkimport { InfiniteTable, DataSource } from '@infinite-table/infinite-react'; import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react'; import * as React from 'react'; const columns: InfiniteTablePropColumns<Developer> = { id: { field: 'id', renderSelectionCheckBox: true, defaultWidth: 80, }, country: { field: 'country', }, firstName: { field: 'firstName', }, stack: { field: 'stack', }, age: { field: 'age', defaultWidth: 80, type: 'number' }, salary: { field: 'salary', type: 'number', }, canDesign: { field: 'canDesign' }, }; export default function App() { return ( <DataSource<Developer> data={dataSource} selectionMode="multi-row" primaryKey="id" > <InfiniteTable<Developer> debugId="default-checkbox-selection-multi-row-example" columns={columns} columnDefaultWidth={150} /> </DataSource> ); } const dataSource = () => { return fetch('https://data.infinite-table.com' + '/developers100') .then((r) => r.json()) .then((data: Developer[]) => data); }; type Developer = { id: number; firstName: string; lastName: string; country: string; city: string; currency: string; email: string; preferredLanguage: string; stack: string; canDesign: 'yes' | 'no'; hobby: string; salary: number; age: number; };
Mouse interactions#
The mouse interactions are the obvious ones you would expect from checkbox selection. Clicking a checkbox will toggle the selection for the correspondign row (or for the whole group, on a group row). Also, clicking the header checkbox will select/deselect all the rows in the table. The selection checkbox in the column header can be in an indeterminate state (when just some of the rows are selected), and when clicking it, it will become checked and select all rows.
shift + clicking a checkbox selects the range of rows from the last checkbox clicked (or the last row selected with the spacebar) to the clicked row - see selecting a range of rows with groups for how group rows are handled.You can use
renderHeaderSelectionCheckBox for a column to customize the checkbox in the column header. If no header selection checkbox is specified, renderSelectionCheckBox will be used for the column header as well, just like it's used for grid rows.Keyboard interactions#
When multi-row selection is configured to use checkboxes, you can still use your keyboard to select rows. Navigate to the desired row (you can have
keyboard navigation active for either cells or rows) and press the spacebar. If the row (or group) is not selected it will select it, otherwise it will deselect it - just like clicking its checkbox.The only supported modifier key when selecting a row by pressing spacebar is the
shift key - it allows users to extend the selection over multiple rows, which is handy. Checkbox clicks and the spacebar share the starting row of the range, so you can click a checkbox and then press shift + spacebar on another row, or the other way around.Selecting all rows when the data is filtered#
When some rows are filtered out, use the
multiRowSelectionTarget prop to decide which rows the header checkbox, the group row checkboxes and selectAll/deselectAll act on:'all'(the default) - they act on all the rows in the data (or in the group), including the rows that are filtered out. The header and group checkboxes are only checked when all those rows are selected.'visible'- they act only on the rows that pass the filter. Rows that are filtered out keep their current selection. The header and group checkboxes are checked when all the visible rows are selected.
<DataSource<Developer> primaryKey="id" selectionMode="multi-row" multiRowSelectionTarget="visible" filterFunction={({ data }) => data.preferredLanguage === 'TypeScript'} />
COPY
When no rows are filtered out, both values behave the same.
The data is grouped by
stack and filtered to only show TypeScript developers.Click the header checkbox, a group checkbox or the "Select all" button, then remove the filter, to see which rows were selected with each
multiRowSelectionTarget value.View Mode
Fork Forkimport { InfiniteTable, DataSource, InfiniteTableApi, DataSourcePropMultiRowSelectionTarget, } from '@infinite-table/infinite-react'; import type { InfiniteTablePropColumns, DataSourcePropGroupBy, } from '@infinite-table/infinite-react'; import * as React from 'react'; const columns: InfiniteTablePropColumns<Developer> = { checkbox: { renderSelectionCheckBox: true, defaultWidth: 70, }, id: { field: 'id', defaultWidth: 80 }, firstName: { field: 'firstName' }, preferredLanguage: { field: 'preferredLanguage' }, stack: { field: 'stack' }, }; const groupBy: DataSourcePropGroupBy<Developer> = [{ field: 'stack' }]; const filterFunction = ({ data }: { data: Developer }) => data.preferredLanguage === 'TypeScript'; export default function App() { const [target, setTarget] = React.useState<DataSourcePropMultiRowSelectionTarget>('visible'); const [filtered, setFiltered] = React.useState(true); const [api, setApi] = React.useState<InfiniteTableApi<Developer> | null>( null, ); const [selectedCount, setSelectedCount] = React.useState(0); return ( <> <div style={{ color: 'var(--infinite-cell-color)', padding: 10, display: 'flex', gap: 20, alignItems: 'center', }} > <label> multiRowSelectionTarget:{' '} <select value={target} onChange={(e) => setTarget(e.target.value as DataSourcePropMultiRowSelectionTarget) } > <option value="all">all</option> <option value="visible">visible</option> </select> </label> <label> <input type="checkbox" checked={filtered} onChange={(e) => setFiltered(e.target.checked)} />{' '} Only show TypeScript developers </label> <button onClick={() => api?.rowSelectionApi.selectAll()}> Select all </button> <span>Selected rows (including filtered out): {selectedCount}</span> </div> <DataSource<Developer> data={dataSource} primaryKey="id" selectionMode="multi-row" multiRowSelectionTarget={target} groupBy={groupBy} filterFunction={filtered ? filterFunction : undefined} onRowSelectionChange={(rowSelection) => { setSelectedCount( api?.rowSelectionApi.getSelectedPrimaryKeys(rowSelection).length ?? 0, ); }} > <InfiniteTable<Developer> columns={columns} columnDefaultWidth={150} onReady={({ api }) => setApi(api)} /> </DataSource> </> ); } const dataSource = () => { return fetch('https://data.infinite-table.com' + '/developers100') .then((r) => r.json()) .then((data: Developer[]) => data); }; type Developer = { id
Specify a rowSelection value
When multiple row selection is used, the
rowSelection prop should be an object that can have the following shape:const rowSelection = {
selectedRows: [3, 6, 100, 23], // those specific rows are selected
defaultSelection: false, // while all other rows are deselected by default
};
// or
const rowSelection = {
deselectedRows: [3, 6, 100, 23], // those specific rows are deselected
defaultSelection: true, // all other rows are selected
};
// or, for grouped data - this example assumes groupBy=continent,country,city
// for using this form of multi-row selection when you have grouping,
// you have to specify DataSource.useGroupKeysForMultiRowSelection = true
const rowSelection = {
selectedRows: [
45, // row with id 45 is selected, no matter the group it is nested in
['Europe', 'France'], // all rows in Europe/France are selected
['Asia'], // all rows in Asia are selected
],
deselectedRows: [
['Europe', 'France', 'Paris'], // all rows in Paris are deselected
],
defaultSelection: false, // all other rows are selected
}; COPY
As shown above, the
rowSelection.selectedRows and rowSelection.deselectedRows arrays can either contain:- primary keys of rows (which are usually strings or numbers) - any non-array value inside
rowSelection.selectedRows/rowSelection.deselectedRowsis considered an id/primaryKey value for a leaf row in the grouped dataset. - arrays of group keys (can be combined with primary keys as well) - those arrays describe the path of the specified selected group. Please note that
rowSelection.selectedRowscan contain certain paths whilerowSelection.deselectedRowscan contain child paths of those paths ... or any other imaginable combination. For this kind ofrowSelection, you need to enableuseGroupKeysForMultiRowSelection.
Row Selection only uses primary keys by default, even when you have grouped data.
For grouping however, you might want to use selection with group keys - for doing that, specify
DataSource.useGroupKeysForMultiRowSelection=true.
Note that if you use selection with group keys, the selection will not be relevant/consistent when the groupBy changes.When you have both grouping and
lazy loading, useGroupKeysForMultiRowSelection must be enabled - read more about it in the note below.When
lazyLoad is being used - this means not all available groups/rows have actually been loaded yet in the dataset - we need a way to allow you to specify that those possibly unloaded rows/groups are selected or not. In this case, the rowSelection.selectedRows/rowSelection.deselectedRows arrays should not have row primary keys as strings/numbers, but rather rows/groups specified by their full path (so useGroupKeysForMultiRowSelection should be set to true).// this example assumes groupBy=continent,country,city const rowSelection = { selectedRows: [ // row with id 45 is selected - we need this because in the lazyLoad scenario, // not all parents might have been made available yet ['Europe','Italy', 'Rome', 45], ['Europe','France'], // all rows in Europe/France are selected ['Asia'] // all rows in Asia are selected ] deselectedRows: [ ['Europe','Italy','Rome'] // all rows in Rome are deselected // but note that row with id 45 is selected, so Rome will be // rendered with an indeterminate selection state ], defaultSelection: false // all other rows are selected }
COPY
In the example above, we know that there are 3 groups (
continent, country, city), so any item in the array that has a 4th element is a fully specified leaf node. While lazy loading, we need this fully specified path for specific nodes, so we know which group rows to render with indeterminate selection.Controlled selection with checkbox column#
When using the controlled
rowSelection, make sure to specify the onRowSelectionChange callback prop to update the selection accordingly as a result of user interaction.This example shows how you can use multiple row selection with a predefined controlled value.
Go ahead and select some groups/rows and see the selection value adjust.
The example also shows how you can use the
InfiniteTableApi to retrieve the actual ids of the selected rows.View Mode
Fork Forkimport { InfiniteTable, DataSource } from '@infinite-table/infinite-react'; import type { InfiniteTableProps, InfiniteTablePropColumns, DataSourceProps, DataSourcePropRowSelection_MultiRow, } from '@infinite-table/infinite-react'; import * as React from 'react'; import { useState } from 'react'; const columns: InfiniteTablePropColumns<Developer> = { country: { field: 'country', }, firstName: { field: 'firstName', defaultHiddenWhenGroupedBy: '*', }, stack: { field: 'stack', renderGroupValue: ({ value }) => `Stack: ${value || ''}`, }, age: { field: 'age' }, id: { field: 'id' }, preferredLanguage: { field: 'preferredLanguage', renderGroupValue: ({ value }) => `Lang: ${value || ''}`, }, canDesign: { field: 'canDesign', renderGroupValue: ({ value }) => `Can design: ${value || ''}`, }, }; const defaultGroupBy: DataSourceProps<Developer>['groupBy'] = [ { field: 'canDesign', }, { field: 'stack', }, { field: 'preferredLanguage', }, ]; const groupColumn: InfiniteTableProps<Developer>['groupColumn'] = { field: 'firstName', renderSelectionCheckBox: true, defaultWidth: 300, }; const domProps = { style: { flex: 1, minHeight: 500, }, }; export default function App() { const [rowSelection, setRowSelection] = useState<DataSourcePropRowSelection_MultiRow>({ selectedRows: [0, 8, 10], defaultSelection: false, }); return ( <div style={{ display: 'flex', flex: 1, overflow: 'auto', color: 'var(--infinite-cell-color)', flexFlow: 'column', background: 'var(--infinite-background)', }} > <div style={{ padding: 10, }} > Current row selection: <code style={{ display: 'block', height: 100, overflow: 'auto', border: '1px dashed currentColor', }} > <pre> {JSON.stringify(rowSelection)}.</pre> </code> </div> <DataSource<Developer> data={dataSource} groupBy={defaultGroupBy} rowSelection={rowSelection} onRowSelectionChange={setRowSelection} primaryKey="id" > <InfiniteTable<Developer> debugId="controlled-multi-row-selection-example" columns={columns} domProps={domProps} groupColumn={groupColumn} columnDefaultWidth={150} /> </DataSource> </div> ); } const dataSource = () => { return fetch('https://data.infinite-table.com' + '/developers100') .then((r) => r.json()) .then((data: Developer[]) => data); }; type Developer = { id
Multi Selection with Lazy Load and Grouping#
Probably the most complex use-case for multi selection (with checkbox) is the combination of grouping and lazy-loading.
In this scenario, not all groups and/or rows are loaded at a given point in time, but we need to be able to know how to render each checkbox for each group - either checked, unchecked or indeterminate, all this depending on whether all children, at any nesting levels are selected or not.
In order to make this possible, the
rowSelection value will only contain arrays (and not individual primary keys) in the selectedRows and deselectedRows arrays and the DataSource will be configured with useGroupKeysForMultiRowSelection.With lazy loading, the server knows which rows are filtered out, so it's the server that applies
multiRowSelectionTarget. The selection keeps storing group keys and defaultSelection, and the data function receives multiRowSelectionTarget in its parameters. Use it when working out which rows a stored selection covers - for 'visible', a selected group (or defaultSelection: true) only covers the rows that pass the current filterValue, while for 'all', it covers every row in the group. Changing multiRowSelectionTarget doesn't reload the data.The checkboxes count the rows that the server reports: the
totalChildrenCount of each group (and of the top-level group response - see server-side grouping) and the totalCount of a response with rows. With 'all', the server can also send the counts without the filter, so the checkboxes count the rows hidden by the filter as well - a selected group with hidden rows that are not selected shows as partly selected:totalChildrenCountUnfilteredon each group, and on the top-level group responsetotalCountUnfilteredon a response with rows (not groups)
With these counts, a row or group that is selected or deselected on its own is counted with its own selection, even when the filter hides it.
Without them, or with
'visible', the checkboxes only count the rows that pass the filter. Once all the rows of a group are loaded, a selected (or deselected) row that the filter hides is no longer counted. Until then, it can't be told apart from a row that's not loaded yet, so it's counted with its own selection.A group whose count is not known yet (it was never loaded) counts as one row.
The
DataSet has lazy loading and grouping.The selection uses group keys (see
useGroupKeysForMultiRowSelection), so it can specify as selected even rows/groups that have not been loaded yet.Note in the example below that some of the group rows are partly selected, even if the leaf rows which are specified as selected in the
rowSelection are not yet loaded.View Mode
Fork Forkimport { InfiniteTable, DataSource } from '@infinite-table/infinite-react'; import type { InfiniteTablePropColumns, InfiniteTablePropGroupColumn, DataSourceData, DataSourcePropRowSelection_MultiRow, DataSourcePropGroupBy, } from '@infinite-table/infinite-react'; import * as React from 'react'; import { useState } from 'react'; type Developer = { id: number; firstName: string; lastName: string; country: string; city: string; currency: string; preferredLanguage: string; stack: string; canDesign: 'yes' | 'no'; hobby: string; salary: number; age: number; }; const dataSource: DataSourceData<Developer> = ({ pivotBy, aggregationReducers, groupBy, groupKeys = [], }) => { const args = [ pivotBy ? 'pivotBy=' + JSON.stringify(pivotBy.map((p) => ({ field: p.field }))) : null, `groupKeys=${JSON.stringify(groupKeys)}`, groupBy ? 'groupBy=' + JSON.stringify(groupBy.map((p) => ({ field: p.field }))) : null, aggregationReducers ? 'reducers=' + JSON.stringify( Object.keys(aggregationReducers).map((key) => ({ field: aggregationReducers[key].field, id: key, name: aggregationReducers[key].reducer, })), ) : null, ] .filter(Boolean) .join('&'); return fetch('https://data.infinite-table.com' + `/developers100-sql?` + args) .then((r) => r.json()) .then((data: Developer[]) => data) .then((data) => { return new Promise((resolve) => { setTimeout(() => { resolve(data); }, 100); }); }); }; const columns: InfiniteTablePropColumns<Developer> = { id: { field: 'id' }, firstName: { field: 'firstName', }, preferredLanguage: { field: 'preferredLanguage', }, stack: { field: 'stack', }, }; const domProps = { style: { height: '80vh', }, }; const groupBy: DataSourcePropGroupBy<Developer> = [ { field: 'stack' }, { field: 'preferredLanguage', }, { field: 'canDesign', }, ]; const groupColumn: InfiniteTablePropGroupColumn<Developer> = { field: 'firstName', defaultWidth: 250, }; export default function GroupByExample() { const [rowSelection] = useState<DataSourcePropRowSelection_MultiRow>({ defaultSelection: false, selectedRows: [ ['backend', 'Java'], ['backend', 'JavaScript'], ['backend', 'CSharp', 'no', 37], ['backend', 'PHP', 'no', 66], ['frontend'], ], }); return ( <DataSource<Developer> primaryKey="id" data={dataSource} groupBy={groupBy} lazyLoad selectionMode="multi-row" defaultRowSelection={rowSelection} useGroupKeysForMultiRowSelection > <InfiniteTable<Developer> debugId="lazy-multi-row-selection-example" domProps={domProps} columns={columns} keyboardNavigation="row" groupRenderStrategy="single-column" groupColumn={groupColumn} columnDefaultWidth={200} /> </DataSource> ); }