I have template rendering in a loop like this
{
category.menuIds.map((menuId, idx) => (
<TemplateMenu
index={idx}
key={menuId}
menu={this.props.menus[menuId]}
menuLayout={category.menu_layout}
/>
));
}
It renders a list of <div>
as below:
<div id="m0" >...</div>
<div id="m1" >...</div>
<div id="m2" >...</div>
<div id="m3" >...</div>
<div id="m4" >...</div>
<div id="m5" >...</div>
<div id="m6" >...</div>
<div id="m7" >...</div>
<div id="m8" >...</div>
<div id="m9" >...</div>
I want to group every 4 <div>
upon the condition this.props.index % 5 !== 0
to get HTML below:
<div id="m0" >...</div>
<div >
<div id="m1" >...</div>
<div id="m2" >...</div>
<div id="m3" >...</div>
<div id="m4" >...</div>
</div>
<div id="m5" >...</div>
<div >
<div id="m6" >...</div>
<div id="m7" >...</div>
<div id="m8" >...</div>
<div id="m9" >...</div>
</div>
Any help would be appreciated.
CodePudding user response:
It would be complex to regroup the tags after they've already been mapped to a flat list of TemplateMenu
, as each element is not aware of its position according to their parent's logic.
To solve your problem I would group the elements before mapping them into the final elements, and then rendering them into a single TemplateMenu
or the grouped MenuRight
.
For example, this is the quickest approach I could think to group them, but of course there are many other algorithms you might prefer:
const groupedIds = menuIds.reduce((groups: any[], id: any, index: number) => {
if (index % 5 === 0) {
groups[Math.floor(index / 5) * 2] = [id];
} else {
const groupIndex = Math.floor(index / 5) * 2 1;
if (groups[groupIndex]) {
groups[groupIndex].push(id);
} else {
groups[groupIndex] = [id];
}
}
return groups;
}, []);
This would result in an array of ids like [[0],[1,2,3,4],[5],[6,7...]...]
. Then you can use these groups to render them in different ways.
Here is a working example with a problem derived from yours: https://codesandbox.io/s/practical-noether-dxx0d?file=/src/App.tsx:277-662
CodePudding user response:
You can reduce the categories.menuIds
array to an array of objects which contains only parent elements for eg. 0, 5, 10,..
and each parent object will only have its child elements inside it for eg. children of 0
will be 1, 2, 3, 4
. Then it will be easier to render this in React
using Array.map()
.
Working Example : Demo Link
First Reduce the categories.menuIds
array
const menuIds = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
const transformed = menuIds.reduce((acc, curr, idx) => {
if (idx % 5 === 0) {
// For every 5th item, create root
acc.push({ data: curr, children: [] });
} else {
// Else add child elements inside root
const parent = acc[Math.floor(idx / 5)];
parent.children.push(curr);
}
return acc;
}, []);
console.log(`Transformed Array : ${transformed}`);
Render this in React
using Array.map()
<div className="App">
{transformed.map((parent, idx) => {
return (
<div
id={`id_${idx}`}
key={idx}
className="Menu"
style={{ backgroundColor: getRandomColor() }}
>
<div className="MenuRight">
{parent.children.map((child, i) => {
return (
<div id="m6" className="Menu">
{child}
</div>
);
})}
</div>
</div>
);
})}
</div>
CodePudding user response:
You can do this with a simple loop, which to me is by far the clearest way; see comments:
const {menuIds} = category;
const elements = [];
// Include every 5th element directly, including the 0th one;
// group the (up to) four following it
let index = 0;
while (index < menuIds.length) {
// Use this entry directly, note we increment index after using it
// in the `TemplateMenu`
const {menuId} = menuIds[index];
elements.push(
<TemplateMenu
index={index }
key={menuId}
menu={this.props.menus[menuId]}
menuLayout={category.menu_layout}
/>
);
// Follow it with up to four in a `<div >`
const following = menuIds.slice(index, index 4);
if (following.length) {
// Note we increment `index` as we go
elements.push(
<div className="MenuRight">
{following.map(({menuId}) => (
<TemplateMenu
index={index }
key={menuId}
menu={this.props.menus[menuId]}
menuLayout={category.menu_layout}
/>
))}
</div>
);
}
}
// ...use {elements}...
Live Example:
const {useState} = React;
const TemplateMenu = ({index, menu, menuLayout}) => <div className="TemplateMenu">{menu}</div>;
class Example extends React.Component {
render() {
const category = {
menuIds: [
{menuId: 0},
{menuId: 1},
{menuId: 2},
{menuId: 3},
{menuId: 4},
{menuId: 5},
{menuId: 6},
{menuId: 7},
{menuId: 8},
{menuId: 9},
],
menu_layout: "example",
};
const {menuIds} = category;
const elements = [];
// Include every 5th element directly, including the 0th one;
// group the (up to) four following it
let index = 0;
while (index < menuIds.length) {
// Use this entry directly, note we increment index after using it
// in the `TemplateMenu`
const {menuId} = menuIds[index];
elements.push(
<TemplateMenu
index={index }
key={menuId}
menu={this.props.menus[menuId]}
menuLayout={category.menu_layout}
/>
);
// Follow it with up to four in a `<div >`
const following = menuIds.slice(index, index 4);
if (following.length) {
// Note we increment `index` as we go
elements.push(
<div className="MenuRight">
{following.map(({menuId}) => (
<TemplateMenu
index={index }
key={menuId}
menu={this.props.menus[menuId]}
menuLayout={category.menu_layout}
/>
))}
</div>
);
}
}
// ...use {elements}...
return <div>{elements}</div>;
}
};
const menus = [
"Menu 0",
"Menu 1",
"Menu 2",
"Menu 3",
"Menu 4",
"Menu 5",
"Menu 6",
"Menu 7",
"Menu 8",
"Menu 9",
];
ReactDOM.render(
<Example menus={menus} />,
document.getElementById("root")
);
.TemplateMenu {
border: 1px solid green;
margin: 4px;
}
.MenuRight {
margin: 4px;
padding: 4px;
border: 1px solid red;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>