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.When using
defaultTreeSelection
or its controlled counterpart treeSelection
, if no selectionMode
is specified, the selection mode will default to "multi-row"
.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']],
};
COPY
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']],
};
COPY
View Mode
Fork Forkimport { 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: [],
}
COPY
For deselecting all nodes, the value should be:
All nodes as deselected
{
defaultSelection: false,
selectedPaths: [],
}
COPY
Using controlled tree selection also gives you an easy way to restore a previously saved tree selection at any point in time.
View Mode
Fork Forkimport { 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); };