In your example, it seems, you are trying to build the tree where each node that has children will also be a tree.
In this case, render will look like this:
{/*The node below should act as the root node for now */}
<TreeItem nodeId={props.id} label={props.name}>
{childNodes || [<div key="stub" />]} // stub div is used so user could see expand icon
</TreeItem>
// you also need to pass props for root items
ReactDOM.render(<MyTreeItem id="1" name="Applications" />, rootElement);
You can check the working code here: https://codesandbox.io/s/material-demo-k5ol6
There are multiple ways to render a tree. One is to model a tree structure and render it in a way so each node that has children would no longer be a whole another tree with local state, like in your implementation. The advantage of this approach is that you can provide a complete tree via props, not only by fetching it on 'expand' action.
Here's the example of such an approach: https://codesandbox.io/s/material-demo-h6zfe
Also, you could render the tree as a list, where each nested level has an offset specified via styles. This has an advantage when you need to have a pagination on each level separately (in case if a single level has a lot of items). The implementation is not as easy as in the cases above, so I will not provide an example.
Answer from SciFiThief on Stack Overflow
» npm install react-lazy-paginated-tree
In your example, it seems, you are trying to build the tree where each node that has children will also be a tree.
In this case, render will look like this:
{/*The node below should act as the root node for now */}
<TreeItem nodeId={props.id} label={props.name}>
{childNodes || [<div key="stub" />]} // stub div is used so user could see expand icon
</TreeItem>
// you also need to pass props for root items
ReactDOM.render(<MyTreeItem id="1" name="Applications" />, rootElement);
You can check the working code here: https://codesandbox.io/s/material-demo-k5ol6
There are multiple ways to render a tree. One is to model a tree structure and render it in a way so each node that has children would no longer be a whole another tree with local state, like in your implementation. The advantage of this approach is that you can provide a complete tree via props, not only by fetching it on 'expand' action.
Here's the example of such an approach: https://codesandbox.io/s/material-demo-h6zfe
Also, you could render the tree as a list, where each nested level has an offset specified via styles. This has an advantage when you need to have a pagination on each level separately (in case if a single level has a lot of items). The implementation is not as easy as in the cases above, so I will not provide an example.
I suggest using a recursive function to build the children. You can see how I have done this in typescript. I have used the includeChildren variable to improve performance by only drawing the items which are on screen.
const displayChildren = (parent: DataItem, isRoot: boolean) => {
var includeChildren = isRoot;
if (includeChildren === false && expanded) {
for (let expandedItem of expanded) {
const id = parseInt(expandedItem);
if (id === parent.id || id === parent.parentId) {
includeChildren = true;
break;
}
}
}
const children = includeChildren && childrenLookup?.get(parent.id);
return (
<TreeItem key={parent.id} nodeId={'' + parent.id} label={parent.name} >
{children && children.map((c: DataItem) => displayChildren(c, false))}
</TreeItem>
)
}
I have stored the expanded items in react state, as per the Controlled tree view example. You can return the TreeView like this:
return (
<TreeView
defaultCollapseIcon={<ExpandMoreIcon />}
defaultExpandIcon={<ChevronRightIcon />}
style={{ height: 240, flexGrow: 1, maxWidth: 400, overflowY: 'auto' }}
expanded={expanded}
selected={selected}
onNodeToggle={handleToggle}
onNodeSelect={handleSelect}
>
{dataItems && getRootItems(dataItems).map((parent) => displayChildren(parent, true))}
</TreeView>)
I found that using a childrenLookup is really quick so you don't need to fetch data from the API. My treeview has 10,000 items and it still works fine.
If you are wondering how the lookup works, this is how I have done it. The data is coming from a mobx store but you could use any API response, just make sure it's only called once.
const [childrenLookup, setChildrenLookup] = React.useState<Map<number, DataItem[]>>();
React.useEffect(() => {
let lookup = new Map<number, DataItem[]>();
if (dataStore.dataItems) {
for (let dataItem of dataStore.dataItems) {
if (dataItem.parentId) {
if (!lookup.has(dataItem.parentId)) {
lookup.set(dataItem.parentId, []);
}
lookup.get(dataItem.parentId)?.push(dataItem);
}
}
setChildrenLookup(lookup);
}
}, [dataStore.dataItems])