The TreeView component is designed to display data using a hierarchical structure. It was built with performance in mind, which influences API design and feature implementations.
This component follows a slightly different thought from our low-level and high-level philosophy, we consider this to be a middle-level component that allows you to build the two in one, allowing flexibility and adding features that you wouldn’t have in a low-level. It’s also easy to move from low-level to high-level quickly and the APIs are more consistent.
Content
As TreeView is middle-level, it allows you to build static or dynamic content if you need to consume data from some service.
It uses a very common technique in React.js components called render props whose value is a function that will receive some data. This is essential for the component to be data agnostic. It allows more flexibility with the names of the properties to be rendered and avoids creating APIs to change those properties.
Snippet
<Mouserender={(mouse) => <Catmouse={mouse} />} />
Another benefit is using children as a render prop which allows you to quickly adapt the static component to dynamic just by changing the syntax.
Snippet
<Mouse>{(mouse) => <Catmouse={mouse} />}</Mouse>
The main components of the TreeView are <TreeView.Item />, <TreeView.Group /> and <TreeView.ItemStack /> which abstract all the necessary markup complexity and allow the granular composition to build different styles of a TreeView. For example: File Explorer, Users, Page Navigation, or Page Structure.
Static
Snippet
<TreeView>
<TreeView.Item>
<TreeView.ItemStack>
<StickerdisplayType="primary"shape="user-icon"size="sm">
NS
</Sticker>
nisi quis eleifend
</TreeView.ItemStack>
<TreeView.Group>
<TreeView.Itemkey="Victor Valle">
<TreeView.ItemStack>
<StickerdisplayType="primary"shape="user-icon"size="sm">
FP
</Sticker>
fusce ut placerat
</TreeView.ItemStack>
<TreeView.Group>
<TreeView.Itemkey="susana-vázquez">
<StickerdisplayType="primary"shape="user-icon"size="sm">
UT
</Sticker>
ultrices dui sapien
</TreeView.Item>
</TreeView.Group>
</TreeView.Item>
<TreeView.Itemkey="emily-young">
<StickerdisplayType="primary"shape="user-icon"size="sm">
MC
</Sticker>
maecenas pharetra convallis
</TreeView.Item>
</TreeView.Group>
</TreeView.Item>
</TreeView>
Dynamic
The TreeView and Group components when used with dynamic items are populated from hierarchical data. The component consumes the data using the items property.
For the component to be dynamic it is necessary to convert children to render prop, which must be a function that will receive the item of the current iterator.
When a tree is very large, loading items (nodes) asynchronously is preferred to decrease the initial payload and memory space. TreeView provides two ways to load asynchronous data:
Load the children of an item when clicking on the item
Load paginated data from an item
Load the children of an item
The TreeView doesn’t know when an item is asynchronous, so the developer must specify whether the item is asynchronous or not. The onLoadMore property is called every time the item is a leaf node of the tree and depending on the method’s return value it will behave differently.
When returning
void
,
null
or
undefined
the TreeView will do nothing.
When returning the
item
will add to the tree.
When adding a new asynchronous item to the tree, the onItemsChange method is respectively called to update the tree with a new value if the items prop is controlled.
Warning
If you have an error in the asynchronous call of the
onLoadMore
method, only the suppression is done and an error is thrown on the console.
The onLoadMore API can also be used to load paginated data for a specific item. The signature just needs to be followed so the TreeView can know what to do.
The cursor helps to store the data that will be used to identify which will be the next request, this can be an offset, cursor, id the URL or any other data that represents the item cursor, when there is no more data just sets the cursor to null. The TreeView also exposes a public API that can invoke loadMore and find out if there is a cursor for a specific item.
Creating a nested item is necessary to declare it inside the <TreeView.Item> to differentiate the nested items from the current item, it is necessary to wrap the item elements in a <TreeView.ItemStack />. To declare the items of an item, declare the same components nested inside a <TreeView.Group /> component.
Actions are added using the actions property on each item. The component abstracts some of the complexities of markup and class usage to make composing components easier without having to add explicitly classes. This works great for Clay’s Button and DropDownWithItems components, other components should consider adding the class component-action explicitly.
An item can be disabled by setting the disabled prop in the <TreeView.ItemStack /> and <TreeView.Item /> components. By default, the Expander and Checkbox are also disabled, and any other clickable elements other than these need to be set to disabled in the composition.
The expansion of an item in the TreeView is controlled internally. It’s also possible to control the state and set an initial value with the keys of the items that are initially expanded.
The TreeView exposes the expandedKeys property and the onExpandedChange callback to control the state of expanded items. The expandedKeys property must be a collection of values using ia Set().
Customizing the expander is possible using the expanderIcons property. Changing the icons is applied the entire tree, and it’s not possible to change them exclusively for a single item.
The expanderDisabled property is only available in the <TreeView.ItemStack /> component where the Expander is visible and has children.
Manual Expand
Some more advanced use cases want to change the behavior of expanding the item when the user interacts with the item differently, e.g single click selects, double click expands the item.
The expand method is available via render props only when the content is dynamic, the same as selection, expose two methods, toggle and has.
Snippet
<TreeView>
{(item, selection, expand) => (
<TreeView.ItemonClick={(event) => {
clearTimeout(clickTimerRef.current);
// Ignores the component's default behavior, clicking the// item expands the item if it has any child items.event.preventDefault();
switch (event.detail) {
case1:
clickTimerRef.current=setTimeout(() => {
if (!selection.has(item.id)) {
selection.toggle(item.id);
}
}, 200);
break;
case2:
expand.toggle(item.id);
default:
break;
}
}}
>
Item
</TreeView.Item>
)}
</TreeView>
Selection
The selection allows the user to select one or more items in the TreeView, there are three main interactions that can be configured and are defined using the selectionMode prop.
single
select only one item.
multiple
select multiple items.
multiple-recursive
selects multiple items and the item’s children recursively. When all children are selected, the parent will be marked as selected, otherwise it will be marked as intermediate.
The selection can be configured and composed in different ways depending on each use case. Setting the selectionMode and using a <Checkbox /> in the item will respect the configuration of the selection. It is also possible to configure the selection manually to customize what will trigger the selection or modify the look of the selected state. For more information check the manual selection section.
Selection can be controlled and uncontrolled, this means you can keep the state of selectedKeys at the implementation level or let the TreeView keep the state controlled internally.
Rendering the TreeView with pre-selected items will cause their parent items to be expanded so that the selected item is visible on the first render. In recursive multiple selection, the parents items will marked as intermediate.
Info
Expanding selected items in the first render implies performance degradation if
the TreeView has too many items; but there are some possibilities to work around
depending on the use case, read the
Selection hydration
section for more information to mitigate this when viewing a noticeable slowdown
on the first render.
Single Selection
The single selection state works independently of adding the <Checkbox /> and this state is set by default.
Multiple Selection
The multi-selection in TreeView is a achieved by composing with the <Checkbox /> component that must be added explicitly in the item rendering.
It isn’t necessary to add the onChange event or the checked property. The TreeView adds the methods to the component under the covers. This allows you to just add the Checkbox in any order you want.
Multi-selection is also controlled in the same way as for the expander. It uses the key of the item to select it. The selectedKeys property and the onSelectionChange event are used for this purpose.
By default, when selecting an item with nested items, its children are recursively selected.
There are some limitations: for static content the recursive selection only works if the item is expanded, (i.e visible). For dynamic content, it works independently.
Warning
Recursive selection will not select items from an asynchronous Item.
Manual Selection
Manual selection is the possibility to trigger the selection without using the <Checkbox /> or visually changing the state of the selection types, such as the single selection state.
The selection method is available via render props when the content is dynamic, it exposes two main methods, toggle and has that allow you to customize who will trigger and check if the item is selected.
The default behavior of clicking the item is to expand and load the item asynchronously if this is not needed when the user selects an item you can prevent this default behavior in the same way you would prevent the default behavior of the browser.
Snippet
<TreeView>
{(item) => (
<TreeView.ItemonClick={(event) => {
event.preventDefault();
// You can do anything after that, for example in a Modal,// close and select.onClose(item);
}}
>
Drive
</TreeView.Item>
)}
</TreeView>
Drag and Drop
The drag and drop implementation handles items internally and works only for TreeView with dynamic content because it depends on the items property and must be enabled using the dragAndDrop property.
The TreeView handles items immutably but follows a partial tree immutability implementation that is more optimized to do at runtime, To access these changes and be able to save in some service, you must control the state using the items property and the onItemsChange event.
The component allows you to add rules in Drag and Drop like disabling an item to be draggable and dropable, for this you need to set the draggable property of the component to TreeView.Item or TreeView.ItemStack.
In some cases it is necessary to check if the item can be dropped in another item, for example a TreeView from a file explorer, does not allow moving a file into another file. You need to use onItemMove to decide if the item can be droppable on a specific item.
Snippet
<TreeViewdragAndDroponItemMove={(item, parentItem) => {
// your logic here// Returning false does not allow the item to be dropped in the parentItem.returnfalse;
}}
>
...
</TreeView>
Shortcuts
The TreeView implements shortcuts and manages the focus. Some shortcuts and focus trigger some actions like renaming, removing or asynchronous loading if any.
Warning
If an Item is asynchronous the
onLoadMore
method will be called as described in the Asynchronous Item section.
Rename Item
The user can press the shortcut key R or F2 to rename whatever property. In that case, feel free to open a Modal or any other user interface element that allows the user to change the item. For this to work, the onRenameItem method must be passed as a property to the component and must be an asynchronous method.
The onRenameItem property receives the item object corresponding to the current item that the user wants to rename and the method needs to return a new immutable object with the new data.
If the user presses the Backspace or Delete key the TreeView removes the item from the items structure. In which case onItemsChange is called with the tree is updated.
Performance
The TreeView component was designed with performance in mind. Internally, the component handles everything without the developer having to worry about making implementation adjustments, but in some scenarios, it may be necessary for the developer to follow good practices to avoid a performance degradation.
This section helps with some good practices that help the TreeView remain responsive.
Selection hydration
Selection hydration is the ability for the TreeView to expand the parent items of the selected items when the component performs the first render.
This implies some performance problems because the component only knows about the selectedKeys and the items, and will have to traverse the entire tree to find the selected items and the path to the parent item to expand it.
This can lead to a performance problem on the first render if you have too many items or too many selected items. It’s possible to mitigate this on the first render by setting the selectionHydrationMode prop: it supports two rendering phases, render-first and hydrate-first, both have trade-offs that depend on the number of items being rendered:
render-first
will render first and then hydrate. It doesn’t block the initial rendering but it is possible to see the items being expanded.
hydrate-first
will hydrate first and then render. This blocks rendering first until it traverses the tree, when rendered the items are already expanded.
Info
The tree traversal implementation internally has some performance optimizations.
Depending on the number of selected items, the TreeView stops traversing the tree
when it finds all the selected items. In most scenarios, the TreeView will almost
never traverse the entire tree.
API Reference
TreeView
typeofTreeView
Parameters
Properties
displayType
"light"|"dark"|undefined= "light"
Flag to determine which style the TreeView will display.
dragAndDrop
boolean|undefined
Flag to enable Drag and Drop of Nodes over the Tree.
dragAndDropContext
(Window&typeofglobalThis) |undefined= "window"
Optional drag and drop context: this is to avoid
errors when using various drag and drop contexts
on the same page.
expandDoubleClick
boolean|undefined
Flag to expand the node’s children when double-clicking the node.
expandOnCheck
boolean|undefined
Flag to expand child nodes when a parent node is checked.
expanderClassName
string|undefined
Extra classes passed to the expander button.
expanderIcons
Icons|undefined
Flag to modify Node expansion state icons.
itemNameKey
string|undefined= "name"
Flag to indicate which key name matches the item name to be displayed
in drag preview.
messages
DragAndDropMessages|undefined
Messages that the TreeView uses to announce to the screen reader. Use
this to handle internationalization.
Callback is called when an item is about to be moved elsewhere in the tree.
onLoadMore
OnLoadMore<T>|undefined
When a tree is very large, loading items (nodes) asynchronously is preferred to
decrease the initial payload and memory space. The callback is called every time
the item is a leaf node of the tree.
onSelect
((item:T) =>void) |undefined
Callback called whenever an item is selected. Similar to the onSelectionChange
callback but instead of passing the selected keys it is called with the current
item being selected.
onRenameItem
((item:T) =>Promise<T>) |undefined
Calback is called when the user presses the R or F2 hotkey.
Flag to indicate the hydration phase to expand the selected items. When
selectionMode is multiple-recursive it also revalidates the
indeterminate state of the items.
It supports two rendering phases, render-first and hydrate after or
hydrate before rendering, both have trade-offs that depend on the number
of items being rendered.
Both cases traverse the tree looking for the selected items to know which
items should be expanded and which should be in the indeterminate state,
this is done only the first time the component is rendered and if it has
selected items. This operation can degrade the performance of the
component depending on the number of items, choose the best option for
your use case.
render-first
will render first and then hydrate. It doesn’t block the
initial rendering but after rendering it is possible to see the items
being expanded.
hydrate-first
will hydrate first and then render. This blocks
rendering first until it traverses the tree, when rendered the items
are already expanded.
defaultSelectedKeys
Set<Key>|undefined
Property to set the initial value of selectedKeys.
onSelectionChange
((keys:Set<Key>) =>void) |undefined
Handler that is called when the selection changes.
selectedKeys
Set<Key>|undefined
The currently selected keys in the collection.
indeterminate
boolean|undefined= true
Flag to disable indeterminate state when selectionMode is multiple-recursive.