This solution assumes that you use unique id for each item in the tree.
It uses Depth first search algorithm.
Before trying please fix your sample data non-unique IDs. I didn't notice them at first and wasted time debugging for no bug.
function dfs(node, term, foundIDS) {
// Implement your search functionality
let isMatching = node.name && node.name.indexOf(term) > -1;
if (Array.isArray(node.children)) {
node.children.forEach((child) => {
const hasMatchingChild = dfs(child, term, foundIDS);
isMatching = isMatching || hasMatchingChild;
});
}
// We will add any item if it matches our search term or if it has a children that matches our term
if (isMatching && node.id) {
foundIDS.push(node.id);
}
return isMatching;
}
function filter(data, matchedIDS) {
return data
.filter((item) => matchedIDS.indexOf(item.id) > -1)
.map((item) => ({
...item,
children: item.children ? filter(item.children, matchedIDS) : [],
}));
}
function search(term) {
// We wrap data in an object to match the node shape
const dataNode = {
children: data,
};
const matchedIDS = [];
// find all items IDs that matches our search (or their children does)
dfs(dataNode, term, matchedIDS);
// filter the original data so that only matching items (and their fathers if they have) are returned
return filter(data, matchedIDS);
}
Answer from rksh1997 on Stack OverflowVideos
» npm install material-ui-treeview
I needed a to deal with tree data in a project as well. I ended up creating MUI Tree Select.
You can demo it in this sandbox.

