Add a child to tree with useState

Viewed 834

I have a simple tree with React, the tree has its data from the root Node and it renders its children recursively -

<div className="Tree">
  <div>{props.name}</div>
  <div className="children">
    {props.children &&
      props.children.map((item) => {
        return <TreeNode name={item.name} children={item.children} />;
      })}
  </div>
</div>

By Changing the TreeNode component and using useState how could I make an option for an action of add child to any node in the tree ?

Here is the code I have so far - Demo

(You can implement the action of add child in the button there and give the new child any name you want)

2 Answers

Here's a demo. I'm storing each sub node in the parent's component state. Input shows the name of the node being created when you click add.

https://codesandbox.io/s/react-hooks-counter-demo-forked-b9mx5

import React, { useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";

const TreeNode = (props) => {
  const [children, setChildren] = useState([]);
  const [input, setInput] = useState(props.name + "-0");

  function addNode(e) {
    e.preventDefault();
    setChildren((children) => [...children, { name: input, children: [] }]);
    setInput(`${props.name}-${children.length + 1}`);
  }

  return (
    <div className="Tree">
      <div>
        <span>{props.name}</span>
        <form onSubmit={addNode}>
          <input
            type="text"
            value={input}
            onChange={(e) => setInput(e.target.value)}
          />
          <button type="submit">Add child</button>
        </form>
      </div>
      <div className="children">
        {children &&
          children.map((item, index) => {
            return <TreeNode key={index} name={item.name} />;
          })}
      </div>
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<TreeNode name="Node" />, rootElement);

One way is to duplicate props.children in an internal state and add new child to it:

https://codesandbox.io/s/react-hooks-counter-demo-forked-clls0

const TreeNode = (props) => {
  const [childState, setChildState] = useState(props.children);
  const addChild = () => {
    setChildState((childState) => {
      return [
        ...(childState || []),
        { name: `${props.name}-${childState?.length + 1 || 1}` }
      ];
    });
  };
  return (
    <div className="Tree">
      <div>
        <span>{props.name}</span>
        <button onClick={addChild}>Add child</button>
      </div>
      <div className="children">
        {childState &&
          childState.map((item) => {
            return <TreeNode name={item.name} children={item.children} />;
          })}
      </div>
    </div>
  );
};

another is to mutate treeData directly and force view update:

https://codesandbox.io/s/react-hooks-counter-demo-forked-lipgf

const TreeNode = ({ item }) => {
  const addChild = () => {
    item.children = [
      ...(item.children || []),
      { name: `${item.name}-${item.children?.length + 1 || 1}` }
    ];
    update({});
  };
  return (
    <div className="Tree">
      <div>
        <span>{item.name}</span>
        <button onClick={addChild}>Add child</button>
      </div>
      <div className="children">
        {item.children &&
          item.children.map((item) => {
            return <TreeNode item={item} />;
          })}
      </div>
    </div>
  );
};

let update;
const rootElement = document.getElementById("root");
const App = () => {
  [, update] = useState();
  return <TreeNode item={treeData} />;
};
ReactDOM.render(<App />, rootElement);
Related