react-hyper-tree

Fully customizable react tree component

Usage no npm install needed!

<script type="module">
  import reactHyperTree from 'https://cdn.skypack.dev/react-hyper-tree';
</script>

README

React hyper tree

Fully customizable tree view react component

Welcome to the react hyper tree component 😄 I want to introduce you to an awesome react component for displaying tree data structure

dependecies license min minzip

Features

  • render tree-like data structure
  • show/hide lines
  • fully custom component by providing render functions (node and drag zone) or custom class names
  • tree management by global utility (treeHandlers)
  • single/multiple node selection
  • async loading of children
  • drag and drop using 3 types of insertion (before, children, after)

Table of contents

Live demo is available!

Check also react-hyper-modal library

Installation

You can use npm or yarn package managers
$ npm i --save react-hyper-tree

or

$ yarn add react-hyper-tree

Usage

Simple Usage

import React from 'react'
import Tree, { useTreeState } from 'react-hyper-tree'

const data = {
  id: 1,
  name: 'Parent 1',
  children: [
    {
      id: 2,
      name: 'Child 1',
      children: [
        {
          id: 5,
          name: 'Child 1__1',
        },
        {
          id: 6,
          name: 'Child 1__2',
        },
        {
          id: 7,
          name: 'Child 1__3',
        },
      ],
    },
  ],
}

...

const MyTreeComponent = () => {
  const { required, handlers } = useTreeState({
    data,
    id: 'your_tree_id',
  })

  return (
    <Tree
      {...required}
      {...handlers}
    />
  )
}

Properties

Props Description
classes? object with elements class names
data nodes data, provided by required prop
depthGap? children indentation related to parent
disableHorizontalLines? disable horizontal lines
disableLines? disable all lines
disableVerticalLines? disable vertical lines
disableTransitions? disable transitions (improves performance)
displayedName? format node content, if you use default node renderer
draggable?: enable draggable mode
gapMode? indentation mode
horizontalLineStyles? horizontal line styles, SVG properties
renderDragZone? function to render your custom drag zone
renderNode? function to render your custom node
setOpen? open node children, provided by handlers prop
setSelected? select node, provided by handlers prop
staticNodeHeight? set static height of node, otherwise dynamic height will be used
verticalLineOffset? vertical line offset related to parent
verticalLineStyles? vertical line styles, SVG properties
verticalLineTopOffset? vertical line top offset

useTreeState API

useTreeState React hook includes the state management functionality. It prepares and transforms the data to use all functionality of the Node API.

useTreeState input

Property Description
childrenKey? set the children key, e.g. 'children'
data tree-like data
defaultOpened? if true, all parent will be opened
filter? function to filter tree nodes
id tree id, required
idKey? set the data id key, e.g. 'id'
multipleSelect? if true, a several nodes can be selected
sort? function to sort tree nodes

useTreeState output

Property Description
handlers handlers to manipulate node state. setOpen, setLoading, setSelected, setChildren, setRawChildren
instance tree view instance including all tree methods
required includes enhanced tree structure

Actually TreeView component is a renderer. It hasn't any functionality to manipulate of tree state.

Node API

Method Description Typings
getChildren returns node children or empty array () => TreeNode[]
getData returns raw node data () => any
getFirstChild returns the first child () => TreeNode | null
getLastChild returns the last child () => TreeNode | null
getPath get node path (array?: boolean) => string
hasChildren returns true if node has atleast one child () => boolean
isLoading returns true if node is loading () => boolean
isOpened returns true if node is opened () => boolean
isSelected returns true if node is selected () => boolean
setChildren a simple equivalent of setNodeChildren (children: TreeNode[]) => void
setData sets node data (data?: any) => void
setLoading set node loading (loading?: boolean) => void
setNodeChildren insert node children (children: TreeNode[], type?: InsertChildType, reset?: boolean) => TreeNode[]
setOpened set node opened (opened?: boolean) => void
setParent set node parent (parent?: TreeNode) => void
setSelected set node selected (selected?: boolean) => void
getPath get node path (array?: boolean) => string | string[]
getReactKey returns calculated property for react key () => string

