Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

@rainetian/react-dnd-treeview

minop1205169MIT1.0.0TypeScript support: included

A draggable / droppable React-based treeview component.

drag and drop, dnd, react, tree, treeview, component, headless component, touch support, sortable, hierarchical data

readme

基于原版修改,新增无动画版本,大大减小打包体积(约65%),其他功能与原版一致。

无动画版本引入:import * from '@raintian/react-dnd-treeview/pure'

React DnD TreeView

A draggable and droppable React treeview component.
You can use render props to create each node freely.

react-dnd-treeview

Demo and Examples

Some of the examples below use MUI(Material-UI) components, but TreeView does not depend on MUI, so you can use other libraries or your own custom components.

https://minop1205.github.io/react-dnd-treeview/

Breaking changes and migration

See Migration Guide for information on breaking changes and migrations between major versions.

Getting Started

Installation

$ npm i react-dnd @raintian/react-dnd-treeview

Usage

import { useState } from "react";
import {
  Tree,
  getBackendOptions,
  MultiBackend,
} from "@raintian/react-dnd-treeview";
import { DndProvider } from "react-dnd";
import initialData from "./sample-default.json";

function App() {
  const [treeData, setTreeData] = useState(initialData);
  const handleDrop = (newTreeData) => setTreeData(newTreeData);

  return (
    <DndProvider backend={MultiBackend} options={getBackendOptions()}>
      <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>
        )}
      />
    </DndProvider>
  );
}

Backends

MultiBackend is a backend to support both touch and pointer devices. If you only need support for one or the other, you can also use the backend provided by react-dnd-html5-backend or react-dnd-touch-backend.

import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";

function App() {
  return (
    <DndProvider backend={HTML5Backend}>
      <Tree {...someProps}> />
    </DndProvider>
  );
}
import { DndProvider } from "react-dnd";
import { TouchBackend } from "react-dnd-touch-backend";

function App() {
  return (
    <DndProvider backend={TouchBackend}>
      <Tree {...someProps}> />
    </DndProvider>
  );
}

Backend options

HTML5Backend, TouchBackend and MultiBackend allow setting the BackendOptions defined in react-dnd.

For more information on TouchBackend, please see here. (For the HTML5Backend option, only the rootElement option is available.)

import { DndProvider } from "react-dnd";
import { HTML5Backend, HTML5BackendOptions } from "react-dnd-html5-backend";
import {TouchBackend, TouchBackendOptions} from "react-dnd-touch-backend"
import {Tree, MultiBackend, getBackendOptions} from "@raintian/react-dnd-treeview"

const touchOptions: Partial<TouchBackendOptions> = {
  // some options
};

const html5Options: Partial<HTML5BackendOptions> = {
  rootElement: document.body,
  // some options
};

const multiOptions = {
  touch: touchOptions,
  html5: html5Options,
}

function App() {
  return (
    <DndProvider
      backend={MultiBackend}
      options={getBackendOptions(multiOptions)}

      // or
      // backend={HTML5Backend}
      // options={html5Options}

      // or
      // backend={TouchBackend}
      // options={touchOptions}
    >
      <Tree {...someProps}> />
    </DndProvider>
  );
}

Data Structure

To display the treeview, pass data with the following structure to the tree property of the Tree component.

Basic example

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,
    "text": "File 1-1"
  },
  {
    "id": 3,
    "parent": 1,
    "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,
    "text": "File 2-1-1"
  }
]

Optional data

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,
    "text": "File 1-1",
    "data": {
      "fileType": "csv",
      "fileSize": "0.5MB"
    }
  },
  {
    "id": 3,
    "parent": 1,
    "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,
    "text": "File 2-1-1",
    "data": {
      "fileType": "image",
      "fileSize": "2.1MB"
    }
  }
]

Node properties

Key Type Required Default Description
id number | string - Identifier of each node
parent number | string - Parent id of each node
text string - Node label
droppable boolean | false If true, child nodes will be accepted and it will be able to drop other node
data any | undefined Additional data to be injected into each node.
These data are available in the render props.

Component API

Props Type Required Default Description
tree array | The data representing the tree structure. An array of node data.
rootId number | string | The id of the root node. It is the parent id of the shallowest node displayed in the tree view.
extraAcceptTypes array | [] If allowing drop from outside the tree, the drag type of the drag source.
classes object | 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 | ul HTML tag for list.
listItemComponent string | li HTML tag for list items.
render function | The render function of each node.
Please refer to the Render prop section for more details about the render functions.
dragPreviewRender function | undefined Render function for customizing the drag preview.
See the Dragging Preview section for more information on customizing the drag preview

