Skip to main content

Overview

Tree manipulation functions allow you to modify tree data in various ways. These functions create new tree data structures rather than mutating the original data, following React’s immutability principles.

addNodeUnderParent

Add a new node as a child of a parent node.
treeData
Array<TreeItem<T>>
required
The current tree data
newNode
TreeItem<T>
required
The node to add
parentKey
NodeKey | null
required
The key of the parent node, or null to add at root level
getNodeKey
GetNodeKeyFn<T>
required
Function to get the unique key of each node
ignoreCollapsed
boolean
default:"true"
Whether to skip collapsed nodes when searching for parent
expandParent
boolean
default:"false"
Whether to automatically expand the parent node
addAsFirstChild
boolean
default:"false"
Whether to add as the first child (true) or last child (false)
return
{ treeData: Array<TreeItem<T>>, treeIndex: number | null }
New tree data with the node added, and the tree index where it was inserted

Example

import { addNodeUnderParent } from '@newtonschool/react-apple-tree';

// Add a node at root level
const { treeData: newTree, treeIndex } = addNodeUnderParent({
  treeData: currentTree,
  newNode: { id: 'new-1', title: 'New Root Node' },
  parentKey: null, // null means root level
  getNodeKey: ({ node }) => node.id,
});

// Add a child node
const result = addNodeUnderParent({
  treeData: currentTree,
  newNode: { id: 'child-1', title: 'New Child' },
  parentKey: 'parent-id',
  getNodeKey: ({ node }) => node.id,
  expandParent: true, // Expand parent to show new child
});

// Add as first child
const resultFirst = addNodeUnderParent({
  treeData: currentTree,
  newNode: { id: 'first-child', title: 'First Child' },
  parentKey: 'parent-id',
  getNodeKey: ({ node }) => node.id,
  addAsFirstChild: true,
});

// Complete example with state update
const handleAddChild = (parentKey: string) => {
  const newNode = {
    id: `node-${Date.now()}`,
    title: 'New Node',
    type: 'item',
  };

  const { treeData: updatedTree } = addNodeUnderParent({
    treeData,
    newNode,
    parentKey,
    getNodeKey: ({ node }) => node.id,
    expandParent: true,
  });

  setTreeData(updatedTree);
};
Set expandParent: true when adding nodes to ensure the user can see the newly added node immediately.

removeNode

Remove a node at a specific path and return the updated tree data along with information about the removed node.
treeData
Array<TreeItem<T>>
required
The current tree data
path
NumberOrStringArray
required
The path to the node to remove
getNodeKey
GetNodeKeyFn<T>
required
Function to get the unique key of each node
ignoreCollapsed
boolean
default:"true"
Whether to skip collapsed nodes when finding the path
return
{ treeData: Array<TreeItem<T>>, node: TreeItem<T> | null, treeIndex: number | null }
Updated tree data, the removed node, and its tree index

Example

import { removeNode } from '@newtonschool/react-apple-tree';

const handleDelete = (path: NumberOrStringArray) => {
  const { treeData: updatedTree, node: removedNode } = removeNode({
    treeData,
    path,
    getNodeKey: ({ node }) => node.id,
  });

  if (removedNode) {
    console.log('Removed node:', removedNode.title);
    setTreeData(updatedTree);
  }
};

// With undo functionality
const [history, setHistory] = useState<TreeItem[][]>([treeData]);

const handleDeleteWithUndo = (path: NumberOrStringArray) => {
  const result = removeNode({
    treeData,
    path,
    getNodeKey: ({ node }) => node.id,
  });

  if (result.node) {
    setTreeData(result.treeData);
    setHistory([...history, result.treeData]);
  }
};

removeNodeAtPath

Remove a node at a specific path (simpler version that just returns updated tree data).
treeData
Array<TreeItem<T>>
required
The current tree data
path
NumberOrStringArray
required
The path to the node to remove
getNodeKey
GetNodeKeyFn<T>
required
Function to get the unique key of each node
ignoreCollapsed
boolean
default:"true"
Whether to skip collapsed nodes when finding the path
return
Array<TreeItem<T>>
Updated tree data with the node removed

Example

import { removeNodeAtPath } from '@newtonschool/react-apple-tree';

const updatedTree = removeNodeAtPath({
  treeData,
  path: ['parent-id', 'child-id'],
  getNodeKey: ({ node }) => node.id,
});

setTreeData(updatedTree);

changeNodeAtPath

Update or replace a node at a specific path.
treeData
Array<TreeItem<T>>
required
The current tree data
path
NumberOrStringArray
required
The path to the node to change
newNode
TreeItem<T> | ((data: NodeData<T>) => TreeItem<T> | null) | null
required
The new node, or a function that returns the new node, or null to remove the node
getNodeKey
GetNodeKeyFn<T>
required
Function to get the unique key of each node
ignoreCollapsed
boolean
default:"true"
Whether to skip collapsed nodes when finding the path
return
Array<TreeItem<T>>
Updated tree data with the node changed

Example

import { changeNodeAtPath } from '@newtonschool/react-apple-tree';

// Replace a node completely
const updatedTree = changeNodeAtPath({
  treeData,
  path: ['parent-id', 'child-id'],
  newNode: { id: 'child-id', title: 'Updated Title', type: 'updated' },
  getNodeKey: ({ node }) => node.id,
});

// Update a node using a function
const toggledTree = changeNodeAtPath({
  treeData,
  path,
  newNode: ({ node }) => ({
    ...node,
    expanded: !node.expanded,
  }),
  getNodeKey: ({ node }) => node.id,
});

