React DnD TreeView

repository·next·Indexed 20 days ago

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

A draggable and droppable React-based treeview component (v3.5.4) that uses render props for full customization of node appearance. It integrates with react-dnd and supports multiple backends including MultiBackend, HTML5Backend, and TouchBackend. Key features include custom drag handles, external drag source support, manual sorting with placeholders, and programmatic control over node open/close states.

Tokens
8.4K
Snippets
28
Records
31
Agent score
68%

What's inside @minoru/react-dnd-treeview

  1. Define the Tree Data Structure

    next

    The tree prop expects an array of node objects. Each node must have an id, parent, and text. You can optionally include droppable to allow children and data to pass custom properties to the render function.

    [
      {
        "id": 1,
        "parent": 0,
        "droppable": true,
        "text": "Folder 1",
        "data": {
          "fileType": "csv"
        }
      },
      {
        "id": 2,
        "parent": 1,
        "text": "File 1-1"
      }
    ]
  2. Allow external drag sources

    next

    To allow dropping files or elements from outside the DndProvider into the tree:

    1. Set extraAcceptTypes to include the drag type of the external source (e.g., NativeTypes.FILE from react-dnd-html5-backend).
    2. In onDrop, use options.monitor to access the dropped item's data.

    Example for dropping files:

    import { NativeTypes } from "react-dnd-html5-backend";
    
    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,
        }));
    
        return [...tree, ...nodes];
      }
      return tree;
    };
    
    return (
      <Tree
        {...props}
        tree={treeData}
        extraAcceptTypes={[NativeTypes.FILE]}
        onDrop={handleDrop}
      />
    );
  3. Migrate from v1.x to v2.x: Install and configure react-dnd

    next

    Starting from version 2.x, react-dnd is no longer bundled with @minoru/react-dnd-treeview. You must manually install react-dnd and provide a DndProvider to wrap your Tree component.

    To set up the provider with the recommended backend, import DndProvider, MultiBackend, and getBackendOptions from @minoru/react-dnd-treeview and pass them to the provider.

    import { DndProvider } from "react-dnd";
    import {
      Tree,
      MultiBackend,
      getBackendOptions,
    } from "@minoru/react-dnd-treeview";
    
    function App() {
      return (
        <DndProvider backend={MultiBackend} options={getBackendOptions()}>
          <Tree {...props} />
        </DndProvider>
      );
    }
  4. Migrate from v2.x to v3.x: Handle optional drag source properties

    next

    In version 3.x, the dragSourceId and dragSource properties within the options object of the onDrop callback are now optional. If the drag source is an external element (like a file or selected text) outside of the DndProvider, these values will be undefined.

    If your onDrop logic relies on options.dragSource or options.dragSourceId, you must add a null/undefined check to prevent runtime errors.

    <Tree
      {...someProps}
      onDrop={(tree, options) => {
        // Use optional chaining or an explicit check
        if (options.dragSource) {
          console.log(options.dragSource.id);
        }
        // or
        console.log(options.dragSource?.id);
      }}
    />
  5. Manual sort with placeholders

    next

    To enable manual sorting (where users can see where a node will land), you must:

    1. Set sort={false}.
    2. Set insertDroppableFirst={false}.
    3. Implement placeholderRender to show a visual indicator.
    4. Use dropTargetOffset to define the active drop range.
    5. Use canDrop to allow specific movement patterns.
    <Tree
      {...props}
      tree={treeData}
      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} />
      )}
    />
  6. Configure DnD Backends

    next

    You can choose different backends depending on your device support needs:

    • MultiBackend: Supports both touch and pointer devices. Requires getBackendOptions() to configure sub-backends.
    • HTML5Backend: Standard for desktop/pointer devices.
    • TouchBackend: For touch-based devices.

    When using MultiBackend, you can pass specific options for both touch and html5 via getBackendOptions(multiOptions).

    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 "@minoru/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)}
        >
          <Tree {...someProps} />
        </DndProvider>
      );
    }
  7. Style the Tree component with CSS classes

    next

    You can style the tree structure using the classes prop. This prop accepts an object where keys represent specific areas of the tree. Some keys support callback functions for dynamic styling based on node properties like depth.

    Available Class Keys:

    • root: Top-level container (ul).
    • container: Element wrapping nodes of the same hierarchy (ul).
    • listItem: Element wrapping each node (li). Supports a function: (node, options) => string.
    • dropTarget: Area that can be dropped into during dragging.
    • draggingSource: The node currently being dragged.
    • placeholder: Element wrapping the placeholder (li).
    <Tree
      {...props}
      classes={{
        root: "my-root-classname",
        dragOver: "my-dragover-classname",
        listItem: (node, options) => {
          return options.depth === 0
            ? "my-listitem-root-classname"
            : "my-listitem-classname";
        },
      }}
    />
  8. Quickstart: Basic TreeView Usage

    next

    To use the Tree component, wrap it in a DndProvider from react-dnd. You must provide a backend (like MultiBackend, HTML5Backend, or TouchBackend) and an onDrop handler to update your tree state. The render prop allows you to define how each node looks, providing access to depth, isOpen, and onToggle.

    import { useState } from "react";
    import {
      Tree,
      getBackendOptions,
      MultiBackend,
    } from "@minoru/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>
      );
    }
  9. Implement a custom drag handle

    next

    By default, the entire node is draggable. To restrict dragging to a specific element (like an icon), use the handleRef provided in the render function's options and assign it to the desired element's ref attribute.

    <Tree
      {...props}
      render={(node, { handleRef }) => (
        <div>
          <span ref={handleRef}>[Drag me]</span>
          {node.text}
        </div>
      )}
    />
  10. Override drop rules with canDrop

    next

    The canDrop callback allows you to define custom logic for where nodes can be dropped.

    • If it returns true or false, it overrides the default behavior (which prevents dropping a node into its own descendants).
    • If it returns undefined or nothing, the default rules apply.

    Warning: Overriding rules with true can lead to malformed trees (e.g., a parent being dropped into its own child) if not handled carefully.

    const canDrop = (currentTree, {
      dragSourceId,
      dropTargetId,
      dragSource,
      dropTarget
    }) => {
      return true; // or false, or undefined
    };
    
    return <Tree {...props} tree={treeData} canDrop={canDrop} />;
  11. Tree Component API Reference

    next

    The Tree component is the primary interface for the library. It accepts several props to control data, rendering, and drag-and-drop behavior.

    Required Props

    • tree: An array representing the tree structure (node data).
    • rootId: The number | string ID of the root node (the parent ID of the shallowest displayed node).
    • render: A function used to render each node.
    • onDrop: A function callback triggered when the tree state changes due to drag-and-drop.

    Key Optional Props

    • extraAcceptTypes: An array of drag types allowed to be dropped from outside the tree.
    • classes: An object containing CSS class names for specific tree areas.
    • sort: Controls child node order. true (default) sorts by text property; false follows array order; or a custom callback function.
    • initialOpen: boolean (if true, all parents open) or array of node IDs to initialize in the open state.
    • canDrag: Callback to determine if a node is draggable.
    • canDrop: Callback to override default drop rules.
    • dragPreviewRender: Custom render function for the drag preview (essential for touch device support).
    • placeholderRender: Render function for the drop destination placeholder.
    • dropTargetOffset: number specifying the effective drop range in pixels.