Reputation: 173
How i can create an accordion like this
-parent
-subparent1
-subparent2
...
-subparentN
- child
Here is my data.
//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}
Basically the ones who have parent_id:null are parents, and when I click on them I want their potential children to be displayed if they have any, now this isn't that hard, but what I can't understand is how to display subparent's children
Upvotes: 1
Views: 6486
Reputation: 962
I think your data structure should be a nested object similarly to how you see your menu working, i.e.
[
{id: 1, name:"", children: [
{id: 5, name: "", children: []},
{id: 6, name: "", children: [
{id: 7, name: "", children: []},
{id: 8, name: "", children: []},
]},
]},
{id: 2, name:"", children:[]}
]
Then you would need a function to output each item:
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;
}
And you would invoke this function by going through your items:
let menuItems = data.map((item,i)=>{
let menuItem = returnMenuItem(item,i);
return menuItem;
});
A complete component would look something like the following:
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;
And a minimum css:
.item {
display: block;
}
.item > .children {
padding: 0 0 0 40px;
}
.item > .toggler {
display: inline-block;
}
.item::before {
content: "-";
padding: 0 5px 0 0;
}
You can find a working code sandbox here sandbox
Upvotes: 0
Reputation: 6525
You can loop over the list of all your items and add each sub item to their parent. Afterwards you just have to loop over all the items in the array and create their respective 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;
}
Upvotes: 1
Reputation: 37
I think that your data structure has a flaw. Beside child to parent relation, you should keep track of parent to child relationship. Now you will be able to easily iterate through the data and render sub parent's children.
{id: 1, parent_id: null, children: [
{id: 2, parent_id: 1, children: []},
{id: 3, parent_id: 1, children: [
{id: 4, parent_id: 3, children: []}
]}
]}
If you need to keep all objects inline, you can structure your data like:
{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: []}
Upvotes: 0