NOTE:
The default preview is not displayed on touch devices. Therefore, if you want to support touch devices, please define a custom preview in dragPreviewRender.
onDrop function | 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.
onDragStart function | undefined This event is fired when a node in the tree is started to be dragged. The event handler is passed the target node and a DragSourceMonitor object.
onDragEnd function | undefined This event is fired when a node in the tree is finished being dragged. The event handler is passed the target node and a DragSourceMonitor object.
onChangeOpen function | undefined Callback function to be called after the open/close state of a node is changed.
The function is passed an array of node IDs in the open state.
canDrop function | undefined A callback function to determine if a given node can be dropped to another node.
If nothing is returned (or if undefined is returned), the default rules are followed.
If it returns true or false, the default rules will be overridden and the droppable properties of each node will not be referenced.
This callback takes the current tree and the same option object that is passed to the onDrop callback.
See the canDrop callback section for more information.
canDrag function | undefined Callback function which should return true or false depending on if a give node should be draggable.
By default, all nodes are draggable.
sort function | boolean | true This property controls the order of the child nodes.
By default (true), they are sorted by the text property of each node.
If false, sorting is disabled. In this case, the nodes will follow the order of the array passed to the tree property.
It is also possible to customize the sorting by passing a callback function.
insertDroppableFirst boolean | true Specifies whether droppable nodes should be placed first in the list of child nodes.
enableAnimateExpand boolean | false Specifies whether use animation when toggle expanding the node.
placeholderRender function | undefined Render function for the drop destination placeholder. By default, placeholder is not displayed.
See the Manual sort with placeholder section for more information on using placeholder.
placeholderComponent string | li HTML tag for placeholder.
dropTargetOffset number | 0 Effective drop range of a droppable node. It is specified in pixels from the top or bottom of the node.
Used to insert a node anywhere using placeholders.

See the Manual sort with placeholder placeholder section for more information on using placeholder.
initialOpen boolean | array | 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. When this prop is updated the open state of the tree is also reset.
rootProps object | undefined Properties to be passed to the root element (by default, ul tag), excluding the ref and role property.

Render prop

To render each tree node, please pass a render function to the render property.

