Treefold
A renderless tree React component for your hierarchical views.
The problem
You need to show hierarchical data in different ways. You know how you want to show the information for each individual data item. But you don't want to repeat over and over again the logic about how to traverse the data, how to assemble it all to make it look like a tree, how to expand/collapse nodes, etc.
This solution
This is a component that abstracts away some of these repetitive details during a tree-rendering process. You just specify how you want to render each individual data item, and Treefold
takes care of everything else.
The component itself is stateless and controlled by default, so you can control any aspects of it from the outside. The expand/collapse state of each individual node is normally also controlled, but if no props for controlling it are specified, the component auto-manages the state for this on its own.
You can see a live demo here.
Installation
This module is distributed via npm which is bundled with node and should be installed as one of your project's dependencies
:
npm install --save react-treefold
or if you use yarn:
yarn add react-treefold
This package also depends on
react
andprop-types
. Please make sure you have those installed as well.
Usage
Use the Treefold
component by passing to it the hierarchical data (nodes
prop) and how to render each node in the tree (render
prop):
import React from 'react'; import Treefold from 'react-treefold'; const MyTreeView = ({ nodes }) => ( <div className="treeview"> <Treefold nodes={nodes} render={({ node, level, isFolder, isExpanded, getToggleProps, hasChildNodes, renderChildNodes, }) => ( <> <div className="item" style={{ paddingLeft: `${level * 20}px` }}> {isFolder && ( <span className="toggle-icon" {...getToggleProps()}> <i className={isExpanded ? 'caret-down' : 'caret-right'} aria-hidden="true" /> </span> )} {node.name} </div> {isExpanded && (hasChildNodes ? ( renderChildNodes() ) : ( <div className="empty" style={{ paddingLeft: `${(level + 1) * 20}px` }} > This folder is empty </div> ))} </> )} /> </div> );
The render
function receives the data for the node it needs to render, in addition to a set of extra props describing several different aspects of how the node needs to be rendered.
Treefold props
nodes
array<object>
| required
The list of root nodes in the tree. Each node in the list should be an object with a unique id
attribute (string or numeric), and a children
array attribute containing the child nodes of that node. If no children
is specified, the node is assumed to be a leaf.
Note: the names of these node attributes is customizable. See getNodeId and getNodeChildren.
render
function({/* see below */}): element
| required
A function that renders a single node of the tree.
This is called with an object argument. Read more about the properties of this object in the section "Rendering a single node".
isNodeExpanded
function(node: object): boolean
| optional
A function that receives a node and returns true
if that node should be expanded, or false
otherwise.
It must be provided alongside onToggleExpand, in order to control the tree from the outside (i.e. to make it behave as a controlled component).
If these two props are not provided, the tree controls its expand/collapse state of nodes on its own (i.e. it behaves as an uncontrolled component).
onToggleExpand
function(node: object)
| optional
A function that receives a node and toggles the state of that node being expanded or collapsed.
It must be provided alongside isNodeExpanded, in order to control the tree from the outside. If these two props are not provided, the tree controls its expand/collapse state on its own.
getNodeId
function(node: object): number | string
| optional, defaults tonode => node.id
A function that receives a node in the tree and returns what to use as a unique id for that node.
getNodeChildren
function(node: object): array?
| optional, defaults tonode => node.children
A function that receives a node in the tree and returns the array of child nodes of that node, or nothing if the node is a leaf.
Note that there can be non-leaf nodes that have no child nodes. These are the ones with an empty array of child nodes. For a node to really be considered a leaf, the resultof this function must be null
or undefined
.
Rendering a single node
The most important thing you have to tell to Treefold
besides the actual tree data to render, is how to render it. You do so primarily by providing a prop called render
which receives all the information necessary about that node, and returns the jsx element that represents it.
<Treefold nodes={treeData} render={props => ( /* you render the node here */ )} />
You can also pass it as the
children
prop if you prefer to do things that way<Treefold>{/* right here*/}</Treefold>
Treefold
takes care of calling this function as it traverses the tree, and passes to it an object (props
in the example just above) with the properties documented below:
node
object
| required
The data for the node that is being rendered.
level
number
| required
The level of depth of the node in the tree. Root nodes have level 0. Child nodes of a given node have the level of their parent node plus 1.
isFolder
boolean
| required
Wether the node is a folder (meaning it has or may have child nodes under it) or not.
A node is considered to be a folder if it has a collection of child nodes, even if that collection is empty. See getNodeChildren to see how Treefold
determines what is the collection of child nodes for a given node.
isExpanded
boolean
| required
Wether the node is to be rendered expanded, with its child nodes visible, or not.
This property will always be false
for leaf nodes. It may be true
for non-leaf nodes that have an empty list of child nodes.
hasChildNodes
boolean
| required
Wether the node to be rendered has a non-empty list of child nodes or not.
This property will always be false
for leaf nodes. It may also be false
for non-leaf nodes that have an empty list of child nodes.
getToggleProps
function(props: object): object | required for folder nodes,
null
for leaf nodes
This is a prop getter that returns all the props that you need to apply to any expand/collapse toggle elements in your node. These are the elements that are meant to be activated by the user to toggle that folder node expand/collapse state.
By definition this function is provided only to non-leaf nodes, even if the node has an empty list of child nodes. For leaf nodes this prop is null
.
renderChildNodes
function(): element?
| required for folder nodes,null
for leaf nodes
A function that you need to call for non-leaf nodes so that its child nodes are rendered.
Why do I need to take care of this?
You might think that this is something that Treefold
should do itself. But if it did so, it would hinder your freedom in achieving different outcomes.
TODO: Document this more in depth
TO-DO
- Ability to control child nodes visibility via css instead of removing them from the DOM.
- Full WAI-ARIA compliance.
- Improve customization to expand the use cases where this can be applied.
Feature requests and suggestions are very welcome. This is a very young project not yet applied to a wide variety of situations, so I know it has a long road of changes and improvements ahead.
LICENCE
MIT