Skip to content

Material UI – TreeView datastructure

I want to structure the data that I get from a server, so I can use the TreeView component from Material UI: https://material-ui.com/api/tree-view/

I’m fetching large amounts of data so I want to fetch child nodes from the server when the user clicks on the expand button. So when the first node is expanded a HTTP request is sent to a server which returns all of the children of that node. When another node is expanded the children of that node is fetched etc.

On startup of the page I want to fetch the root node and its children. The JSON returned will look something like this:

{
 "division": {
 "id": "1234",
 "name": "Teest",
 "address": "Oslo"
 },
 "children": [
   {
    "id": "3321",
    "parentId": "1234",
    "name": "Marketing",
    "address": "homestreet"
   },
   {
    "id": "3323",
    "parentId": "1234",
    "name": "Development",
    "address": "homestreet"
   }
 ]
}

When expanding the Marketing node I want to make a HTTP call to fetch the children of this node. So I would get JSON like this:

{
  "children": [
    {
      "id": "2212",
      "parentId": "3321",
      "name": "R&D",
      "address": "homestreet"
    },
    {
      "id": "4212",
      "parentId": "3321",
      "name": "Testing",
      "address": "homestreet"
    }
  ]
}

But I am confused on how to create such a data structure which can later be used my the TreeView component. How can I create such a structure?

Answer

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>
    );
  }
}