Skip to main content

Overview

React Apple Tree provides a comprehensive set of utility functions for tree manipulation. All operations are immutable and return new tree structures.
Import utility functions from the main package:
import { 
  addNodeUnderParent, 
  removeNode, 
  changeNodeAtPath 
} from 'react-apple-tree';

Adding Nodes

Add Under Parent

Add a new node as a child of a specific parent:
import { addNodeUnderParent } from 'react-apple-tree';

const { treeData: updatedTree, treeIndex } = addNodeUnderParent({
  treeData: currentTree,
  parentKey: 'parent-1',  // null for root level
  newNode: {
    id: 'new-node',
    title: 'New Child'
  },
  getNodeKey: ({ node }) => node.id,
  expandParent: true,      // Expand parent after adding
  addAsFirstChild: false   // Add at end (true for beginning)
});

setTreeData(updatedTree);
treeData
Array<TreeItem<T>>
required
The current tree data
parentKey
NodeKey | null
required
Key of parent node. Use null to add at root level
newNode
TreeItem<T>
required
The node to add
getNodeKey
GetNodeKeyFn<T>
required
Function to extract node keys
expandParent
boolean
default:"false"
Whether to expand parent after insertion
addAsFirstChild
boolean
default:"false"
Add as first child (true) or last child (false)

Insert at Depth and Index

Insert a node at a specific depth and position:
import { insertNode } from 'react-apple-tree';

const { treeData: newTree, treeIndex, path, parentNode } = insertNode({
  treeData: currentTree,
  depth: 2,              // Depth level (0 = root)
  minimumTreeIndex: 5,   // Insert after this index
  newNode: {
    id: 'new-node',
    title: 'Inserted Node'
  },
  getNodeKey: ({ node }) => node.id,
  ignoreCollapsed: true,
  expandParent: false
});

Removing Nodes

Remove by Path

Remove a node and get information about the removal:
import { removeNode } from 'react-apple-tree';

const { treeData: newTree, node: removedNode, treeIndex } = removeNode({
  treeData: currentTree,
  path: ['root', 'folder-1', 'file-to-delete'],
  getNodeKey: ({ node }) => node.id,
  ignoreCollapsed: true
});

console.log('Removed:', removedNode.title);
setTreeData(newTree);
Removing a parent node will also remove all its descendants.

Remove Only Node

If you only need the updated tree without removal details:
import { removeNodeAtPath } from 'react-apple-tree';

const newTree = removeNodeAtPath({
  treeData: currentTree,
  path: ['root', 'node-to-delete'],
  getNodeKey: ({ node }) => node.id
});

Updating Nodes

Change Node at Path

Update a specific node by its path:
import { changeNodeAtPath } from 'react-apple-tree';

// Replace with new node
const updatedTree = changeNodeAtPath({
  treeData: currentTree,
  path: ['root', 'folder-1', 'file-1'],
  newNode: {
    id: 'file-1',
    title: 'Updated Title',
    subtitle: 'Modified just now'
  },
  getNodeKey: ({ node }) => node.id
});

// Or use a function to update based on current node
const updatedTree2 = changeNodeAtPath({
  treeData: currentTree,
  path: ['root', 'counter'],
  newNode: ({ node, treeIndex }) => ({
    ...node,
    count: (node.count || 0) + 1
  }),
  getNodeKey: ({ node }) => node.id
});
// Replace with a new node object
changeNodeAtPath({
  treeData,
  path: ['1', '1-1'],
  newNode: { id: '1-1', title: 'New Title' },
  getNodeKey: ({ node }) => node.id
});

Get Node at Path

Retrieve node information by path:
import { getNodeAtPath } from 'react-apple-tree';

const nodeInfo = getNodeAtPath({
  treeData: currentTree,
  path: ['root', 'folder-1', 'file-1'],
  getNodeKey: ({ node }) => node.id,
  ignoreCollapsed: true
});

if (nodeInfo) {
  console.log('Node:', nodeInfo.node);
  console.log('Index:', nodeInfo.treeIndex);
}

Moving Nodes

Nodes are automatically moved during drag-and-drop operations. The internal function:
src/utils/node-operations.ts
export function moveNodeToDifferentParent(
  treeData: Array<TreeItem>,
  treeMap: TreeMap,
  nodeKey: NodeKey,
  prevParentKey: NodeKey | null,
  nextParentKey: NodeKey | null,
  siblingIndex: number,
  getNodeKey: GetNodeKeyFn,
): Array<TreeItem>
You typically don’t call this directly. Instead, use the onMoveNode callback to respond to moves:
<ReactAppleTree
  treeData={data}
  onChange={setData}
  getNodeKey={({ node }) => node.id}
  onMoveNode={(data) => {
    console.log('Node moved:', data.node.title);
    console.log('From:', data.prevPath);
    console.log('To:', data.nextPath);
  }}
/>

Expanding and Collapsing

Toggle Single Node

Expand or collapse by updating the expanded property:
const toggleNode = (path: string[]) => {
  const updated = changeNodeAtPath({
    treeData,
    path,
    newNode: ({ node }) => ({
      ...node,
      expanded: !node.expanded
    }),
    getNodeKey: ({ node }) => node.id
  });
  setTreeData(updated);
};

Expand/Collapse All

Toggle expansion state for all nodes:
import { toggleExpandedForAll } from 'react-apple-tree';

// Expand all
const allExpanded = toggleExpandedForAll({
  treeData: currentTree,
  expanded: true
});

// Collapse all
const allCollapsed = toggleExpandedForAll({
  treeData: currentTree,
  expanded: false
});

setTreeData(allExpanded);

Respond to Expansion Changes

