Guide
Installation
npm install he-tree-reactpnpm add he-tree-reactyarn add he-tree-reactData 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,
nullmeans there is no parent. The order of flat data must be the same as the tree, you can use thesortFlatDatamethod 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 areidandparent_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.useHeTreeoptions include many callback functions, such asonChange, canDrop.isFunctionReactivecan be used to control whether to listen for changes to these callback functions. If your callback functions anddatachange synchronously, you do not need to enable this. Otherwise, you need to enable this and use React'suseCallbackoruseMemoto 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 toStatAPI.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
tsxformat, if you need thejsformat, 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
openIdsoption to indicate the open nodes. - The
openstatus of the node can be obtained throughstat.open. - The
allIdsreturned byuseHeTreecontains 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
checkedIdsto indicate the checked nodes. - The
checkedstatus of this node can be obtained throughstat.checked. - This library exports methods that can get
checkedIdsfor one or more nodes after thecheckedstatus changes. Flat data:updateCheckedInFlatData. Tree data: `updateCheckedInTreeData.- The update of this method to the node's
checkedis 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.
Technologyand its sub-nodes can be dragged.Scienceand its sub-nodes cannot be dragged.Scienceand its sub-nodes can be dropped.Technologyand its sub-nodes cannot be dropped.
Open when dragging over
Use the following options to control:
dragOpen, whether to enable, defaultfalse.dragOpenDelay, delay, default600milliseconds.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-immerpnpm add immer use-immeryarn add immer use-immerUpdate 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.