Direactree

Interactive example

A complete in-browser file explorer demonstrating selection, content preview, creation, renaming, deletion, and moving nodes.

File explorer demo

This example keeps its structure in React state and loads mock file contents from the public directory. Every change is local to your browser and can be returned to the initial state with Reset tree.

LIVE PLAYGROUNDEditable file explorer
  • ►Desktop
Select a file
Choose a file in the tree to preview its content.

Operations to try

Select and preview a file

Click a file such as project-notes.txt, config.json, or REST-API.md. onSelectedNodeChange receives its NodePath, and the example loads the content associated with the node ID.

Create a folder or file

Select a folder and use the toolbox create action. After a name is confirmed, onSave receives:

{
  actionType: "create",
  createType: "folder", // or "file"
  newName: "components",
  selectedNode: { /* selected parent path */ }
}

When no folder is selected, a create callback receives null; the new node can be added at the root.

Rename or delete a node

Select a node and choose the rename or delete action. Rename is finalized through onSave with actionType: "edit". Delete reports the selected NodePath through onDelete.

Move a node

Drag a file or folder onto another folder. onNodeMove receives the source node and its valid target. Dropping onto a file, onto the source itself, or into a descendant is rejected.

Implementation notes

The example uses recursive helpers for the three state operations required by a mutable explorer:

function findNode(nodes: TreeNode[], id: string): TreeNode | undefined {
  for (const node of nodes) {
    if (node.id === id) return node;
    const nested = node.children ? findNode(node.children, id) : undefined;
    if (nested) return nested;
  }
}

function removeNode(nodes: TreeNode[], id: string): TreeNode | undefined {
  const index = nodes.findIndex((node) => node.id === id);
  if (index >= 0) return nodes.splice(index, 1)[0];

  for (const node of nodes) {
    const removed = node.children ? removeNode(node.children, id) : undefined;
    if (removed) return removed;
  }
}

The onSave handler distinguishes creation from editing:

function handleSave(change: SaveProps) {
  if (change.actionType === "create") {
    // Construct a TreeNode and insert it under change.selectedNode.
  }

  if (change.actionType === "edit") {
    // Find change.selectedNode.id and update its name.
  }
}

The example clones the current structure before modifying it so React receives a new array instead of a mutated state reference.

Persisting changes

For a database-backed tree, treat callbacks as commands rather than immediately trusting the browser state:

async function handleSave(change: SaveProps) {
  const response = await fetch("/api/tree", {
    method: change.actionType === "create" ? "POST" : "PATCH",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(change),
  });

  const persistedStructure = await response.json();
  setStructure(persistedStructure);
}

Validate permissions, names, and parent relationships on the server. Direactree protects the UI from structurally invalid moves, but application-specific authorization remains your responsibility.

Inspect every payload live in the Props & callbacks reference.