Как создать динамический вложенный аккордеон в React

#javascript #reactjs #accordion

#javascript #reactjs #аккордеон

Вопрос:

Как я могу создать такой аккордеон

 -parent
   -subparent1
      -subparent2
        ...
          -subparentN
             - child
  

Вот мои данные.

 //parents
{id: 1, name: "", parent_id: null}
{id: 2, name: "", parent_id: null }
{id: 3, name: "", parent_id: null }
{id: 4, name: "", parent_id: null}

//children
{id: 5, name: "", parent_id: 1}
{id: 6, name: "", parent_id: 1}
{id: 7, name: "", parent_id: 5}
{id: 8, name: "", parent_id: 5}
{id: 9, name: "", parent_id: 6}
{id: 10, name: "", parent_id: 6}
{id: 11, name: "", parent_id: 6}
{id: 12, name: "", parent_id: 6}
{id: 13,name: "", parent_id: 6}
{id: 14, name: "", parent_id: 6}

  

В основном те, у кого есть parent_id:null, являются родителями, и когда я нажимаю на них, я хочу, чтобы отображались их потенциальные дочерние элементы, если они у них есть, теперь это не так сложно, но я не могу понять, как отображать дочерние элементы subparent

Комментарии:

1. создайте дочерние дочерние элементы в качестве компонента с собственным состоянием open / close, а затем распространяйте его по щелчку, должно быть так же, как вы делали для parent

Ответ №1:

Вы можете перебрать список всех ваших элементов и добавить каждый подпункт к их родительскому элементу. После этого вам просто нужно перебрать все элементы в массиве и создать их соответствующий html.

 const items = [
  //parents
  {id: 1, name: "1", parent_id: null},
  {id: 2, name: "2", parent_id: null },
  {id: 3, name: "3", parent_id: null },
  {id: 4, name: "4", parent_id: null},

  //children
  {id: 5, name: "5", parent_id: 1},
  {id: 6, name: "6", parent_id: 1},
  {id: 7, name: "7", parent_id: 5},
  {id: 8, name: "8", parent_id: 5},
  {id: 9, name: "9", parent_id: 6},
  {id: 10, name: "10", parent_id: 6},
  {id: 11, name: "11", parent_id: 6},
  {id: 12, name: "12", parent_id: 6},
  {id: 13,name: "13", parent_id: 6},
  {id: 14, name: "14", parent_id: 6},
];

for(const item of items) {
  // Find the parent object
  const parent = items.find(({ id }) => id === item.parent_id);
  // If the parent is found add the object to its children array
  if(parent) {
    parent.children = parent.children ? [...parent.children, item] : [item]
  }
};

// Only keep root elements (parents) in the main array
const list = items.filter(({ parent_id }) => !parent_id);

// console.log(list);

// Show tree (vanillaJS, no REACT)
for(const item of list) {
  // Create a new branch for each item
  const ul = createBranch(item);
  // Append branch to the document
  document.body.appendChild(ul);
}

function createBranch(item) {
  // Create ul item for each branch
  const ul = document.createElement("ul");
  // Add current item as li to the branch
  const li = document.createElement("li");
  li.textContent = item.name;
  ul.appendChild(li);

  // Check if there are children
  if(item.children) {
    // Create a new branch for each child
    for(const child of item.children) {
      const subUl = createBranch(child);
      // Append child branch to current branch
      ul.appendChild(subUl);
    }
  }
  
  return ul;
}  
 ul {
  margin: 0;
  padding-left: 2rem;
}  

Комментарии:

1. ты абсолютный зверь, настоящий гений, огромное тебе спасибо, ты спасаешь жизнь. Я бы никогда даже близко не подошел к этому сам. Кстати, у меня есть еще один дочерний слой, и он работает все так же, еще раз спасибо!

Ответ №2:

Я думаю, что ваша структура данных должна быть вложенным объектом аналогично тому, как вы видите, как работает ваше меню, т. Е.

 [
    {id: 1, name:"", children: [
      {id: 5, name: "", children: []},
      {id: 6, name: "", children: [
        {id: 7, name: "", children: []},
        {id: 8, name: "", children: []},
      ]},
    ]},
    {id: 2, name:"", children:[]}
]
  

Тогда вам понадобится функция для вывода каждого элемента:

 const returnMenuItem = (item, i) =>{
  let menuItem;

  if (item.children.length===0) {
    menuItem = <div key={i}>{item.label}</div>;
  }
  else {
    let menuItemChildren = item.children.map((item,i)=>{
      let menuItem = returnMenuItem(item,i);
      return menuItem;
    });
    menuItem = <div key={i}>
      <div>{item.label}</div>
      <div>
        {menuItemChildren}
      </div>
    </div>;
  }
  return menuItem;
}
  