// Update node title
const handleRename = (path: NumberOrStringArray, newTitle: string) => {
  const updatedTree = changeNodeAtPath({
    treeData,
    path,
    newNode: ({ node }) => ({
      ...node,
      title: newTitle,
    }),
    getNodeKey: ({ node }) => node.id,
  });
  
  setTreeData(updatedTree);
};

// Mark node as favorite
const handleToggleFavorite = (path: NumberOrStringArray) => {
  const updatedTree = changeNodeAtPath({
    treeData,
    path,
    newNode: ({ node }) => ({
      ...node,
      isFavorite: !node.isFavorite,
    }),
    getNodeKey: ({ node }) => node.id,
  });
  
  setTreeData(updatedTree);
};
If the callback function returns null, the node will be removed from the tree.

insertNode

Insert a node at a specific depth and tree index.
treeData
Array<TreeItem<T>>
required
The current tree data
depth
number
required
The depth level to insert at (0 = root)
minimumTreeIndex
number
required
The minimum tree index position
newNode
TreeItem<T>
required
The node to insert
getNodeKey
GetNodeKeyFn<T>
required
Function to get the unique key of each node
ignoreCollapsed
boolean
default:"true"
Whether to skip collapsed nodes
expandParent
boolean
default:"false"
Whether to automatically expand the parent node
return
{ treeData: Array<TreeItem<T>>, treeIndex: number | null, path: NumberOrStringArray, parentNode: TreeItem<T> | null }
Updated tree data with insertion details

Example

import { insertNode } from '@newtonschool/react-apple-tree';

// Insert at root level
const result = insertNode({
  treeData,
  depth: 0,
  minimumTreeIndex: 0,
  newNode: { id: 'new-1', title: 'New Root' },
  getNodeKey: ({ node }) => node.id,
});

// Insert as child at specific position
const childResult = insertNode({
  treeData,
  depth: 1, // First level children
  minimumTreeIndex: 5, // After 5th visible node
  newNode: { id: 'new-child', title: 'New Child' },
  getNodeKey: ({ node }) => node.id,
  expandParent: true,
});

toggleExpandedForAll

Expand or collapse all nodes in the tree.
treeData
Array<TreeItem<T>>
required
The current tree data
expanded
boolean
default:"true"
Whether to expand (true) or collapse (false) all nodes
return
Array<TreeItem<T>>
Updated tree data with all nodes expanded or collapsed

Example

import { toggleExpandedForAll } from '@newtonschool/react-apple-tree';

// Expand all nodes
const handleExpandAll = () => {
  const expandedTree = toggleExpandedForAll({
    treeData,
    expanded: true,
  });
  setTreeData(expandedTree);
};

// Collapse all nodes
const handleCollapseAll = () => {
  const collapsedTree = toggleExpandedForAll({
    treeData,
    expanded: false,
  });
  setTreeData(collapsedTree);
};

// Toggle all
const [allExpanded, setAllExpanded] = useState(false);

const handleToggleAll = () => {
  const newTree = toggleExpandedForAll({
    treeData,
    expanded: !allExpanded,
  });
  setTreeData(newTree);
  setAllExpanded(!allExpanded);
};

Complete Example

import React, { useState } from 'react';
import ReactAppleTree, {
  addNodeUnderParent,
  removeNode,
  changeNodeAtPath,
  toggleExpandedForAll,
} from '@newtonschool/react-apple-tree';

interface MyNode {
  id: string;
  title: string;
  children?: MyNode[];
}

const TreeWithActions = () => {
  const [treeData, setTreeData] = useState<MyNode[]>([/* ... */]);

  const handleAddNode = (parentKey: string | null) => {
    const { treeData: newTree } = addNodeUnderParent({
      treeData,
      newNode: {
        id: `node-${Date.now()}`,
        title: 'New Node',
      },
      parentKey,
      getNodeKey: ({ node }) => node.id,
      expandParent: true,
    });
    setTreeData(newTree);
  };

  const handleRemoveNode = (path: string[]) => {
    const { treeData: newTree } = removeNode({
      treeData,
      path,
      getNodeKey: ({ node }) => node.id,
    });
    setTreeData(newTree);
  };

  const handleRename = (path: string[], newTitle: string) => {
    const newTree = changeNodeAtPath({
      treeData,
      path,
      newNode: ({ node }) => ({ ...node, title: newTitle }),
      getNodeKey: ({ node }) => node.id,
    });
    setTreeData(newTree);
  };

  const handleExpandAll = () => {
    setTreeData(toggleExpandedForAll({ treeData, expanded: true }));
  };

  const handleCollapseAll = () => {
    setTreeData(toggleExpandedForAll({ treeData, expanded: false }));
  };

  return (
    <div>
      <div style={{ marginBottom: 10 }}>
        <button onClick={() => handleAddNode(null)}>Add Root</button>
        <button onClick={handleExpandAll}>Expand All</button>
        <button onClick={handleCollapseAll}>Collapse All</button>
      </div>
      
      <div style={{ height: 400 }}>
        <ReactAppleTree
          treeData={treeData}
          onChange={setTreeData}
          getNodeKey={({ node }) => node.id}
          generateNodeProps={({ node, path }) => ({
            buttons: [
              <button onClick={() => handleAddNode(node.id)}>+</button>,
              <button onClick={() => handleRemoveNode(path)}>×</button>,
            ],
          })}
        />
      </div>
    </div>
  );
};

Next Steps

Tree Traversal

Walk through and iterate over tree nodes

Data Conversion

Convert between flat and nested structures

Build docs developers (and LLMs) love