Using tree selection

When using a tree grid, a common use-case is to allow users to select nodes, both parent and child nodes.

The <TreeDataSource> component allows you to specify an initial tree selection, via the defaultTreeSelection prop.

Note

When using defaultTreeSelection or its controlled counterpart treeSelection, if no selectionMode is specified, the selection mode will default to "multi-row".

Note

If you enable selection, don't forget to specify which column should render a selection checkbox, by using renderSelectionCheckBox=true.

Example of tree selection value with default selection set to false
import type { TreeSelectionValue } from '@infinite-table/infinite-react';

// Default selection is false, with some selected node paths: ['1'] and ['2', '20']
// however, node ['1', '10'] is deselected
const treeSelectioDefaultDeselected: TreeSelectionValue = {
  defaultSelection: false,
  selectedPaths: [['1'], ['2', '20']],
  deselectedPaths: [['1', '10']],
};
Example of tree selection value with default selection set to true
// Default selection is true, with some deselected node paths: ['2'] and ['3']
// however, inside ['3'], we have a selected node ['3','30','301']
const treeSelectionDefaultSelected: TreeSelectionValue = {
  defaultSelection: true,
  deselectedPaths: [['2'], ['3']],
  selectedPaths: [['3','30','301']],
};
Using default tree selection
View Mode
Fork
import {
  DataSourceApi,
  InfiniteTableColumn,
  TreeDataSource,
  TreeGrid,
  TreeSelectionValue,
} from '@infinite-table/infinite-react';
import { useState } from 'react';

type FileSystemNode = {
  id: string;
  name: string;
  type: 'folder' | 'file';
  extension?: string;
  mimeType?: string;
  sizeInKB: number;
  children?: FileSystemNode[];
};

const columns: Record<string, InfiniteTableColumn<FileSystemNode>> = {
  name: {
    field: 'name',
    header: 'Name',
    renderTreeIcon: true,
    renderSelectionCheckBox: true,
  },
  type: { field: 'type', header: 'Type' },
  extension: { field: 'extension', header: 'Extension' },
  mimeType: { field: 'mimeType', header: 'Mime Type' },
  size: { field: 'sizeInKB', type: 'number', header: 'Size (KB)' },
};

const defaultTreeSelection: TreeSelectionValue = {
  defaultSelection: true,
  deselectedPaths: [
    ['1', '10'],
    ['3', '31'],
  ],
  selectedPaths: [['3']],
};

export default function App() {
  const [dataSourceApi, setDataSourceApi] =
    useState<DataSourceApi<FileSystemNode> | null>();

  return (
    <>
      <TreeDataSource
        onReady={setDataSourceApi}
        nodesKey="children"
        primaryKey="id"
        data={dataSource}
        defaultTreeSelection={defaultTreeSelection}
      >
        <div
          style={{
            color: 'var(--infinite-cell-color)',
            padding: 10,
            display: 'flex',
            gap: 10,
          }}
        >
          <button
            onClick={() => {
              dataSourceApi!.treeApi.selectAll();
            }}
          >
            Select all
          </button>
          <button
            onClick={() => {
              dataSourceApi!.treeApi.deselectAll();
            }}
          >
            Deselect all
          </button>
        </div>

        <TreeGrid columns={columns} />
      </TreeDataSource>
    </>
  );
}

const dataSource = () => {
  const nodes: FileSystemNode[] = [
    {
      id: '1',
      name: 'Documents',
      sizeInKB: 1200,
      type: 'folder',
      children: [
        {
          id: '10',
          name: 'Private',
          sizeInKB: 100,
          type: 'folder',
          children: [
            {
              id: '100',
              name: 'Report.docx',
              sizeInKB: 210,
              type: 'file',
              extension: 'docx',
              mimeType: 'application/msword',
            },
            {
              id: '101',
              name: 'Vacation.docx',
              sizeInKB: 120,
              type: 'file',
              extension: 'docx',
              mimeType: 'application/msword',
            },
            {
              id: '102',
              name: 'CV.pdf',
              sizeInKB: 108,
              type: 'file',
              extension: 'pdf',
              mimeType: 'application/pdf',
            },
          ],
        },
      ],
    },
    {
      id: '2',
      name: 'Desktop',
      sizeInKB: 1000,
      type: 'folder',
      children: [
        {
          id: '20',
          name: 'unknown.txt',
          sizeInKB: 100,
          type: 'file',
        },
      ],
    },
    {
      id: '3',
      name: 'Media',
      sizeInKB: 1000,
      type: 'folder',
      children: [
        {
          id: '31',
          name: 'Videos',
          sizeInKB: 5400,
          type: 'folder',
          children: [
            {
              id: '310',
              name: 'Vacation.mp4',
              sizeInKB: 108,
              type: 'file',
              extension: 'mp4',
              mimeType: 'video/mp4',
            },
          ],
        },
      ],
    },
  ];
  return Promise.resolve(nodes);
};

