Guide
Installation
npm install he-tree-react
pnpm add he-tree-react
yarn add he-tree-react
Data Types
This library supports two types of data structures:
- Flat data, which is a one-dimensional array. Similar to data stored in a database. Each item requires an id, a parent id,
null
means there is no parent. The order of flat data must be the same as the tree, you can use thesortFlatData
method to sort the data when initializing the data.js[ { id: 1, pid: null }, { id: 2, pid: 1 }, { id: 3, pid: 2 }, ];
- Tree data. Each node contain child nodes in an array. If id is not specified, this library will use the node's index in the tree as the id. When using tree data, you need to set
dataType: 'tree'
.js[ { id: 1, children: [ { id: 2, children: [{ id: 3 }], }, ], }, ];
The id, pid, children
in the data are not fixed. In the options, use idKey, parentIdKey, childrenKey
to indicate the corresponding key names in your data.
No Components
This library does not export components, but exports a hook useHeTree
. Use the returned renderTree
to render the tree. The advantage of this is that in addition to renderTree
, useHeTree
will also return some internal states and methods, which can be easily obtained.
import { useHeTree } from "he-tree-react";
export default function App() {
const { renderTree } = useHeTree({...})
return <div>
{renderTree()}
</div>
}
Options
useHeTree
is the primary function used, its first parameter is an options object. The required options are data
, and at least one of renderNode, renderNodeBox
must be present. Other important options include:
dataType
, indicating data type. Available values:flat
, default. Flat data.tree
, tree-shaped data.
idKey, parentIdKey
, the default values areid
andparent_id
. Needed when using flat data. Although there are default values, it is better to explicitly state them.childrenKey
, the default ischildren
. Needed when using tree-shaped data. Although there are default values, it is better to explicitly state them.onChange
, a function called when data changes, the parameter is new data. If your tree will not change then this is not required.-
isFunctionReactive
, boolean. Defaultfalse
.useHeTree
options include many callback functions, such asonChange, canDrop
.isFunctionReactive
can be used to control whether to listen for changes to these callback functions. If your callback functions anddata
change synchronously, you do not need to enable this. Otherwise, you need to enable this and use React'suseCallback
oruseMemo
to cache all your callback functions to avoid performance issues.
See the useHeTree
API documentation for more information.
Tips
stat
, information related to a single node. Most of the parameters in callback functions havestat
. Refer toStat
API.node
, the data of the node. You can get node data throughstat.node
.getStat
, through this function you can getstat
, the only parameter can beid, node, stat
. This function is in the return object ofuseHeTree
:const {getStat} = useHeTree({...})
.- The code examples below have preview. These examples can be directly copied for use. Pay attention to the the highlighted lines in code.
- The code examples below use the
tsx
format, if you need thejs
format, you can use any ts js online converter.
Use Flat Data
import { useHeTree, sortFlatData } from "he-tree-react";
import { useState } from 'react';
export default function BasePage() {
const keys = { idKey: 'id', parentIdKey: 'parent_id' };
const [data, setdata] = useState(() => sortFlatData([
{
id: 1,
parent_id: null,
name: "Root Category",
},
{
id: 2,
parent_id: 1,
name: "Technology",
},
{
id: 5,
parent_id: 2,
name: "Hardware",
},
{
id: 10,
parent_id: 5,
name: "Computer Components",
},
{
id: 4,
parent_id: 2,
name: "Programming",
},
{
id: 8,
parent_id: 4,
name: "Python",
},
{
id: 3,
parent_id: 1,
name: "Science",
},
{
id: 7,
parent_id: 3,
name: "Biology",
},
{
id: 6,
parent_id: 3,
name: "Physics",
},
], keys));
const { renderTree } = useHeTree({
...keys,
data,
dataType: 'flat',
onChange: setdata,
renderNode: ({ id, node, open, checked }) => <div>
{node.name}
</div>,
})
return <div>
{renderTree({ style: { width: '300px', border: '1px solid #555', padding: '20px' } })}
</div>
}
Use Tree-shaped Data
import { useHeTree } from "he-tree-react";
import { useState } from 'react';
export default function BasePage() {
const [data, setdata] = useState(() => [
{
id: 1,
name: "Root Category",
children: [
{
id: 2,
name: "Technology",
children: [
{
id: 5,
name: "Hardware",
children: [
{
id: 10,
name: "Computer Components",
children: [],
},
],
},
{
id: 4,
name: "Programming",
children: [
{
id: 8,
name: "Python",
children: [],
},
],
},
],
},
{
id: 3,
name: "Science",
children: [
{
id: 7,
name: "Biology",
children: [],
},
{
id: 6,
name: "Physics",
children: [],
},
],
},
],
},
]);
const { renderTree } = useHeTree({
data,
dataType: 'tree',
childrenKey: 'children',
onChange: setdata,
renderNode: ({ id, node, open, checked }) => <div>
{node.name}
</div>,
})
return <div>
{renderTree({ style: { width: '300px', border: '1px solid #555', padding: '20px' } })}
</div>
}
Custom Drag Trigger Element
You can add the draggable
attribute to any child element of the node.
import { useHeTree, sortFlatData } from "he-tree-react";
import { useState } from 'react';
export default function BasePage() {
const keys = { idKey: 'id', parentIdKey: 'parent_id' };
// prettier-ignore
const [data, setdata] = useState(() => sortFlatData([{ id: 1, parent_id: null, name: "Root Category", }, { id: 2, parent_id: 1, name: "Technology", }, { id: 5, parent_id: 2, name: "Hardware", }, { id: 10, parent_id: 5, name: "Computer Components", }, { id: 4, parent_id: 2, name: "Programming", }, { id: 8, parent_id: 4, name: "Python", }, { id: 3, parent_id: 1, name: "Science", }, { id: 7, parent_id: 3, name: "Biology", }, { id: 6, parent_id: 3, name: "Physics", },], keys));
const { renderTree } = useHeTree({
...keys,
data,
dataType: 'flat',
onChange: setdata,
renderNode: ({ id, node, open, checked, draggable }) => <div>
<button draggable={draggable}>Drag</button>
{node.name}
</div>,
})
return <div>
{renderTree({ style: { width: '300px', border: '1px solid #555', padding: '20px' } })}
</div>
}
HTML and Style of Node
Node HTML:
<div
draggable="true"
data-key="1"
data-level="1"
data-node-box="true"
style="padding-left: 0px;"
>
<div>Node</div>
</div>
There are two div
above. Use the renderNode
option to control the rendering of the inner div. For example: renderNode: ({node}) => <div>{node.name}</div>
.
The outer div is called nodeBox
, don't modify its padding-left, padding-right
. Use the indent
option to control the indentation of the node. If you want to control the rendering of nodeBox
or the drag placeholder, you can use the renderNodeBox
option, which will override renderNode
. The standard renderNodeBox
is as follows:
renderNodeBox: ({ stat, attrs, isPlaceholder }) => (
<div {...attrs} key={attrs.key}>
{isPlaceholder ? (
<div
className="he-tree-drag-placeholder"
style={{ minHeight: "20px", border: "1px dashed blue" }}
/>
) : (
<div>{/* node area */}</div>
)}
</div>
);
Lines 4 to 7 are drag-and-drop placeholder. Line 9 is node.
Custom Drag Placeholder and Node Box
import { useHeTree, sortFlatData } from "he-tree-react";
import { useState } from 'react';
export default function BasePage() {
const keys = { idKey: 'id', parentIdKey: 'parent_id' };
// prettier-ignore
const [data, setdata] = useState(() => sortFlatData([{ id: 1, parent_id: null, name: "Root Category", }, { id: 2, parent_id: 1, name: "Technology", }, { id: 5, parent_id: 2, name: "Hardware", }, { id: 10, parent_id: 5, name: "Computer Components", }, { id: 4, parent_id: 2, name: "Programming", }, { id: 8, parent_id: 4, name: "Python", }, { id: 3, parent_id: 1, name: "Science", }, { id: 7, parent_id: 3, name: "Biology", }, { id: 6, parent_id: 3, name: "Physics", },], keys));
const { renderTree } = useHeTree({
...keys,
data,
dataType: 'flat',
onChange: setdata,
renderNodeBox: ({ stat, attrs, isPlaceholder }) => (
<div {...attrs} key={attrs.key}>
{isPlaceholder ? <div className="my-drag-placeholder">drop here</div>
: <div className="mynode">{stat.node.name}</div>
}
</div>
),
})
return <div>
{renderTree({ className: 'mytree', style: { width: '300px', border: '1px solid #555', padding: '20px' } })}
<style>{`
.mytree [data-node-box]{
padding: 5px 0;
}
.mytree [data-node-box]:hover{
background-color: #eee;
}
.mytree .he-tree-drag-placeholder{
height: 30px;
line-height: 30px;
text-align: center;
border: 1px dashed red;
}
.mynode{
padding-left:5px;
}
`}</style>
</div>
}
Open & Close
- Use the
openIds
option to indicate the open nodes. - The
open
status of the node can be obtained throughstat.open
. - The
allIds
returned byuseHeTree
contains the ids of all nodes. - This library exports methods that can expand all parents of one or multiple nodes. For flat data:
openParentsInFlatData
. For tree data:openParentsInTreeData
.
import { useHeTree, sortFlatData, openParentsInFlatData } from "he-tree-react";
import type { Id } from "he-tree-react";
import { useState } from 'react';
export default function BasePage() {
const keys = { idKey: 'id', parentIdKey: 'parent_id' };
// prettier-ignore
const [data, setdata] = useState(() => sortFlatData([{ id: 1, parent_id: null, name: "Root Category", }, { id: 2, parent_id: 1, name: "Technology", }, { id: 5, parent_id: 2, name: "Hardware", }, { id: 10, parent_id: 5, name: "Computer Components", }, { id: 4, parent_id: 2, name: "Programming", }, { id: 8, parent_id: 4, name: "Python", }, { id: 3, parent_id: 1, name: "Science", }, { id: 7, parent_id: 3, name: "Biology", }, { id: 6, parent_id: 3, name: "Physics", },], keys));
const [openIds, setopenIds] = useState<Id[] | undefined>([]);
const handleOpen = (id: Id, open: boolean) => {
if (open) {
setopenIds([...(openIds || allIds), id]);
} else {
setopenIds((openIds || allIds).filter((i) => i !== id));
}
}
const { renderTree, allIds } = useHeTree({
...keys,
data,
dataType: 'flat',
onChange: setdata,
openIds,
renderNode: ({ id, node, open, checked, draggable }) => <div>
<button onClick={() => handleOpen(id, !open)}>{open ? '-' : '+'}</button>
{node.name} - {id}
</div>,
})
return <div>
<button onClick={() => setopenIds(allIds)}>Open All</button>
<button onClick={() => setopenIds([])}>Close All</button>
<button onClick={() => setopenIds(openParentsInFlatData(data, openIds || allIds, 8, keys))}>Open 'Python'</button>
<button onClick={() => setopenIds(openParentsInFlatData(data, [], 8, keys))}>Only Open 'Python'</button>
{renderTree({ style: { width: '300px', border: '1px solid #555', padding: '20px' } })}
</div>
}
Checked
- Use the option
checkedIds
to indicate the checked nodes. - The
checked
status of this node can be obtained throughstat.checked
. - This library exports methods that can get
checkedIds
for one or more nodes after thechecked
status changes. Flat data:updateCheckedInFlatData
. Tree data: `updateCheckedInTreeData.- The update of this method to the node's
checked
is cascading. If you don't want to cascade updates, replace it with your own logic. - This method returns an array of length 2. The first item is all checked ids, and the second item is all semi-checked ids. If you don't need semi-checked, ignore the second item.
- Semi-checked, that is, there are child nodes that are checked or semi-checked, and there are child nodes that are not checked.
- The update of this method to the node's
import { useHeTree, sortFlatData, updateCheckedInFlatData } from "he-tree-react";
import type { Id } from "he-tree-react";
import { useState } from 'react';
export default function BasePage() {
const keys = { idKey: 'id', parentIdKey: 'parent_id' };
// prettier-ignore
const [data, setdata] = useState(() => sortFlatData([{ id: 1, parent_id: null, name: "Root Category", }, { id: 2, parent_id: 1, name: "Technology", }, { id: 5, parent_id: 2, name: "Hardware", }, { id: 10, parent_id: 5, name: "Computer Components", }, { id: 4, parent_id: 2, name: "Programming", }, { id: 8, parent_id: 4, name: "Python", }, { id: 3, parent_id: 1, name: "Science", }, { id: 7, parent_id: 3, name: "Biology", }, { id: 6, parent_id: 3, name: "Physics", },], keys));
const [checkedIds, setcheckedIds] = useState<Id[]>([]);
const [semiCheckedIds, setsemiCheckedIds] = useState<Id[]>([]);
const handleChecked = (id: Id, checked: boolean) => {
const r = updateCheckedInFlatData(data, checkedIds, id, checked, keys);
setcheckedIds(r[0]);
setsemiCheckedIds(r[1]);
}
const { renderTree } = useHeTree({
...keys,
data,
dataType: 'flat',
onChange: setdata,
checkedIds,
renderNode: ({ id, node, open, checked, draggable }) => <div>
<input type="checkbox" checked={checked || false} onChange={() => handleChecked(id, !checked)} />
{node.name} - {id}
</div>,
})
return <div>
Checked: {JSON.stringify(checkedIds)} <br />
Semi-Checked: {JSON.stringify(semiCheckedIds)}
{renderTree({ style: { width: '300px', border: '1px solid #555', padding: '20px' } })}
</div>
}
draggable & droppable
Use the following options to control:
canDrag
, whether the node can be dragged.canDrop
, whether the node can be dropped.canDropToRoot
, whether the tree root can be dropped.
import { useHeTree, sortFlatData } from "he-tree-react";
import { useState } from 'react';
export default function BasePage() {
const keys = { idKey: 'id', parentIdKey: 'parent_id' };
// prettier-ignore
const [data, setdata] = useState(() => sortFlatData([{ id: 2, parent_id: 1, name: "Technology", }, { id: 5, parent_id: 2, name: "Hardware", }, { id: 10, parent_id: 5, name: "Computer Components", }, { id: 4, parent_id: 2, name: "Programming", }, { id: 8, parent_id: 4, name: "Python", }, { id: 3, parent_id: 1, name: "Science", }, { id: 7, parent_id: 3, name: "Biology", }, { id: 6, parent_id: 3, name: "Physics", },], keys));
const { renderTree } = useHeTree({
...keys,
data,
dataType: 'flat',
onChange: setdata,
renderNode: ({ id, node, open, checked, draggable }) => <div>
{node.name} - {id}
</div>,
canDrag: ({ id }) => id === 2 ? true : (id === 3 ? false : undefined),
canDrop: ({ id }) => id === 3 ? true : (id === 2 ? false : undefined),
canDropToRoot: (index) => false,
})
return <div>
{renderTree({ style: { width: '300px', border: '1px solid #555', padding: '20px' } })}
</div>
}
- The root node cannot be dropped.
Technology
and its sub-nodes can be dragged.Science
and its sub-nodes cannot be dragged.Science
and its sub-nodes can be dropped.Technology
and its sub-nodes cannot be dropped.
Open when dragging over
Use the following options to control:
dragOpen
, whether to enable, defaultfalse
.dragOpenDelay
, delay, default600
milliseconds.onDragOpen
, the function called when the node is opened.
import { useHeTree, sortFlatData } from "he-tree-react";
import type { Id } from "he-tree-react";
import { useState } from 'react';
export default function BasePage() {
const keys = { idKey: 'id', parentIdKey: 'parent_id' };
// prettier-ignore
const [data, setdata] = useState(() => sortFlatData([{ id: 1, parent_id: null, name: "Root Category", }, { id: 2, parent_id: 1, name: "Technology", }, { id: 5, parent_id: 2, name: "Hardware", }, { id: 10, parent_id: 5, name: "Computer Components", }, { id: 4, parent_id: 2, name: "Programming", }, { id: 8, parent_id: 4, name: "Python", }, { id: 3, parent_id: 1, name: "Science", }, { id: 7, parent_id: 3, name: "Biology", }, { id: 6, parent_id: 3, name: "Physics", },], keys));
const [openIds, setopenIds] = useState<Id[] | undefined>([1, 3]);
const handleOpen = (id: Id, open: boolean) => {
if (open) {
setopenIds([...(openIds || allIds), id]);
} else {
setopenIds((openIds || allIds).filter((i) => i !== id));
}
}
const { renderTree, allIds } = useHeTree({
...keys,
data,
dataType: 'flat',
onChange: setdata,
openIds,
renderNode: ({ id, node, open, checked, draggable }) => <div>
<button onClick={() => handleOpen(id, !open)}>{open ? '-' : '+'}</button>
{node.name} - {id}
</div>,
dragOpen: true,
onDragOpen(stat) {
handleOpen(stat.id, true)
},
})
return <div>
{renderTree({ style: { width: '300px', border: '1px solid #555', padding: '20px' } })}
</div>
}
Update Data
Due to the immutable nature of React, it is difficult to update flat data and tree data. For flat data, this library provides two methods to add nodes or delete nodes. If you want to perform more complex operations, or update tree data, it is recommended that you use immer
.
npm install immer use-immer
pnpm add immer use-immer
yarn add immer use-immer
Update Flat Data
addToFlatData
. removeByIdInFlatData
. These 2 methods will modify original data, so pass copy to it, or use immer
.
import {
useHeTree, sortFlatData,
addToFlatData, removeByIdInFlatData
} from "he-tree-react";
import type { Id } from "he-tree-react";
import { useRef, useState } from 'react';
export default function BasePage() {
const keys = { idKey: 'id', parentIdKey: 'parent_id' };
// prettier-ignore
const [data, setdata] = useState(() => sortFlatData([{ id: 1, parent_id: null, name: "Root Category", }, { id: 2, parent_id: 1, name: "Technology", }, { id: 5, parent_id: 2, name: "Hardware", }, { id: 10, parent_id: 5, name: "Computer Components", }, { id: 4, parent_id: 2, name: "Programming", }, { id: 8, parent_id: 4, name: "Python", }, { id: 3, parent_id: 1, name: "Science", }, { id: 7, parent_id: 3, name: "Biology", }, { id: 6, parent_id: 3, name: "Physics", },], keys));
const add = (pid: Id) => {
let id = parseInt(Math.random().toString().substring(2, 5));
let newData = [...data];
addToFlatData(newData, { id, parent_id: pid as number, name: "New" }, 0, keys)
setdata(newData);
}
const remove = (id: Id) => {
let newData = [...data];
removeByIdInFlatData(newData, id as number, keys)
setdata(newData);
}
const initialData = useRef<typeof data>();
initialData.current = initialData.current || data;
const { renderTree } = useHeTree({
...keys,
data,
dataType: 'flat',
onChange: setdata,
renderNode: ({ id, node, draggable }) => <div>
<button draggable={draggable}>👉</button>
{node.name} - {id} -
<button onClick={() => add(id)}>+</button>
<button onClick={() => remove(id)}>-</button>
</div>,
})
return <div>
<button onClick={() => setdata(initialData.current!)}>Restore</button>
{renderTree({ style: { width: '300px', border: '1px solid #555', padding: '20px' } })}
</div>
}
Update Flat Data with immer
Note, here we use useImmer
instead of React's useState
.
import {
useHeTree, sortFlatData,
addToFlatData, removeByIdInFlatData
} from "he-tree-react";
import type { Id } from "he-tree-react";
import { useRef } from 'react';
import { useImmer } from "use-immer";
export default function BasePage() {
const keys = { idKey: 'id', parentIdKey: 'parent_id' };
// prettier-ignore
const [data, setdata] = useImmer(() => sortFlatData([{ id: 1, parent_id: null, name: "Root Category", }, { id: 2, parent_id: 1, name: "Technology", }, { id: 5, parent_id: 2, name: "Hardware", }, { id: 10, parent_id: 5, name: "Computer Components", }, { id: 4, parent_id: 2, name: "Programming", }, { id: 8, parent_id: 4, name: "Python", }, { id: 3, parent_id: 1, name: "Science", }, { id: 7, parent_id: 3, name: "Biology", }, { id: 6, parent_id: 3, name: "Physics", },], keys));
const add = (pid: Id) => {
let id = parseInt(Math.random().toString().substring(2, 5));
setdata(draft => {
addToFlatData(draft, { id, parent_id: pid as number, name: "New" }, 0, keys)
});
}
const remove = (id: Id) => {
setdata(draft => {
removeByIdInFlatData(draft, id as number, keys)
})
}
const edit = (id: Id) => {
let newName = prompt("Enter new name")
setdata(draft => {
if (newName) {
draft.find(node => node.id === id)!.name = newName
}
})
}
const initialData = useRef<typeof data>();
initialData.current = initialData.current || data;
const { renderTree } = useHeTree({
...keys,
data,
dataType: 'flat',
onChange: setdata,
renderNode: ({ id, node, draggable }) => <div>
<button draggable={draggable}>👉</button>
{node.name} - {id} -
<button onClick={() => add(id)}>+</button>
<button onClick={() => remove(id)}>-</button>
<button onClick={() => edit(id)}>Edit</button>
</div>,
})
return <div>
<button onClick={() => setdata(initialData.current!)}>Restore</button>
{renderTree({ style: { width: '300px', border: '1px solid #555', padding: '20px' } })}
</div>
}
Update Tree Data with immer
Note, here we use useImmer
instead of React's useState
. findTreeData
is like Array.prototype.find
.
import { useHeTree, findTreeData } from "he-tree-react";
import type { Id } from "he-tree-react";
import { useRef } from 'react';
import { useImmer } from "use-immer";
export default function BasePage() {
const CHILDREN = 'children'
const keys = { idKey: 'id', childrenKey: CHILDREN };
// prettier-ignore
const [data, setdata] = useImmer(() => [{ id: 1, name: "Root Category", children: [{ id: 2, name: "Technology", children: [{ id: 5, name: "Hardware", children: [{ id: 10, name: "Computer Components", children: [], },], }, { id: 4, name: "Programming", children: [{ id: 8, name: "Python", children: [], },], },], }, { id: 3, name: "Science", children: [{ id: 7, name: "Biology", children: [], }, { id: 6, name: "Physics", children: [], },], },], },]);
const add = (pid: Id) => {
let id = parseInt(Math.random().toString().substring(2, 5));
setdata(draft => {
findTreeData(draft, (node) => node.id === pid, CHILDREN)![CHILDREN].unshift({ id, name: "New", [CHILDREN]: [], })
})
}
const remove = (id: Id, pid: Id | null) => {
setdata(draft => {
const children = findTreeData(draft, (node,) => node.id === pid, CHILDREN)![CHILDREN]
children.splice(children.findIndex(t => t.id === id), 1)
})
}
const edit = (id: Id) => {
let newName = prompt("Enter new name")
setdata(draft => {
if (newName) {
findTreeData(draft, (node) => node.id === id, CHILDREN)!.name = newName
}
})
}
const initialData = useRef<typeof data>();
initialData.current = initialData.current || data;
const { renderTree } = useHeTree({
...keys,
data,
dataType: 'tree',
onChange: setdata,
renderNode: ({ id, pid, node, draggable }) => <div>
<button draggable={draggable}>👉</button>
{node.name} - {id} -
<button onClick={() => add(id)}>+</button>
<button onClick={() => remove(id, pid)}>-</button>
<button onClick={() => edit(id)}>Edit</button>
</div>,
})
return <div>
<button onClick={() => setdata(initialData.current!)}>Restore</button>
{renderTree({ style: { width: '300px', border: '1px solid #555', padding: '20px' } })}
</div>
}
Drag from External
Related options:
onExternalDragOver
: Indicate whether to handle external drag.onExternalDrop
: Callback when drop from external.
import { useHeTree, sortFlatData, addToFlatData } from "he-tree-react";
import { useImmer } from "use-immer";
export default function BasePage() {
const keys = { idKey: 'id', parentIdKey: 'parent_id' };
// prettier-ignore
const [data, setdata] = useImmer(() => sortFlatData([{ id: 1, parent_id: null, name: "Root Category", }, { id: 2, parent_id: 1, name: "Technology", }, { id: 5, parent_id: 2, name: "Hardware", }, { id: 10, parent_id: 5, name: "Computer Components", }, { id: 4, parent_id: 2, name: "Programming", }, { id: 8, parent_id: 4, name: "Python", }, { id: 3, parent_id: 1, name: "Science", }, { id: 7, parent_id: 3, name: "Biology", }, { id: 6, parent_id: 3, name: "Physics", },], keys));
const { renderTree, allIds } = useHeTree({
...keys,
data,
dataType: 'flat',
onChange: setdata,
renderNode: ({ id, node, open, checked, draggable }) => <div>
{node.name} - {id}
</div>,
onExternalDragOver: (e) => true,
onExternalDrop: (e, parentStat, index) => {
setdata(draft => {
const newNode = { id: 100 + data.length, parent_id: parentStat?.id ?? null, name: "New Node" }
addToFlatData(draft, newNode, index, keys)
})
},
})
return <div>
<button draggable={true}>Drag me in to the tree</button>
{renderTree({ style: { width: '300px', border: '1px solid #555', padding: '20px' } })}
</div>
}
Big Data
Use option virtual
to enable virtual list. Remember to set height for tree.
import { useHeTree, sortFlatData } from "he-tree-react";
import type { Id } from "he-tree-react";
import { useState } from 'react';
export default function BasePage() {
const keys = { idKey: 'id', parentIdKey: 'pid' };
// prettier-ignore
const [data, setdata] = useState(() => sortFlatData(createData(), keys));
const [openIds, setopenIds] = useState<Id[] | undefined>([]);
const handleOpen = (id: Id, open: boolean) => {
if (open) {
setopenIds([...(openIds || allIds), id]);
} else {
setopenIds((openIds || allIds).filter((i) => i !== id));
}
}
const { renderTree, allIds } = useHeTree({
...keys,
data,
dataType: 'flat',
onChange: setdata,
openIds,
virtual: true,
renderNode: ({ id, node, open, checked, draggable }) => <div>
<button onClick={() => handleOpen(id, !open)}>{open ? '-' : '+'}</button>
{id}
</div>,
})
return <div>
{renderTree({ style: { width: '300px', height: '300px', border: '1px solid #555', padding: '20px' } })}
</div>
}
// generate 10000 nodes
function createData() {
const genId = () => result.length
const result: { id: number, pid: number | null }[] = [];
for (let i = 0; i < 1000; i++) {
let id1 = genId()
result.push({ id: id1, pid: null })
for (let j = 0; j < 4; j++) {
result.push({ id: genId(), pid: id1 })
}
let id2 = genId()
result.push({ id: id2, pid: null })
for (let j = 0; j < 4; j++) {
result.push({ id: genId(), pid: id2 })
}
}
return result;
}
Touch & Mobile Device
It is based on HTML5 Drag and Drop API. So it works in any device that supports Drag and Drop API. For others, you can try Drag and Drop API polyfill.
Notice
In mobile, user need touch and hold to trigger drag.
Others
- Option
direction
: from right to left. - Option
customDragImage
: custom drag image. - Option
rootId
: the parent id of root nodes in flat data. - Option
keepPlaceholder
: whether to retain the drag placeholder node when dragging outside the tree. Default isfalse
. - Function
scrollToNode
: Scroll to a node.