Home > Blockchain >  Nested state (react) with onclick events
Nested state (react) with onclick events

Time:10-10

I was given the task to build a recursive tree with some functionality. I managed to build a tree using a recursive component, I attach the code below.

function App() {
  const [data, setData] = useState([{
    id: 1,
    name: "node 1",
    children: [{
      id: 2,
      name: "node 1.1",
      children: [{
        id: 8,
        name: "node 1.1.1",
        children: []
      }]
    },{
      id: 3,
      name: "node 1.2",
      children: [{
        id: 4,
        name: "node 1.2.1",
        children: [{
          id: 5,
          name: "node 1.2.1.1",
          children: []
        }]
      }]
    }]
  }])


  return (
    <div className="App">
      <Tree data = {data} setData = {setData} margin={15}/>
      <button>Add child</button>
    </div>
  );
}

and

const Tree = ({data, margin, setData}) => {
    return (
        <Fragment>
            {data.map((element, index) => (
             <div>
                 <div className="tier">
                    <div
                        style={{marginLeft: `${margin}px`}}
                    >
                        {element.name}
                    </div>
                 </div>
                {element.children && element.children.length ? <Tree
                    data={element.children}
                    setData={setData}
                    margin={margin   15}
                /> : false}
             </div>))}
        </Fragment>
    );
};

I need to add a node selection when clicking on it. I have an idea about the implementation: create a state in which the ID of the desired node will be stored, and design it using CSS. But I have absolutely no idea how to add a child node to the selected node when the button is clicked.

CodePudding user response:

Nested array in your date tree you should map as well. It should look like that

{element.children?.map(el, I => (
<p key={I}> {el. name}</p>

{el.children?.map(child, ind => (
<p key={ind}>{child.name}</p>
)}
)}

the ? is very important, because it would work only if there was a date like that.

CodePudding user response:

You did half of the job and your idea about storing id is the best overall, you could actually store the selected item itself but it will be not as great as it sounds due to mutation of this item, you will not be able to trigger dependent hooks update for it.

So you only need to store the id of selected item and a few utility things to simplify the work with the tree. On the code below you can see the flatTree that was calculated with useMemo from the original data. It just flattens your tree to an array, so you will be able to easilly find real selected item and to calculate the next id that will be inserted on button click. And to add a few more props to the Tree component itself. onClick to handle actual click and selectedTreeItem just to add some classes for selected item (optional).

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

export default function App() {
  const [data, setData] = useState([{id:1,name:"node 1",children:[{id:2,name:"node 1.1",children:[{id:8,name:"node 1.1.1",children:[]}]},{id:3,name:"node 1.2",children:[{id:4,name:"node 1.2.1",children:[{id:5,name:"node 1.2.1.1",children:[]}]}]}]}]);

  const [selectedTreeItemId, setSelectedTreeItemId] = useState();

  const flatTree = useMemo(() => {
    const flat = (item) => [item, ...(item.children || []).flatMap(flat)];
    return data.flatMap(flat);
  }, [data]);

  const selectedTreeItem = useMemo(() => {
    if (!selectedTreeItemId || !flatTree) return undefined;
    return flatTree.find((x) => x.id === selectedTreeItemId);
  }, [flatTree, selectedTreeItemId]);

  const getNextListItemId = () => {
    const allIds = flatTree.map((x) => x.id);
    return Math.max(...allIds)   1;
  };

  const onTreeItemClick = ({ id }) => {
    setSelectedTreeItemId((curr) => (curr === id ? undefined : id));
  };

  const onAddChildClick = () => {
    if (!selectedTreeItem) return;
    if (!selectedTreeItem.children) selectedTreeItem.children = [];
    const newObj = {
      id: getNextListItemId(),
      name: `${selectedTreeItem.name}.${selectedTreeItem.children.length   1}`,
      children: []
    };
    selectedTreeItem.children.push(newObj);

    // dirty deep-clone of the tree to force "selectedTreeItem"
    // to be recalculated and its !reference! to be updated.
    // so any hook that has a "selectedTreeItem" in depsArray
    // will be executed now (as expected)
    setData((curr) => JSON.parse(JSON.stringify(curr)));
  };

  useEffect(() => {
    console.log("selectedTreeItem: ", selectedTreeItem);
  }, [selectedTreeItem]);

  return (
    <div className="App">
      <Tree
        data={data}
        margin={15}
        onTreeItemClick={onTreeItemClick}
        selectedTreeItem={selectedTreeItem}
      />
      <button
        type="button"
        disabled={!selectedTreeItem}
        onClick={onAddChildClick}
      >
        Add child
      </button>
    </div>
  );
}

const Tree = ({ data, margin, onTreeItemClick, selectedTreeItem }) => {
  return (
    <Fragment>
      {data.map((element) => (
        <div key={element.id}>
          <div
            className={`tier ${
              selectedTreeItem === element ? "selected-list-item" : ""
            }`}
            onClick={() => onTreeItemClick(element)}
          >
            <div style={{ marginLeft: `${margin}px` }}>
              {element.id}: {element.name}
            </div>
          </div>
          {element.children?.length > 0 && (
            <Tree
              data={element.children}
              margin={margin   15}
              onTreeItemClick={onTreeItemClick}
              selectedTreeItem={selectedTreeItem}
            />
          )}
        </div>
      ))}
    </Fragment>
  );
};

Edit React Tree (forked)

  • Related