Row Selection

InfiniteTable offers support for both single and multiple row selection. 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/nested data is used.

Row selection (both single and multiple) is driven by the rowSelection prop

Note

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).

Note

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 a string or a number (or null for no selection).

<DataSource<DATA_TYPE>
  primaryKey="id"
  data={[...]}
  defaultRowSelection={4}
>
  <InfiniteTable {...} />
</DataTable>
Uncontrolled single row selection

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
import { InfiniteTable, DataSource } from '@infinite-table/infinite-react';
import type {
  DataSourcePropGroupBy,
  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> columns={columns} columnDefaultWidth={150} />
    </DataSource>
  );
}

const dataSource = () => {
  return fetch('https://infinite-table.com/.netlify/functions/json-server' + '/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.

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.

Note

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.

Controlled single row selection

This example uses onRowSelectionChange callback prop to update the controlled rowSelection

View Mode
Fork
import { 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> columns={columns} columnDefaultWidth={150} />
      </DataSource>
    </>
  );
}

const dataSource = () => {
  return fetch('https://infinite-table.com/.netlify/functions/json-server' + '/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.

Using your mouse and keyboard to select rows

If you’re using checkboxes for selection, users will be selecting 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/ctrl modifier 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 + shift modifier key will perform a multi selection, starting from the last selection click where the shift key was not used.
Multi row 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
import { 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> columns={columns} columnDefaultWidth={150} />
    </DataSource>
  );
}

const dataSource = () => {
  return fetch('https://infinite-table.com/.netlify/functions/json-server' + '/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;
};

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.

Multi row selection with keyboard support

Use spacebar + optional cmd/ctrl/shift modifier keys just like you would do clicking + the same modifier keys.

View Mode
Fork
import { InfiniteTable, DataSource } from '@infinite-table/infinite-react';
import type {
  DataSourcePropGroupBy,
  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>
          keyboardSelection={keyboardSelection}
          columns={columns}
          columnDefaultWidth={150}
        />
      </DataSource>
    </>
  );
}

const dataSource = () => {
  return fetch('https://infinite-table.com/.netlify/functions/json-server' + '/developers100')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

type Developer = {
  id

Note

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.

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',
  },
};

Note

Any column can show a selection checkbox if column.renderSelectionCheckBox is set to true.

Nothing stops you from even having multiple checkbox columns.

Multi row selection with checkbox support

Use the selection checkboxes to select rows. You can also use the spacebar key (+ optional shift modifier) to modify the selection

View Mode
Fork
import { 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> columns={columns} columnDefaultWidth={150} />
    </DataSource>
  );
}

const dataSource = () => {
  return fetch('https://infinite-table.com/.netlify/functions/json-server' + '/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. 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.

Gotcha

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 is not selected it will select it, otherwise it will deselect it.

Note

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.

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
};

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.deselectedRows is 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.selectedRows can contain certain paths while rowSelection.deselectedRows can contain child paths of those paths … or any other imaginable combination. For this kind of rowSelection, you need to enable useGroupKeysForMultiRowSelection.

Gotcha

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.

Note

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
}

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.

Multi row checkbox selection with grouping

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
import { 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>
          columns={columns}
          domProps={domProps}
          groupColumn={groupColumn}
          columnDefaultWidth={150}
        />
      </DataSource>
    </div>
  );
}

const dataSource = () => {
  return fetch('https://infinite-table.com/.netlify/functions/json-server' + '/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.

Multi row checkbox selection with lazy data and grouping

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
import { 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://infinite-table.com/.netlify/functions/json-server' + `/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>
        domProps={domProps}
        columns={columns}
        keyboardNavigation="row"
        groupRenderStrategy="single-column"
        groupColumn={groupColumn}
        columnDefaultWidth={200}
      />
    <