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
.
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']],
};
// 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']],
};
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:
{
defaultSelection: true,
deselectedPaths: [],
}
For deselecting all nodes, the value should be:
{
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.
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); };