Controlled usage
There are two ways to give TreeSelect its data:
| Prop | Mode | Who owns the selection |
|---|---|---|
defaultData | Uncontrolled | TreeSelect. It starts from the data passed on the first render (later changes to defaultData are ignored), applies each selection itself, and reports it through onChange. |
data | Controlled | You. TreeSelect renders exactly the data it receives, and only reports changes through onChange. |
Controlled example
Keep the tree in state and pass onChange your state setter:
[]Show code
import { useState } from 'react';
import TreeSelect, { getSelectedIDs, SelectionMode } from '@attebyte/react-tree-select';
import { exampleData, ExampleNode } from './data';
export default function ControlledExample() {
const [data, setData] = useState(exampleData);
const [mode, setMode] = useState<SelectionMode>('leaves');
const selectedIDs = getSelectedIDs(data, {
uniqueIDProperty: 'value',
childrenProperty: 'children',
valueProperty: 'included',
mode
});
return (
<div className='flex flex-wrap gap-6'>
<TreeSelect<ExampleNode>
data={data}
onChange={setData}
uniqueIDProperty='value'
labelProperty='label'
childrenProperty='children'
valueProperty='included'
defaultExpanded
aria-label='Controlled example'
/>
<div className='min-w-48'>
<label className='block mb-2'>
Mode{' '}
<select value={mode} onChange={e => setMode(e.target.value as SelectionMode)} className='text-black rounded px-1'>
<option value='all'>all</option>
<option value='leaves'>leaves</option>
<option value='topmost'>topmost</option>
</select>
</label>
<div>
Selected IDs: <code data-testid='selected-ids'>{JSON.stringify(selectedIDs)}</code>
</div>
<button className='mt-3 border rounded px-2 py-1' onClick={() => setData(exampleData)}>
Reset
</button>
</div>
</div>
);
}
If you pass data but don't update it from onChange, checking a node has no visible effect, the same as a controlled <input> without an onChange handler. This makes it easy to reject or adjust a change before applying it.
What onChange receives
onChange is called with a new tree every time a selection changes. TreeSelect never modifies the data you pass in:
- Nodes that changed, and their ancestors, are new objects.
- Every other node is the same object as before, so unchanged subtrees can be compared with
===.
Treat the result as immutable. If you need to change it, copy the nodes you modify, as you would with any React state.
A single root node
If your tree has one root, pass the node itself instead of wrapping it in an array. onChange reports a single node too, so the state can stay a single node:
const [root, setRoot] = useState<Category>(rootCategory);
<TreeSelect
data={root}
onChange={setRoot}
uniqueIDProperty='value'
labelProperty='label'
childrenProperty='children'
valueProperty='included'
/>
Loading data later
data can start as undefined (for example while a request is in flight). TreeSelect renders nothing until it receives data.
Expansion state
Which nodes are expanded is managed by TreeSelect in both modes. defaultExpanded sets whether nodes start expanded, and changing it later expands or collapses every node.