A draggable / droppable React-based treeview component.
You can use render props to create each node freely.
Some of the examples below use Material-UI components, but TreeView does not depend on Material-UI, so you can use other libraries or your own custom components.
- Minimum configuration (JavaScript | TypeScript)
- Custom node (JavaScript | TypeScript)
- Custom drag preview (JavaScript | TypeScript)
- Select node (JavaScript | TypeScript)
- Multiple selections(checkobx) (JavaScript | TypeScript)
- Opening and closing all nodes (JavaScript | TypeScript)
- Auto expand with drag over node (JavaScript | TypeScript)
- Initialize with open parents (JavaScript | TypeScript)
$ npm install --save @minoru/react-dnd-treeview
import { Tree } from "@minoru/react-dnd-treeview";
...
const [treeData, setTreeData] = useState(initialData);
const handleDrop = (newTreeData) => setTreeData(newTreeData);
<Tree
tree={treeData}
rootId={0}
onDrop={handleDrop}
render={(node, {depth, isOpen, onToggle}) => (
<div style={{marginLeft: depth * 10}}>
{node.droppable && (
<span onClick={onToggle}>
{isOpen ? "[-]" : "[+]"}
</span>
)}
{node.text}
</div>
)}
/>
In order to display the tree,
we need to pass the following data to the Tree
component
The minimal data structure for representing the tree is shown in the following example
[
{
"id": 1,
"parent": 0,
"droppable": true,
"text": "Folder 1"
},
{
"id": 2,
"parent": 1,
"droppable": false,
"text": "File 1-1"
},
{
"id": 3,
"parent": 1,
"droppable": false,
"text": "File 1-2"
},
{
"id": 4,
"parent": 0,
"droppable": true,
"text": "Folder 2"
},
{
"id": 5,
"parent": 4,
"droppable": true,
"text": "Folder 2-1"
},
{
"id": 6,
"parent": 5,
"droppable": false,
"text": "File 2-1-1"
}
]
If you want to pass custom properties to each node's rendering,
you can use the data
property.
[
{
"id": 1,
"parent": 0,
"droppable": true,
"text": "Folder 1"
},
{
"id": 2,
"parent": 1,
"droppable": false,
"text": "File 1-1",
"data": {
"fileType": "csv",
"fileSize": "0.5MB"
}
},
{
"id": 3,
"parent": 1,
"droppable": false,
"text": "File 1-2",
"data": {
"fileType": "pdf",
"fileSize": "4.8MB"
}
},
{
"id": 4,
"parent": 0,
"droppable": true,
"text": "Folder 2"
},
{
"id": 5,
"parent": 4,
"droppable": true,
"text": "Folder 2-1"
},
{
"id": 6,
"parent": 5,
"droppable": false,
"text": "File 2-1-1",
"data": {
"fileType": "image",
"fileSize": "2.1MB"
}
}
]
Key | Type | Required | Description |
---|---|---|---|
id | number | string | yes | Identifier of each node |
parent | number | string | yes | Parent id of each node |
droppable | boolean | yes | If true , child nodes will be accepted and other nodes can be dropped |
text | string | yes | Node label |
data | any | no | Additional data to be injected into each node. These data are available in the render props. |
Props | Type | Required | Default | Description |
---|---|---|---|---|
tree | array | yes | The data representing the tree structure. An array of node data. | |
rootId | number | string | yes | The id of the root node. It is the parent id of the shallowest node displayed in the tree view. | |
classes | object | no | undefined | A set of CSS class names to be applied to a specific area in the tree view. See the Component Styling section for more information. |
listComponent | string | no | ul | The HTML tag for the list. |
listItemComponent | string | no | li | HTML tag for list items. |
render | function | yes | The render function of each node. Please refer to the Render prop section for more details about the render functions. |
|
dragPreviewRender | function | no | undefined | Render function for customizing the drag preview. See the Dragging Preview section for more information on customizing the drag preview. |
onDrop | function | yes | Callback function for when the state of the tree is changed. The new data is passed as the argument. See the onDrop callback section for more information. |
|
canDrop | function | no | undefined | Callback function which should return true or false depending on if a give node should be droppable onto another node. The callback will receive the current tree and an options object which is the same as the one which would be passed to the onDrop callback. See the canDrop callback section for more information. |
sort | function | boolean | no | true | Passing false will disable sorting. Alternatively, pass a callback to use in place of the default sort callback. |
initialOpen | boolean | array | no | false | If true, all parent nodes will be initialized to the open state. If an array of node IDs is passed instead of the boolean value, only the specified node will be initialized in the open state. |
To render each tree node, please pass a render function to the render
property.
<Tree
{...props}
render={(node, {depth, isOpen, onToggle}) => (
<div style={{marginLeft: depth * 10}}>
{node.droppable && (
<span onClick={onToggle}>
{isOpen ? "[-]" : "[+]"}
</span>
)}
{node.text}
</div>
)}
/>
The arguments passed to the render function are as follows
Name | Type | Description |
---|---|---|
data | object | Node data. (an element in the tree data array) |
options.depth | number | The depth of the node hierarchy. |
options.isOpen | boolean | The open and closed state of the node. If droppable = false, isOpen is always false. |
options.hasChild | boolean | Flag indicating whether or not the node has children. It is true if the node has children, false otherwise. |
options.onToggle | function | An event handler for the open/close button of a node. |
By default, the drag preview is a screenshot of a DOM node.
The dragPreviewRender
property allows you to display a custom React component instead of a screenshot.
<Tree
{...props}
dragPreviewRender={(monitorProps) => {
const item = monitorProps.item;
return (
<div>
<p>{item.text}</p>
</div>
);
}}
/>
The data passed to dragPreviewRender
contains the following properties
Name | Type | Description |
---|---|---|
item | object | Node data. (an element in the tree data array) It also includes the ref property, which is a reference to the HTML element to be dragged. |
clientOffset | object | The client offset of the pointer during the dragging operation. It is in the format of {x: number, y: number} .If the item is not being dragged, it is set to null . |
If the tree is modified by drag-and-drop, the changes can be retrieved by the onDrop
callback.
const [treeData, setTreeData] = useState(initialTreeData);
const handleDrop = (newTree, {dragSourceId, dropTargetId, dragSource, dropTarget}) => {
// Do something
setTreeData(newTree);
};
return (
<Tree
{...props}
tree={treeData}
onDrop={handleDrop}
/>
);
The arguments passed to the onDrop callback function are as follows
Name | Type | Description |
---|---|---|
newTree | array | This data represents the updated TreeView. To redraw the modified TreeView, you need to set this data to the tree property. |
options.dragSourceId | number | string | node id of the dragging source |
options.dropTargetId | number | string | node id of the drop destination. If the drop destination is the root node, it will be the value of the rootId property. |
options.dragSource | object | node item of the dragging source |
options.dropTarget | object | undefined | node item of the drop destination. If the drop destination is the root node, it will be undefined |
This callback should return true if the node being dragged can be dropped onto a given node. If it returns false and the user drops the dragged node, no action will be taken and the onDrop callback will not be fired.
This callback accepts the same parameters as the onDrop callback except that the first parameter will be the current tree.
const canDrop = (currentTree, {dragSourceId, dropTargetId, dragSource, dropTarget}) => {
return true;
// or
return false;
};
return (
<Tree
{...props}
tree={treeData}
canDrop={canDrop}
/>
);
Note that this will replace the default behaviour which will not take any action when a drop would not result in a change to the tree structure or when dropping on a node would result in a malformed tree e.g. dropping a droppable node on itself as shown in the below graphic. Therefore, if you pass this callback, you may need to handle such cases.
You are free to define the styling of individual nodes in the tree in your Render props, but the rest of the tree can be styled by specifying the CSS class name for the classes
property.
<Tree
{...props}
classes={{
root: "my-root-classname",
dragOver: "my-dragover-classname",
}}
/>
You can use the following keys for the objects you pass to the classes
property. Neither key is required.
Name | Description |
---|---|
root | CSS class name to give to the top-level container element (by default, ul tag) that wraps all nodes. |
container | CSS class name to give to the element wrapping the list of nodes of the same hierarchy (by default, ul tag). |
dropTarget | CSS class name to give to the area that can be dropped during a node dragging operation. |
draggingSource | CSS class name to give to the node during the dragging operation. |
The open/close state of a node is managed within the Tree component, but methods are available outside the component to open and close all nodes.
const ref = useRef(null);
const handleOpenAll = () => ref.current.openAll();
const handleCloseAll = () => ref.current.closeAll();
<Tree
ref={ref}
{...props}
>
<button onClick={handleOpenAll}>Open All Folders</button>
<button onClick={handleCloseAll}>Close All Folders</button>
MIT.