Search code examples
reactjsmaterial-uitreeviewcheckboxtree

React component renders before the data is successfully fetched from the API so it can't render TreeItem of MUI


I'm trying display tree data using MUI TreeView component. I have an object passed to the RenderTreeWithCheckboxes function. If I declare global my variable data and pass in function RenderTreeWithCheckboxes then my code works fine. But if I do fetch data from API and setState for my variable my code doesn't work. I think the problem is because the component is rendered before my data is successfully fetched. Here is my code

I also have code in CodeSanBox: https://codesandbox.io/s/quizzical-feather-oyg9f0?file=/src/App.js

import { useEffect, useState } from "react";
import "./styles.css";

import axios from "axios";

import TreeView from "@mui/lab/TreeView";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import ChevronRightIcon from "@material-ui/icons/ChevronRight";
import TreeItem from "@mui/lab/TreeItem";
import { Checkbox, FormControlLabel } from "@mui/material";

const category = {
  id: "0",
  termName: "Tất cả chuyên mục",
  subTermTaxonomies: [
    {
      id: 1,
      termName: "Không có chuyên mục",
      description: "Không có chuyên mục",
      subTermTaxonomies: []
    },
    {
      id: 2,
      termName: "Tin tức chung",
      description: "Chuyên mục Tin tức chung",
      subTermTaxonomies: []
    },
    {
      id: 3,
      termName: "Hướng nghiệp",
      description: "Chuyên mục Hướng nghiệp",
      subTermTaxonomies: []
    },
    {
      id: 4,
      termName: "Tin tuyển sinh",
      description: "Chuyên mục Tin tuyển sinh",
      subTermTaxonomies: []
    },
    {
      id: 5,
      termName: "Đại học FPT",
      description: "Trường Đại học FPT",
      subTermTaxonomies: [
        {
          id: 6,
          termName: "Đại học FPT - Hà Nội",
          description: "Đại học FPT - Hà Nội",
          subTermTaxonomies: []
        },
        {
          id: 7,
          termName: "Đại học FPT - Đà Nẵng",
          description: "Đại học FPT - Đà Nẵng",
          subTermTaxonomies: []
        },
        {
          id: 8,
          termName: "Đại học FPT - TP. Hồ Chí Minh",
          description: "Đại học FPT - TP. Hồ Chí Minh",
          subTermTaxonomies: []
        },
        {
          id: 9,
          termName: "Đai học FPT - Cần Thơ",
          description: "Đai học FPT - Cần Thơ",
          subTermTaxonomies: []
        },
        {
          id: 10,
          termName: "Đai học FPT - Quy Nhơn",
          description: "Đai học FPT - Quy Nhơn",
          subTermTaxonomies: []
        }
      ]
    }
  ]
};
export default function App() {
  const [category, setCategory] = useState({});
  const [selected, setSelected] = useState([]);

  function getChildById(node, id) {
    let array = [];

    //returns an array of nodes ids: clicked node id and all children node ids
    function getAllChild(nodes) {
      if (nodes === null) return [];
      array.push(nodes.id);
      if (Array.isArray(nodes.subTermTaxonomies)) {
        nodes.subTermTaxonomies.forEach((node) => {
          array = [...array, ...getAllChild(node)];
          array = array.filter((v, i) => array.indexOf(v) === i);
        });
      }
      return array;
    }

    //returns the node object that was selected
    function getNodeById(nodes, id) {
      if (nodes.id === id) {
        return nodes;
      } else if (Array.isArray(nodes.subTermTaxonomies)) {
        let result = null;
        nodes.subTermTaxonomies.forEach((node) => {
          if (!!getNodeById(node, id)) {
            result = getNodeById(node, id);
          }
        });
        return result;
      }

      return null;
    }

    return getAllChild(getNodeById(node, id));
  }

  function getOnChange(checked, nodes) {
    //gets all freshly selected or unselected nodes
    const allNode = getChildById(category, nodes.id);
    //combines newly selected nodes with existing selection
    //or filters out newly deselected nodes from existing selection
    let array = checked
      ? [...selected, ...allNode]
      : selected.filter((value) => !allNode.includes(value));

    setSelected(array);
  }

  const RenderTreeWithCheckboxes = (nodes) => {
    return (
      <TreeItem
        key={nodes.id}
        nodeId={nodes.id.toString()}
        label={
          <FormControlLabel
            control={
              <Checkbox
                checked={selected.some((item) => item === nodes.id)}
                onChange={(event) =>
                  getOnChange(event.currentTarget.checked, nodes)
                }
                //onClick={(e) => e.stopPropagation()}
              />
            }
            label={<>{nodes.termName}</>}
            key={nodes.id}
          />
        }
      >
        {Array.isArray(nodes.subTermTaxonomies)
          ? nodes.subTermTaxonomies.map((node) =>
              RenderTreeWithCheckboxes(node)
            )
          : null}
      </TreeItem>
    );
  };

  const fetchCategoryHandler = async () => {
    const response = await axios.get(
      `https://news-feamsapi.tranduydat.com/api/term-taxonomy/get-ref`,
      {
        headers: {
          Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmYmE3OTdjNC1hZGRmLTQ0MDEtOTIzOC01NDNlZTMxNWI5MTAiLCJlbWFpbCI6ImR1Y2xvbmcya3p6QGdtYWlsLmNvbSIsIm5hbWUiOiJidWkgZHVjIGxvbmciLCJqdGkiOiJhYTFhMGVlYi02NGE3LTRiMTAtYjgxMi01OGU1N2RhMTQ1NDgiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJVc2VyIiwiZXhwIjoxNjY5MTEzMzI3LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjUwMDEiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjQyMDAifQ.i-G56xKTM432C7D1xcVJHJXkktOCxG3KdmU8WdhcyZs`
        }
      }
    );

    const data = response.data.data;
    const transformData = { id: "0", termName: "", subTermTaxonomies: [] };
    transformData.subTermTaxonomies = data;

    setCategory(transformData);
  };

  useEffect(() => {
    fetchCategoryHandler();
    console.log("category", category);
  }, []);

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <TreeView
        name="category"
        defaultCollapseIcon={<ExpandMoreIcon />}
        defaultExpanded={["0"]}
        defaultExpandIcon={<ChevronRightIcon />}
      >
        {RenderTreeWithCheckboxes(category)}
      </TreeView>
    </div>
  );
}

I have learned to use useEffect and do fetch data inside useEffect but still everything is not working. I want to render my component with data fetch from API, not hardcode


Solution

  • Need to add a condition to check nodes?.id exists

     {nodes?.id && (
          <TreeItem
          .....
          </TreeItem>
        )}
      </>
    

    https://codesandbox.io/s/compassionate-sound-3s5n2j?file=/src/App.js:2052-2924