Use the onVisibilityToggle callback:
<ReactAppleTree
  treeData={data}
  onChange={setData}
  getNodeKey={({ node }) => node.id}
  onVisibilityToggle={({ node, expanded, treeData }) => {
    console.log(`${node.title} is now ${expanded ? 'expanded' : 'collapsed'}`);
    
    // Optional: Save expansion state to backend
    saveExpansionState(node.id, expanded);
  }}
/>

Traversing the Tree

Walk (Iterate)

Execute a callback for each node:
import { walk } from 'react-apple-tree';

walk({
  treeData,
  getNodeKey: ({ node }) => node.id,
  callback: ({ node, path, treeIndex }) => {
    console.log(`${node.title} at path:`, path);
    
    // Return false to stop traversal
    if (node.id === 'target') {
      return false;
    }
  },
  ignoreCollapsed: true  // Only visit expanded nodes
});

Map (Transform)

Transform the entire tree:
import { map } from 'react-apple-tree';

const prefixedTree = map({
  treeData,
  getNodeKey: ({ node }) => node.id,
  callback: ({ node }) => ({
    ...node,
    title: `[Prefix] ${node.title}`
  }),
  ignoreCollapsed: false  // Transform all nodes
});
Custom DFS traversal with enter/exit callbacks:
src/utils/tree-traversal.ts
import { dfs } from 'react-apple-tree';

dfs({
  treeData,
  callback: (node) => {
    console.log('Visiting:', node.title);
  },
  onNodeEnter: (node) => {
    console.log('Entering:', node.title);
  },
  onNodeExit: (node) => {
    console.log('Exiting:', node.title);
  },
  ignoreCollapsed: false
});

Utility Functions

Get Descendant Count

Count all descendants of a node:
import { getDescendantCount } from 'react-apple-tree';

const count = getDescendantCount({
  node: myNode,
  ignoreCollapsed: true  // Only count visible descendants
});

console.log(`${count} descendants`);

Get Visible Node Count

Count total visible nodes in the tree:
import { getVisibleNodeCount } from 'react-apple-tree';

const visibleCount = getVisibleNodeCount({ treeData });
console.log(`${visibleCount} visible nodes`);

Get Node at Index

Get node information by tree index:
import { getVisibleNodeInfoAtIndex } from 'react-apple-tree';

const nodeInfo = getVisibleNodeInfoAtIndex({
  treeData,
  index: 5,
  getNodeKey: ({ node }) => node.id
});

if (nodeInfo) {
  console.log('Node at index 5:', nodeInfo.node.title);
}

Get Depth

Calculate maximum depth of a node’s descendants:
import { getDepth } from 'react-apple-tree';

const maxDepth = getDepth(rootNode);
console.log(`Tree depth: ${maxDepth}`);

Check Descendant

Check if one node is a descendant of another:
import { isDescendant } from 'react-apple-tree';

if (isDescendant(parentNode, childNode)) {
  console.log('childNode is a descendant of parentNode');
}

Common Patterns

Bulk Operations

Perform multiple updates efficiently:
import { map } from 'react-apple-tree';

// Mark multiple nodes as selected
const selectedIds = new Set(['1', '3', '5']);

const updated = map({
  treeData,
  getNodeKey: ({ node }) => node.id,
  callback: ({ node }) => ({
    ...node,
    selected: selectedIds.has(node.id)
  }),
  ignoreCollapsed: false
});

Search and Replace

Find and update nodes matching criteria:
import { map } from 'react-apple-tree';

const updated = map({
  treeData,
  getNodeKey: ({ node }) => node.id,
  callback: ({ node }) => {
    if (node.title?.includes('OLD')) {
      return {
        ...node,
        title: node.title.replace('OLD', 'NEW')
      };
    }
    return node;
  },
  ignoreCollapsed: false
});

Collect All Nodes

Gather all nodes into a flat array:
import { walk } from 'react-apple-tree';

const allNodes: TreeItem[] = [];

walk({
  treeData,
  getNodeKey: ({ node }) => node.id,
  callback: ({ node }) => {
    allNodes.push(node);
  },
  ignoreCollapsed: false
});

console.log(`Total nodes: ${allNodes.length}`);

Find by Predicate

Find nodes matching a condition:
import { walk } from 'react-apple-tree';

let foundNode: TreeItem | null = null;

walk({
  treeData,
  getNodeKey: ({ node }) => node.id,
  callback: ({ node }) => {
    if (node.title === 'Target') {
      foundNode = node;
      return false;  // Stop searching
    }
  },
  ignoreCollapsed: false
});

Performance Tips

Instead of multiple changeNodeAtPath calls, use map to update multiple nodes in one pass:
// Good: Single pass
const updated = map({
  treeData,
  getNodeKey: ({ node }) => node.id,
  callback: ({ node }) => updateLogic(node)
});

// Bad: Multiple passes
let tree = changeNodeAtPath(...);
tree = changeNodeAtPath(...);
tree = changeNodeAtPath(...);
Set ignoreCollapsed: true to skip hidden nodes for better performance:
walk({
  treeData,
  getNodeKey: ({ node }) => node.id,
  callback: ({ node }) => { /* ... */ },
  ignoreCollapsed: true  // Skip collapsed nodes
});
Define getNodeKey outside render to avoid recreating it:
// Good: Defined once
const getNodeKey = ({ node }) => node.id;

function MyTree() {
  return (
    <ReactAppleTree
      getNodeKey={getNodeKey}
      // ...
    />
  );
}

// Bad: New function every render
function MyTree() {
  return (
    <ReactAppleTree
      getNodeKey={({ node }) => node.id}
      // ...
    />
  );
}

Next Steps

Drag and Drop

Learn about drag-and-drop functionality

API Reference

Complete API documentation for all functions

Build docs developers (and LLMs) love