<Tree
  {...props}
  render={(node, { depth, isOpen, draggable, 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 is not true, isOpen is always false.
options.draggable boolean Indicates whether this node is draggable or not.
options.hasChild boolean Flag indicating whether or not the node has children. It is true if the node has children, false otherwise.
options.isDragging boolean Flag indicating whether this node is being dragged or not.
options.isDropTarget boolean Flag indicating whether or not this node is a drop target.
options.containerRef React.RefObject Reference to the HTML element (default: li tag) that wraps the custom node.
options.handleRef React.RefObject Reference to the HTML element you wish to set as a drag handle. It is used by assigning it to the ref attribute of the element you want to set as a handle.By default, no handle is set and the entire node is draggable.
See Drag handle for details.
options.onToggle function An event handler for the open/close button of a node.

Drag handle

By default, the entire node is draggable, but the handleRef render option allows the node to be dragged only by the specified handle, as in the following example.

<Tree
  {...props}
  render={(node, { handleRef }) => (
    <div>
      <span ref={handleRef}>[Drag me]</span>
      {node.text}
    </div>
  )}
/>

Dragging preview

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.

NOTE:
The default preview is not displayed on touch devices.
Therefore, if you want to support touch devices, please define a custom preview in dragPreviewRender.

<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.

onDrop callback

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 | undefined node id of the dragging source.
If the drag source is an element external to DndProvider or a file or selected text, these will be undefined.
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.
If the drag source is an element external to DndProvider or a file or selected text, these will be undefined.
options.dropTarget object | undefined node item of the drop destination.
If the drop destination is the root node, it will be undefined
options.destinationIndex number | undefined When the sort property is false, it indicates the index to which the drag source in the tree data array should be moved.
When the sort property is true, it will be undefined.
options.relativeIndex number | undefined When the sort property is false, it indicates the relative index of the drop destination with respect to the parent node.
When the sort property is true, it will be undefined.
options.monitor object Provides various methods for accessing react-dnd's internal state, e.g. for accessing drag sources from outside DndProvider.
See this definition for details.

canDrop callback

By default, it allows dropping to any droppable node (or root node) except its own descendants. This callback can override the default rules.

If it returns nothing or returns undefined, the default rules will be applied. If it returns a boolean value, it will override the default rules and the droppable property of each node will no longer be referenced.

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 takes the same parameters as the onDrop callback, but the first parameter specifies the current tree.

const canDrop = (
  currentTree,
  { dragSourceId, dropTargetId, dragSource, dropTarget }
) => {
  return true;

  // or

  return false;

  // or

  return;

  // or

  return undefined;
};

return <Tree {...props} tree={treeData} canDrop={canDrop} />;

NOTE:
When overriding the default rules by returning true or false, be careful of inconsistencies in the tree structure.
For example, if you allow dropping from a parent node to a child node as shown in the figure below, inconsistency will occur and the tree will collapse.

malformed tree

Manual sort with placeholder

By default, nodes are automatically sorted and cannot be sorted manually, but by combining some APIs, you can sort them manually and display placeholders as follows.

placeholder_sample

The following is an example (excerpt) of the implementation of manual sort of nodes and placeholder display.

import { CustomPlaceholder } from "./CustomPlaceholder";
import styles from "./App.module.css";

function App() {
  const [treeData, setTreeData] = useState(SampleData);
  const handleDrop = (newTree) => setTreeData(newTree);

  return (
    <Tree
      {...props}
      tree={treData}
      onDrop={handleDrop}
      classes={{
        placeholder: styles.placeholder,
      }}
      sort={false}
      insertDroppableFirst={false}
      canDrop={(tree, { dragSource, dropTargetId }) => {
        if (dragSource?.parent === dropTargetId) {
          return true;
        }
      }}
      dropTargetOffset={5}
      placeholderRender={(node, { depth }) => (
        <CustomPlaceholder node={node} depth={depth} />
      )}
    />;
  );
}

External drag source

To drop elements outside the tree into the tree, extraAcceptTypes must be set.

If the external drag source is under a DndProvider, set the type and item using useDrag in react-dnd for that element. add the external drag source type to extraAcceptTypes so it can be dropped in the tree.

If the external drag source is an element or file external to DndProvider, adding the type defined in react-dnd-html5-backend to extraAcceptTypes will allow dropping into the tree.

In this case, the onDrop callback will access the dropped element via options.monitor and add the new node to the data array of tree, as in the following example.

import { NativeTypes } from "react-dnd-html5-backend";

function App() {
  const [treeData, setTreeData] = useState(SampleData);
  const [lastId, setLastId] = useState(100);

  const handleDrop = (tree, options) => {
    const { dropTargetId, monitor } = options;
    const itemType = monitor.getItemType();

    if (itemType === NativeTypes.FILE) {
      const files = monitor.getItem().files;
      const nodes = files.map((file, index) => ({
        id: lastId + index,
        parent: dropTargetId,
        text: file.name,
      }));

      const mergedTree = [...tree, ...nodes];
      setTree(mergedTree);
      setLastId(lastId + files.length);
    } else {
      setTree(tree);
    }
  };

  return (
    <Tree
      {...someProps}
      tree={treeData}
      extraAcceptTypes={[NativeTypes.FILE]}

      {/*
        extraAcceptTypes={[NativeTypes.URL]}
        extraAcceptTypes={[NativeTypes.TEXT]}
        extraAcceptTypes={[NativeTypes.HTML]}
      */}

      onDrop={handleDrop}
    />;
  );
}

Component styling

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).
listItem CSS class name to give to the element that wraps each node item (by default, li 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.
placeholder CSS class name to give to the element wrapping the placeholder (by default, li tag).

Usage to open / close methods

The open/close status of a node is managed within the Tree component, but the methods for opening and closing nodes are public, so they can be controlled from outside the Tree component.

const ref = useRef(null);

const handleOpenAll = () => ref.current.openAll();
const handleCloseAll = () => ref.current.closeAll();

// open /close method can be passed a node ID or an array of node IDs
const handleOpen = (nodeId) => ref.current.open(nodeId);
const handleClose = (nodeId) => ref.current.close(nodeId);

<Tree
  ref={ref}
  {...props}
>

<button onClick={handleOpenAll}>Open All Folders</button>
<button onClick={handleCloseAll}>Close All Folders</button>
<button onClick={handleOpen}>Open specific folder(s)</button>
<button onClick={handleClose}>Close specific folder(s)</button>

License

MIT.

changelog

Change Log

3.4.4

Mar 23, 2023

Fixed

  • Can not open a node with droppable set to false with the open method.

3.4.3

Mar 15, 2023

Fixed

  • openAll method and initialOpen API don't work for non-droppable node.

3.4.2

Mar 9, 2023

Fixed

  • canDrop API is not called when dragging an external node over a tree.

3.4.1

Dec 31, 2022

Fixed

  • Improved performance when enableAnimateExpand is true.

3.4.0

Nov 29, 2022

Added

  • Added enableAnimateExpand API to support open/close animation.

3.3.0

Nov 2, 2022

Added

  • Added relativeIndex to options passed onDrop callback.

3.2.2

Oct 31, 2022

Fixed

  • Nodes may not be draggable.

3.2.1

Oct 19, 2022

Fixed

  • The URL of the demo page listed in the README is incorrect.

3.2.0

Oct 05, 2022

Added

  • Added handleRef to Render params to support dragging with handle.
  • Added React 17.x and React DnD 15.x to peer dependencies.

Changed

  • Changed all parameters of getBackendOptions to optional.

Fixed

  • Rollup bundling process does not handle "~" alias.

3.1.0

Sep 08, 2022

Added

  • Added onDragStart and onDragend APIs for handling the start and end of node dragging.
  • Added Multiple drag sample.

3.0.0

Aug 12, 2022

Added

  • Nodes can now be added by dropping elements outside the tree, files, selected text, etc. into the tree.

Breaking changes

  • Among the options passed to the onDrop callback, dragSourceId and dragSource are now optional.
    If the drag source is an element external to DndProvider or a file or selected text, these will be undefined.

2.0.2

June 29, 2022

Changed

  • If the initialOpen property is updated, the open state of the tree will be updated.

2.0.1

June 15, 2022

Fixed

  • Error using require to import from react-dnd and react-dnd-html5-backend.

    Since react-dnd and react-dnd-html5-backend are now distributed only in ESM format, this package is also provided only in ESM format and the package type has been changed to module.

2.0.0

May 15, 2022

Added

  • Migrated examples to Storybook.

Breaking changes

  • Exclude react-dnd from this package

    react-dnd is no longer included in this package, so users of this package must separately install the latest version of react-dnd and import DndProvider.

1.6.1

Jan 07, 2022

Fixed

  • DragPreview component is momentarily displayed in the upper left corner of the screen after the drag operation is completed.

1.6.0

Nov 18, 2021

Added

  • Added isDragging and isDropTarget flags to the options passed to the render callback.
  • Added a reference to the wrapper element to the options passed to the render callback. (options.containerRef)

1.5.11

Nov 08, 2021

Added

  • If the sort property is set to false, add an destinationIndex property to the options passed to the onDrop callback to indicate where to move the node.
  • A utility function has been added to get descendant nodes.
  • Added demos for adding, removing, and duplicating nodes.

Changed

  • Update sample code of basic usage.
  • If the sort property is not false, then the order of the tree array passed to the onDrop callback will be preserved.
  • The new node array passed to the onDrop callback is no longer read-only.

1.5.10

Oct 27, 2021

Fixed

  • generic type cannot be used in sort callback.

1.5.9

Oct 07, 2021

Added

  • add rootProps API.

1.5.7

Oct 01, 2021

Fixed

  • react-dnd version is mismatch with dependent packages.

1.5.6

Sept 29, 2021

Fixed

  • dragging not possible after editing a node without hovering in Firefox or Safari.

1.5.4

Sept 24, 2021

Fixed

  • unable to install in npm version 7.

1.5.3

Sept 08, 2021

Added

  • add classes.listItem property for styling node wrapper.

1.5.1

Aug 22, 2021

Added

  • add onChangeOpen API.

1.5.0

Aug 19, 2021

Added

  • add APIs for manual sort and placeholder
  • add open / close methods for control open state of nodes.

Fixed

  • type inference for custom data types is not working.

1.4.3

July 17, 2021

Fixed

  • incorrect parameters passed to the canDrop callback.
  • mouse cursor becomes the default pointer when hovering over an area that cannot be dropped.

1.4.2

July 08, 2021

Added

  • add canDrag API to control dragging.

Fixed

  • unable to select text in a text field in a node by mouse dragging.

1.4.1

June 02, 2021

Added

  • support touch device.

1.3.2

Apr 22, 2021

Security

  • update all dependencies.

1.3.1

Apr 20, 2021

Fixed

  • fail drop into root container when rootId is string.
  • dropTarget in canDrop parameter is incrrect.

1.3.0

Apr 12, 2021

Added

  • add canDrop API.

1.2.0

Apr 05, 2021

Added

  • add hasChild argument to render function.

1.1.0

Apr 02, 2021

Added

  • add initialOpen API to control the opening and closing state of the node in the initial state of the component.
  • add sort API to control the sorting of nodes.

1.0.0

Jan 25, 2021

Initial major release.