Global state manager

The main goal to implement the tree view library was a simple usage and global tree manager.

Actually, global state manager (GSM) is represented as treeHandlers object. It has all instances of trees in the project.

Every time you use useTreeState hook. It will create a new TreeView instance and add the instance to treeHandlers object.

The GSM structure

The GSM object has the one property trees.

type Handler = (...args: any[]) => any

interface IHandlers {
  [key: string]: Handler;
}

interface ITreeItem {
  instance: TreeView;
  handlers: IHandlers;
}

interface ITrees {
  [key: string]: ITreeItem;
}

trees: ITrees

When you use useTreeState with the tree id, it will add tree instance to GSM. To access to tree instance you should do the next:

import { treeHandlers } from 'react-hyper-tree'

treeHandlers.trees[your-tree-id].instance

You can use the full tree instance functionality from the GSM. Also the GSM has the handlers property for every tree instance.

Every tree has a default set of methods to manipulate the data

Method Descriptipn Typings
rerender rerender the tree component (callback? () => void) => void
setLoading set loading property (node: TreeNode | string | number, loading?: boolean) => void
setOpen set opened property (node: TreeNode | string | number) => void
setOpenByPath set opened by path (path: string) => void
setRawChildren set node children, use it if you have a raw children data (parent: TreeNode | string | number, children: IData[], type?: InsertChildType, reset?: boolean) => void
setChildren set node children, use it if you have an enhanced children data (parent: TreeNode | string | number, children: TreeNode[], type?: InsertChildType, reset?: boolean) => void
setSelected set selected property (node: TreeNode | string | number, selected?: boolean) => void
setSelectedByPath set selected by path (path: string, all: boolean) => void
setSiblings set node siblings (node: TreeNode | string | number, siblings: TreeNode[], type: InsertSiblingType) => void

To call any method you should do the next:

import { treeHandlers } from 'react-hyper-tree'

treeHandlers.trees[your-tree-id].handlers.setOpen(...)

treeHandlers API

Method Description Typings
getIds get trees ids () => string[]
remove remove tree from the GSM (id: string): TreeHandlers
removeHandler remove handler from the tree removeHandler(treeId: string, handlerName: string): TreeHandlers
safeUpdate add or update tree in the GSM safeUpdate(id: string, tree: TreeView) => TreeHandlers
safeUpdateHandler add or update tree handler safeUpdateHandler(treeId: string, handlerName: string, handler: Handler): TreeHandlers

You can also use treeHandlers like call chain

treeHandlers
  .safeUpdateHandler(id, 'setLoading', setLoading)
  .safeUpdateHandler(id, 'setSelected', setSelected)
  .safeUpdateHandler(id, 'setRawChildren', setRawChildren)
  .safeUpdateHandler(id, 'setChildren', setChildren)

Async children

You also can use loadable children. To enable the feature you should provide getChildren function to node data

const getChildren = ({ node }) => {
  return getChildrenByParentId(node.id)
}

const data = {
    id: 1,
  name: 'Parent 1',
  getChildren,
}

getChildren function can return Promise and resolve the children data in format like this:

const getChildren = () => new Promise((resolve) => setTimeout(() => resolve([
  {
    id: 2,
    name: 'Child',
  },
]), 1000))

You can also fire any events like redux-actions in the getChildren function. In this case you can set the children by the GSM

Default properties

export const defaultProps = {
  childrenKey: 'children',
  classes: {} as ClassesType,
  depthGap: 20,
  displayedName: (node: TreeNode) => node.data.name,
  filter: () => true,
  gapMode: 'margin' as const,
  horizontalLineStyles: { stroke: 'black', strokeWidth: 1, strokeDasharray: '1 1' },
  idKey: 'id',
  opened: [],
  verticalLineOffset: 5,
  verticalLineStyles: { stroke: 'black', strokeWidth: 1, strokeDasharray: '1 1' },
  verticalLineTopOffset: 0,
}

Road map

  • Coverage by tests
  • Inner improvements and extending functionality
  • Documentation improvements

Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

License

MIT