When using uncontrolled tree selection, the <TreeDataSource /> will manage the selection state internally, and will update it as a result of user actions. If you want to change the selection, you can use the Tree API to do so: selectNode, deselectNode, selectAll, deselectAll, etc.

Reacting to user actions

To listen to selection changes, you can use the onTreeSelectionChange callback.

This callback is called both when the user interacts with the grid, and when you use the Tree API to change the selection.

Using controlled tree selection

When using the controlled treeSelection prop, you have to make sure you update the tree selection via onTreeSelectionChange.

Controlled tree selection also gives you a more declarative way to manage the selection state. You no longer have to call Tree API methods to change the selection. Simply pass a new tree selection state object to the treeSelection prop and the tree grid will be updated accordingly.

For example, if you want to select all nodes, set the treeSelection prop to:

Tree selection value to show all nodes as selected
{
  defaultSelection: true,
  deselectedPaths: [],
}

For deselecting all nodes, the value should be:

All nodes as deselected
{
  defaultSelection: false,
  selectedPaths: [],
}

Using controlled tree selection also gives you an easy way to restore a previously saved tree selection at any point in time.

Using controlled tree selection
View Mode
Fork
import {
  InfiniteTableColumn,
  TreeDataSource,
  TreeGrid,
  TreeSelectionValue,
} from '@infinite-table/infinite-react';
import { useState } from 'react';

type FileSystemNode = {
  id: string;
  name: string;
  type: 'folder' | 'file';
  extension?: string;
  mimeType?: string;
  sizeInKB: number;
  children?: FileSystemNode[];
};

const columns: Record<string, InfiniteTableColumn<FileSystemNode>> = {
  name: {
    field: 'name',
    header: 'Name',
    renderTreeIcon: true,
    renderSelectionCheckBox: true,
  },
  type: { field: 'type', header: 'Type' },
  extension: { field: 'extension', header: 'Extension' },
  mimeType: { field: 'mimeType', header: 'Mime Type' },
  size: { field: 'sizeInKB', type: 'number', header: 'Size (KB)' },
};

export default function App() {
  const [treeSelection, setTreeSelection] = useState<TreeSelectionValue>({
    defaultSelection: false,
    selectedPaths: [['1', '10'], ['3']],
  });

  return (
    <>
      <TreeDataSource
        nodesKey="children"
        primaryKey="id"
        data={dataSource}
        treeSelection={treeSelection}
        onTreeSelectionChange={setTreeSelection}
      >
        <div
          style={{
            color: 'var(--infinite-cell-color)',
            padding: '10px',
          }}
        >
          <button
            onClick={() => {
              setTreeSelection({
                defaultSelection: false,
                selectedPaths: [],
              });
            }}
          >
            Deselect all
          </button>
          <button
            onClick={() => {
              setTreeSelection({
                defaultSelection: true,
                deselectedPaths: [],
              });
            }}
          >
            Select all
          </button>
          <div
            style={{
              overflow: 'auto',
              height: '10vh',
              minHeight: '200px',
            }}
          >
            Current tree selection:
            <pre>{JSON.stringify(treeSelection, null, 2)}</pre>
          </div>
        </div>

        <TreeGrid columns={columns} />
      </TreeDataSource>
    </>
  );
}

const dataSource = () => {
  const nodes: FileSystemNode[] = [
    {
      id: '1',
      name: 'Documents',
      sizeInKB: 1200,
      type: 'folder',
      children: [
        {
          id: '10',
          name: 'Private',
          sizeInKB: 100,
          type: 'folder',
          children: [
            {
              id: '100',
              name: 'Report.docx',
              sizeInKB: 210,
              type: 'file',
              extension: 'docx',
              mimeType: 'application/msword',
            },
            {
              id: '101',
              name: 'Vacation.docx',
              sizeInKB: 120,
              type: 'file',
              extension: 'docx',
              mimeType: 'application/msword',
            },
            {
              id: '102',
              name: 'CV.pdf',
              sizeInKB: 108,
              type: 'file',
              extension: 'pdf',
              mimeType: 'application/pdf',
            },
          ],
        },
      ],
    },
    {
      id: '2',
      name: 'Desktop',
      sizeInKB: 1000,
      type: 'folder',
      children: [
        {
          id: '20',
          name: 'unknown.txt',
          sizeInKB: 100,
          type: 'file',
        },
      ],
    },
    {
      id: '3',
      name: 'Media',
      sizeInKB: 1000,
      type: 'folder',
      children: [
        {
          id: '31',
          name: 'Videos',
          sizeInKB: 5400,
          type: 'folder',
          children: [
            {
              id: '310',
              name: 'Vacation.mp4',
              sizeInKB: 108,
              type: 'file',
              extension: 'mp4',
              mimeType: 'video/mp4',
            },
          ],
        },
      ],
    },
  ];
  return Promise.resolve(nodes);
};