Custom Render Hooks
React Complex Tree is completely unopinionated and allows you to customize every single node written to the DOM tree.
React Complex Tree provides default renderers that fulfill the accessibility requirements for tree structures as specified by W3C. This may no longer hold if you implement your own renderers instead.
If you provide custom renderers, make sure to create a DOM structure that fulfills the accessibility requirements by W3C.
If you only want to make small adjustments to the styling of the tree like adaptions to spacing, fonts or colors, you can look into the styling guide to see how you can use custom CSS variables and rules to adapt how the tree rendered by the default render hooks looks like.
Minimalistic Example for custom render hooks
All currently available render hooks are documented in the TreeRenderProps interface.
Complex Example for custom render hooks
As part of the react-complex-tree
monorepo, we maintain official render logic that generates a tree according
to the UI framework BlueprintJS by Palantir.
You can find the code for the custom render implementation
here.
Customizing the render logic for tree items
The most interesting hook is probably the renderItem hook,
which allows you to customize how individual tree items are rendered.
When using this hook to render an item, you can use the provided
TreeItemRenderContext to access the render details of the item
(e.g. whether the user currently drags over this item, or whether it is selected), and directly alter the
tree state (i.e. context.addtoselecteditems()
).
The props.children
prop contains the child nodes of the tree item. You need to render this so that children
are displayed. In the above example, children are rendered as child nodes for the item node itself, according
to W3C accessibility specifications. If you want to render a linear list of items, independent of item depth,
for example because you want to implement a virtualized list, you can do so by rendering the children outside the
item container:
renderItem={({ title, arrow, depth, context, children }) => {
const InteractiveComponent = context.isRenaming ? 'div' : 'button';
return (
<>
<li
{...context.itemContainerWithChildrenProps}
>
<InteractiveComponent
{...context.itemContainerWithoutChildrenProps}
{...context.interactiveElementProps}
>
{ arrow }
{ title }
</InteractiveComponent>
</li>
{children}
</>
);
}}
Make sure to provide the props-objects context.itemContainerWithoutChildrenProps
and
context.itemContainerWithChildrenProps
to the respective elements in your DOM structure, the first to the node
that contains the item and its children, and the second to the node that only contains the item without
its children. This is necessary to compute sizing information during drags.
Furthermore, the context.interactiveElementProps
props can be spread to the interactive element to
implement default interaction handlers, so that clicking on an element invokes its primary actions,
it is selected and focused etc. Those props implement the most common DOM interaction hooks and attach them
to the tree state, meaning that you need to provide minimal implementation effort for custom renderers. You
can omit those props if you want to implement custom interaction logic.
Note that, if you want to customize the way how mouse clicks interact with the tree state (i.e. whether clicking on a parent node should expand it or just focus it) should not be changed by providing custom DOM hooks, but by choosing a different interaction mode. Read more on interaction modes here.
The InteractiveComponent = context.isRenaming ? 'div' : 'button'
is important in case you want to
support renaming items. If the tree item is always rendered as button, it's focusing behavior will
cause a blur event when the rename starts, and revert the item back to the non-renaming state.