И вы бы вызвали эту функцию, просмотрев свои элементы:

 let menuItems = data.map((item,i)=>{
    let menuItem = returnMenuItem(item,i);
    return menuItem;
});
  

Полный компонент будет выглядеть примерно так:

 import React, { useState, useEffect } from "react";
import { UncontrolledCollapse } from "reactstrap";

const Menu = (props) => {

  const [loading, setLoading] = useState(true);
  const [items, setItems] = useState([]);

  useEffect(() => {
    const menuData = [
      {
        id: 1,
        name: "test 1",
        children: [
          { id: 5, name: "test 5", children: [] },
          {
            id: 6,
            name: "test 6",
            children: [
              { id: 7, name: "test 7", children: [] },
              { id: 8, name: "test 8", children: [] }
            ]
          }
        ]
      },
      { id: 2, name: "test 2", children: [] }
    ];
    const returnMenuItem = (item, i) => {
      let menuItem;

      if (item.children.length === 0) {
        menuItem = (
          <div className="item" key={i}>
            {item.name}
          </div>
        );
      } else {
        let menuItemChildren = item.children.map((item, i) => {
          let menuItem = returnMenuItem(item, i);
          return menuItem;
        });
        menuItem = (
          <div key={i} className="item">
            <div className="toggler" id={`toggle-menu-item-${item.id}`}>
              {item.name}
            </div>
            <UncontrolledCollapse
              className="children"
              toggler={`#toggle-menu-item-${item.id}`}
            >
              {menuItemChildren}
            </UncontrolledCollapse>
          </div>
        );
      }
      return menuItem;
    };

    const load = async () => {
      setLoading(false);
      let menuItems = menuData.map((item, i) => {
        let menuItem = returnMenuItem(item, i);
        return menuItem;
      });
      setItems(menuItems);
    };
    if (loading) {
      load();
    }
  }, [loading]);

  return <div className="items">{items}</div>;
};
export default Menu;
  

И минимальный css:

 .item {
  display: block;
}
.item > .children {
  padding: 0 0 0 40px;
}
.item > .toggler {
  display: inline-block;
}

.item::before {
  content: "-";
  padding: 0 5px 0 0;
}
  

Вы можете найти рабочую песочницу кода здесь песочница

Комментарии:

1. привет, отличный ответ, это работает, если я хочу отобразить все элементы, но как бы я сделал что-то вроде аккордеона? Я хочу нажать родительский элемент и заставить дочерние элементы свернуться.

2. Обычно я использую reactstrap в качестве пакета react-bootstrap. Затем я оборачиваю свое меню в навигатор ( reactstrap.github.io/components/navs ) и пункты меню, у которых есть дочерние элементы в элементе UncontrolledDropdown (reactstrap.github.io/components/ dropdowns ). Наконец, для элементов я использую элемент NavItem . Если вы хотите использовать подход аккордеона, вы можете обернуть свои элементы дочерними элементами в collapse ( reactstrap.github.io/components/collapse ). Если вам это сложно или вы не уверены, как это использовать, подробно опишите ожидаемое поведение, и я могу расширить свой ответ, чтобы охватить это.

3. да, пожалуйста. Я не понимаю, как заставить его работать с функцией returnMenuItem, которую вы были так любезны предоставить. Большое спасибо, от всей этой вложенности и рекурсии у меня кружится голова

4. Я обновил свой ответ примером компонента. Теперь у вас должно быть все, что вам нужно, чтобы создать компонент в соответствии с вашими потребностями.

5. Приветствия, всегда рад помочь. Если это то, что вы хотели, тогда вы должны проверить это как правильный ответ.

Ответ №3:

Я думаю, что в вашей структуре данных есть недостаток. Помимо отношения «потомок-родитель», вы должны отслеживать отношения «родитель-потомок». Теперь вы сможете легко перебирать данные и отображать дочерние элементы дочернего элемента.

 {id: 1, parent_id: null, children: [
  {id: 2, parent_id: 1, children: []},
  {id: 3, parent_id: 1, children: [
    {id: 4, parent_id: 3, children: []}
  ]}
]}
  

Если вам нужно сохранить все объекты встроенными, вы можете структурировать свои данные следующим образом:

 {id: 1, parent_id: null, children: [2, 3]}
{id: 2, parent_id: 1, children: []},
{id: 3, parent_id: 1, children: [4]},
{id: 4, parent_id: 3, children: []}