3.4.4 • Published 10 months ago

@minoru/react-dnd-treeview v3.4.4

Weekly downloads
5
License
MIT
Repository
github
Last release
10 months ago

React DnD TreeView

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

react-dnd-treeview

Demo

Examples (on CodeSandbox)

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.

Getting Started

Installation

$ npm install --save @minoru/react-dnd-treeview

Usage

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>
  )}
/>

Data Structure

In order to display the tree,
we need to pass the following data to 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

KeyTypeRequiredDefaultDescription
idnumber | stringyes-Identifier of each node
parentnumber | stringyes-Parent id of each node
textstringyes-Node label
droppablebooleannofalseIf true, child nodes will be accepted and it will be able to drop other node
dataanynoundefinedAdditional data to be injected into each node.These data are available in the render props.

Component API

PropsTypeRequiredDefaultDescription
treearrayyesThe data representing the tree structure. An array of node data.
rootIdnumber | stringyesThe id of the root node. It is the parent id of the shallowest node displayed in the tree view.
classesobjectnoundefinedA set of CSS class names to be applied to a specific area in the tree view.See the Component Styling section for more information.
listComponentstringnoulThe HTML tag for the list.
listItemComponentstringnoliHTML tag for list items.
renderfunctionyesThe render function of each node.Please refer to the Render prop section for more details about the render functions.
dragPreviewRenderfunctionnoundefinedRender function for customizing the drag preview.See the Dragging Preview section for more information on customizing the drag previewNOTE:The default preview is not displayed on touch devices. Therefore, if you want to support touch devices, please define a custom preview in dragPreviewRender.
onDropfunctionyesCallback 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.
canDropfunctionnoundefinedCallback function which should return true or false depending on if a give node should be droppable onto another node.If the canDrop callback is given, the droppable property of each node will no longer be referenced.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.
sortfunction | booleannotruePassing false will disable sorting. Alternatively, pass a callback to use in place of the default sort callback.
initialOpenboolean | arraynofalseIf 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.

Render prop

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

NameTypeDescription
dataobjectNode data. (an element in the tree data array)
options.depthnumberThe depth of the node hierarchy.
options.isOpenbooleanThe open and closed state of the node.If droppable is not true, isOpen is always false.
options.hasChildbooleanFlag indicating whether or not the node has children. It is true if the node has children, false otherwise.
options.onTogglefunctionAn event handler for the open/close button of a node.

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

NameTypeDescription
itemobjectNode 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.
clientOffsetobjectThe 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

NameTypeDescription
newTreearrayThis data represents the updated TreeView.To redraw the modified TreeView, you need to set this data to the tree property.
options.dragSourceIdnumber | stringnode id of the dragging source
options.dropTargetIdnumber | stringnode id of the drop destination.If the drop destination is the root node, it will be the value of the rootId property.
options.dragSourceobjectnode item of the dragging source
options.dropTargetobject | undefinednode item of the drop destination.If the drop destination is the root node, it will be undefined

canDrop callback

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.

malformed tree

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.

NameDescription
rootCSS class name to give to the top-level container element (by default, ul tag) that wraps all nodes.
containerCSS class name to give to the element wrapping the list of nodes of the same hierarchy (by default, ul tag).
dropTargetCSS class name to give to the area that can be dropped during a node dragging operation.
draggingSourceCSS class name to give to the node during the dragging operation.

Usage to openAll, closeAll methods

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>

License

MIT.

3.5.0-alpha.0

10 months ago

3.4.4

1 year ago

3.4.3

1 year ago

3.4.2

1 year ago

3.4.2-alpha.0

1 year ago

3.4.0

1 year ago

3.4.1

1 year ago

3.4.1-alpha.0

1 year ago

3.2.2

2 years ago

3.2.1

2 years ago

3.2.0

2 years ago

3.2.0-alpha.4

2 years ago

3.2.0-alpha.5

2 years ago

3.2.0-alpha.0

2 years ago

3.2.0-alpha.1

2 years ago

3.2.0-alpha.2

2 years ago

3.2.0-alpha.3

2 years ago

3.3.0

2 years ago

3.0.0

2 years ago

3.1.0

2 years ago

3.1.0-alpha.1

2 years ago

2.0.2

2 years ago

2.0.1

2 years ago

3.0.0-beta.1

2 years ago

3.0.0-beta.3

2 years ago

3.0.0-beta.2

2 years ago

2.0.1-alpha.1

2 years ago

2.0.0-alpha.7

2 years ago

2.0.0-alpha.8

2 years ago

2.0.0-alpha.9

2 years ago

2.0.0-alpha.6

2 years ago

2.0.0

2 years ago

2.0.1-alpha.0

2 years ago

2.0.0-alpha.3

2 years ago

2.0.0-alpha.4

2 years ago

2.0.0-alpha.5

2 years ago

1.6.1

2 years ago

2.0.0-alpha.1

2 years ago

2.0.0-alpha.2

2 years ago

1.5.11-beta.1

3 years ago

1.6.0

2 years ago

1.6.0-alpha.1

2 years ago

1.6.0-alpha.0

2 years ago

1.6.0-alpha.2

2 years ago

1.5.11

2 years ago

1.5.11-beta.0

3 years ago

1.5.11-alpha.0

3 years ago

1.5.10

3 years ago

1.5.10-alpha.0

3 years ago

1.5.8-beta.0

3 years ago

1.5.9

3 years ago

1.5.7-alpha.4

3 years ago

1.5.7-alpha.1

3 years ago

1.5.7-alpha.0

3 years ago

1.5.7-alpha.3

3 years ago

1.5.7-alpha.2

3 years ago

1.5.7

3 years ago

1.5.5

3 years ago

1.5.6

3 years ago

1.5.4-alpha.1

3 years ago

1.5.4-alpha.2

3 years ago

1.5.4-alpha.0

3 years ago

1.5.4

3 years ago

1.5.3

3 years ago

1.5.3-beta.4

3 years ago

1.5.3-beta.2

3 years ago

1.5.3-beta.3

3 years ago

1.5.3-beta.1

3 years ago

1.5.3-beta.0

3 years ago

1.5.2

3 years ago

1.5.1

3 years ago

1.5.0

3 years ago

1.5.0-beta.0

3 years ago

1.4.3

3 years ago

1.4.2

3 years ago

1.4.2-beta.2

3 years ago

1.4.2-beta.1

3 years ago

1.4.2-beta.0

3 years ago

1.4.1

3 years ago

1.3.2

3 years ago

1.3.1

3 years ago

1.3.0

3 years ago

1.2.0

3 years ago

1.1.0

3 years ago

1.0.0

3 years ago

0.0.1

3 years ago