I searched a lot for that in the end I made this by myself sandbox. you can choose to parent or child with this and you can custom it easily.
import { ThemeProvider, createTheme } from "@mui/material/styles";
import React, { useState } from "react";
import ReactDOM from "react-dom";
import TreeItem from "@mui/lab/TreeItem";
import { Popover, TextField, Typography } from "@mui/material";
import clsx from "clsx";
import { TreeView, useTreeItem } from "@mui/lab";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
import { useMediaQuery } from "@mui/material";
const data = [
{
id: "root",
name: "Parent",
children: [
{
id: "1",
name: "Child - 1"
},
{
id: "3",
name: "Child - 3",
children: [
{
id: "4",
name: "Child - 4"
}
]
}
]
},
{
id: "1root",
name: "Parent1",
children: [
{
id: "5",
name: "Child - 1-1"
},
{
id: "7",
name: "Child - 3-1",
children: [
{
id: "8",
name: "Child - 4-1"
}
]
}
]
}
];
const CustomContent = React.forwardRef(function CustomContent(props, ref) {
const {
classes,
className,
label,
nodeId,
icon: iconProp,
expansionIcon,
displayIcon
} = props;
const {
disabled,
expanded,
selected,
focused,
handleExpansion,
handleSelection,
preventSelection
} = useTreeItem(nodeId);
const icon = iconProp || expansionIcon || displayIcon;
const handleMouseDown = (event) => {
preventSelection(event);
};
const handleExpansionClick = (event) => {
handleExpansion(event);
};
const handleSelectionClick = (event) => {
handleSelection(event);
};
return (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<div
className={clsx(className, classes.root, {
[classes.expanded]: expanded,
[classes.selected]: selected,
[classes.focused]: focused,
[classes.disabled]: disabled
})}
onMouseDown={handleMouseDown}
ref={ref}
style={{ padding: "3px 0" }}
>
<div onClick={handleExpansionClick} className={classes.iconContainer}>
{icon}
</div>
<Typography
onClick={handleSelectionClick}
component="div"
className={classes.label}
>
{label}
</Typography>
</div>
);
});
const CustomTreeItem = (props) => (
<TreeItem ContentComponent={CustomContent} {...props} />
);
export default function RichObjectTreeView({ formik, edit }) {
const [anchorEl, setAnchorEl] = React.useState(null);
const [equipmentItem, setEquipmentItem] = useState("");
const [equipmentId, setEquipmentId] = useState("");
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const open = Boolean(anchorEl);
const id = open ? "simple-popover" : undefined;
const renderTree = (nodes) => (
<CustomTreeItem key={nodes.id} nodeId={nodes.id} label={nodes.name}>
{Array.isArray(nodes.children)
? nodes.children.map((node) => renderTree(node))
: null}
</CustomTreeItem>
);
return (
<>
<TextField
variant="standard"
required={false}
label="Equipment Item"
name="equipmentItem"
id="equipmentItem"
defaultValue={equipmentItem}
value={equipmentItem}
className="w-100"
inputProps={{ readOnly: !edit }}
onClick={handleClick}
/>
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: "bottom",
horizontal: "left"
}}
>
<TreeView
aria-label="icon expansion"
defaultSelected={equipmentId}
selected={equipmentId}
defaultCollapseIcon={<ExpandMoreIcon />}
defaultExpandIcon={<ChevronRightIcon />}
onNodeSelect={(e, id) => {
setEquipmentId(id);
setEquipmentItem(e.target.innerText);
}}
sx={{
height: 200,
flexGrow: 1,
minWidth: "200px",
overflowY: "auto"
}}
>
{data.map((item, i) => renderTree(item))}
</TreeView>
</Popover>
</>
);
}
For anyone still looking for a solution to this problem I've recently tackled it using a combination of the selected and expanded props in the TreeView API. See this Code Sandbox demo for an example of how to asynchronously load new children and expand their parent once they are loaded.
import React from "react";
import TreeView from "@material-ui/lab/TreeView";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import ChevronRightIcon from "@material-ui/icons/ChevronRight";
import TreeItem from "@material-ui/lab/TreeItem";
import TreeNode from "./TreeNode";
const mockApiCall = async () => {
return new Promise((resolve) => {
setTimeout(() => {
const nextId = Math.ceil(Math.random() * 100);
resolve([
{
id: `${nextId}`,
name: `child-${nextId}`,
children: []
},
{
id: `${nextId + 1}`,
name: `child-${nextId + 1}`,
children: []
}
]);
}, Math.ceil(Math.random() * 1000));
});
};
export default class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
expanded: [],
selected: "1",
tree: new TreeNode({
id: "1",
name: "src",
children: []
})
};
}
handleChange = async (event, nodeId) => {
const node = this.state.tree.search(nodeId);
if (node && !node.children.length) {
mockApiCall()
.then((result) => {
this.setState({ tree: this.state.tree.addChildren(result, nodeId) });
})
.catch((err) => console.error(err))
.finally(() => {
this.setState({
selected: nodeId,
expanded: [...this.state.expanded, nodeId]
});
});
}
};
createItemsFromTree = (tree) => {
if (tree.children.length) {
return (
<TreeItem key={tree.id} nodeId={tree.id} label={tree.name}>
{tree.children.length > 0 &&
tree.children.map((child) => this.createItemsFromTree(child))}
</TreeItem>
);
}
return <TreeItem key={tree.id} nodeId={tree.id} label={tree.name} />;
};
render() {
return (
<TreeView
defaultCollapseIcon={<ExpandMoreIcon />}
defaultExpandIcon={<ChevronRightIcon />}
selected={this.state.selected}
onNodeSelect={this.handleChange}
expanded={this.state.expanded}
>
{this.createItemsFromTree(this.state.tree)}
</TreeView>
);
}
}
<div class="container-fluid">
<div class="row mt-3">
<div class="col-lg-8">
<div class="card">
<div class="card-body">
<div id="tree">
<div class="branch">
<div class="entry main-entry"><span class="drop"><p-dropdown [options]="logicGates"
optionLabel="name" placeholder="Select" /></span>
<div class="branch">
<div class="entry">
<span class="drop">
<div class="card sub-card p-2">
<div class="row align-items-center">
<div class="col-lg-4">
<select name="" id="" class="form-select">
<option value="">Select Field</option>
</select>
</div>
<div class="col-lg-4">
<select name="" id="" class="form-select">
<option value="">Select Condition</option>
</select>
</div>
<div class="col-lg-3">
<input type="text" class="form-control"
placeholder="Field Text">
</div>
<div class="col-lg-1">
<button class="btn border-0"><i
class='bx bx-trash'></i></button>
</div>
</div>
</div>
</span>
</div>
<div class="entry">
<span class="drop">
<div class="card sub-card p-2">
<div class="row align-items-center">
<div class="col-lg-4">
<select name="" id="" class="form-select">
<option value="">Select Field</option>
</select>
</div>
<div class="col-lg-4">
<select name="" id="" class="form-select">
<option value="">Select Condition</option>
</select>
</div>
<div class="col-lg-3">
<input type="text" class="form-control"
placeholder="Field Text">
</div>
<div class="col-lg-1">
<button class="btn border-0"><i
class='bx bx-trash'></i></button>
</div>
</div>
</div>
</span>
</div>
</div>
<div class="margin">
<a href="">+ Add Condition</a>
</div>
</div>
</div>
</div>
</div>
<div class="card-footer">
<button class="btn border-0 btn_foot me-3">
<i class='bx bx-plus-circle'></i> Add Rule
</button>
<button class="btn border-0 btn_foot">
<i class='bx bx-folder-plus'></i> Add Inner Group
</button>
</div>
</div>
</div>
</div>
</div>
#tree {
display: inline-block;
padding: 10px;
width: 100%;
}
#tree * {
box-sizing: border-box;
}
#tree .branch {
padding: 20px 0 5px 20px;
}
#tree .branch:not(:first-child) {
margin-left: 170px;
}
#tree .branch:not(:first-child):after {
content: "";
width: 20px;
border-top: 1px solid #ccc;
position: absolute;
left: 150px;
top: 50%;
margin-top: 1px;
}
.entry {
position: relative;
min-height: 100px;
display: block;
}
.entry:before {
content: "";
height: 100%;
border-left: 1px solid #ccc;
position: absolute;
left: -20px;
}
.entry:first-child:after {
height: 10px;
border-radius: 10px 0 0 0;
}
.entry:first-child:before {
width: 10px;
height: 50%;
top: 50%;
margin-top: 1px;
border-radius: 10px 0 0 0;
}
.entry:after {
content: "";
width: 20px;
transition: border 0.5s;
border-top: 1px solid #ccc;
position: absolute;
left: -20px;
top: 50%;
margin-top: 1px;
}
.entry:last-child:before {
width: 10px;
height: 50%;
border-radius: 0 0 0 10px;
}
.entry:last-child:after {
height: 10px;
border-top: none;
transition: border 0.5s;
border-bottom: 1px solid #ccc;
border-radius: 0 0 0 10px;
margin-top: -9px;
}
.entry:only-child:after {
width: 10px;
height: 0px;
margin-top: 1px;
border-radius: 0px;
}
.entry:only-child:before {
display: none;
}
.entry span {
border: 1px solid #ccc;
display: block;
min-width: 150px;
padding: 5px 10px;
line-height: 20px;
text-align: center;
position: absolute;
left: 0;
top: 50%;
margin-top: -15px;
color: #666;
font-family: arial, verdana, tahoma;
font-size: 14px;
display: inline-block;
border-radius: 5px;
transition: all 0.5s;
}
// #tree .entry span:hover,
// #tree .entry span:hover + .branch .entry span {
// background: #e6e6e6;
// color: #000;
// border-color: #a6a6a6;
// }
#tree .entry span:hover + .branch .entry::after,
#tree .entry span:hover + .branch .entry::before,
#tree .entry span:hover + .branch::before,
#tree .entry span:hover + .branch .branch::before {
border-color: #a6a6a6;
}
::ng-deep {
.p-dropdown {
border-radius: 20px;
width: 150px;
}
.p-inputtext {
padding: 0.35rem 0.45rem;
font-size: 14px;
}
.p-icon {
width: 0.5rem;
height: 0.5rem;
}
.p-dropdown-panel .p-dropdown-items {
padding-left: 0rem;
margin-bottom: 0rem;
}
.p-dropdown-panel .p-dropdown-items .p-dropdown-item {
padding: 0.55rem 0.75rem;
text-align: start;
}
}
div.main-entry {
.drop {
border: 0px;
padding: 0px;
}
}
.margin {
margin-left: 180px;
a {
text-decoration: none;
}
}
button.btn_foot {
background: #0367a51f;
color: #0367a5;
}
Material-UI 4.9.13 adds onIconClick props to the TreeItem. Since every TreeItem needs to attach a click listener, you should write a wrapper component to reuse the code:
function MyTreeItem(props) {
return (
<TreeItem
{...props}
onIconClick={() => console.log('delete', props.nodeId)}
/>
);
}
Unfortunately, this props was removed in Material-UI 5.0.0 alpha-12 in favor of ContentComponent. This API requires you to write significantly more code, in exchange of more flexible customization:
import Typography from "@material-ui/core/Typography";
import IconButton from "@material-ui/core/IconButton";
import DeleteIcon from "@material-ui/icons/Delete";
import TreeView from "@material-ui/lab/TreeView";
import TreeItem, { useTreeItem } from "@material-ui/lab/TreeItem";
import clsx from "clsx";
const CustomContent = React.forwardRef(function CustomContent(props, ref) {
const {
classes,
label,
nodeId,
icon: iconProp,
expansionIcon,
displayIcon,
onDelete
} = props;
const {
disabled,
expanded,
selected,
focused,
handleExpansion,
handleSelection,
preventSelection
} = useTreeItem(nodeId);
const icon = iconProp || expansionIcon || displayIcon;
const handleDelete = () => onDelete(nodeId);
return (
<div
className={clsx(classes.root, {
[classes.expanded]: expanded,
[classes.selected]: selected,
[classes.focused]: focused,
[classes.disabled]: disabled
})}
onMouseDown={preventSelection}
ref={ref}
>
<div onClick={handleExpansion} className={classes.iconContainer}>
{icon}
</div>
<IconButton size="small" onClick={handleDelete}>
<DeleteIcon fontSize="small" />
</IconButton>
<Typography
onClick={handleSelection}
component="div"
className={classes.label}
>
{label}
</Typography>
</div>
);
});
TreeItem
function MyTreeItem(props) {
return (
<TreeItem
{...props}
ContentComponent={CustomContent}
ContentProps={{
onDelete: (nodeId) => console.log("delete", nodeId)
}}
/>
);
}
TreeView
<TreeView
defaultCollapseIcon={<ExpandMoreIcon />}
defaultExpandIcon={<ChevronRightIcon />}
multiSelect
>
<MyTreeItem nodeId="1" label="Applications">
<MyTreeItem nodeId="2" label="Calendar" />
<MyTreeItem nodeId="3" label="Chrome" />
<MyTreeItem nodeId="4" label="Webstorm" />
</MyTreeItem>
<MyTreeItem nodeId="5" label="Documents">
<MyTreeItem nodeId="6" label="Material-UI">
<MyTreeItem nodeId="7" label="src">
<MyTreeItem nodeId="8" label="index.js" />
<MyTreeItem nodeId="9" label="tree-view.js" />
</MyTreeItem>
</MyTreeItem>
</MyTreeItem>
</TreeView>
Live Demo
In Material UI 5 (as of Feb 2024) using a controlled and styled TreeView with dynamic TreeItems, I've been going about it this way:
<TreeView
...
onNodeSelect={handleSelected}
...
/>
and handleSelected:
const handleSelected = (event) => {
const itemId = event.target.parentNode.parentNode.id
//
// go do stuff with itemId
//
}
I was bit surprised that I had to walk the DOM to find the actual TreeItem and suspect this is all subject to change. The MUI TreeView appears to be an extended work-in-progress or lost step child to the Material UI framework. I'm not sure which.