Reputation: 4862
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" class="Menu">...</div>
<div id="m1" class="Menu">...</div>
<div id="m2" class="Menu">...</div>
<div id="m3" class="Menu">...</div>
<div id="m4" class="Menu">...</div>
<div id="m5" class="Menu">...</div>
<div id="m6" class="Menu">...</div>
<div id="m7" class="Menu">...</div>
<div id="m8" class="Menu">...</div>
<div id="m9" class="Menu">...</div>
I want to group every 4 <div>
upon the condition this.props.index % 5 !== 0
to get HTML below:
<div id="m0" class="Menu">...</div>
<div class="MenuRight">
<div id="m1" class="Menu">...</div>
<div id="m2" class="Menu">...</div>
<div id="m3" class="Menu">...</div>
<div id="m4" class="Menu">...</div>
</div>
<div id="m5" class="Menu">...</div>
<div class="MenuRight">
<div id="m6" class="Menu">...</div>
<div id="m7" class="Menu">...</div>
<div id="m8" class="Menu">...</div>
<div id="m9" class="Menu">...</div>
</div>
Any help would be appreciated.
[Edit]
index
doesn't matter. It can be the same as it is now or relative to the group. I just described it in the example code to make it more clear.
At the condition this.props.index % 5 === 0
, the element <div class="Menu">
would be standalone, else the other elements would be grouped with <div class="MenuRight">
.
Upvotes: 0
Views: 441
Reputation: 1914
You can reduce the categories.menuIds
array to a 2D array similar to the mentioned structure. For ex. [[0], [1, 2, 3, 4], [5], [6, 7, 8, 9]...]
. Now even index contains MenuItem [0]
and odd index contain MenuRight [1, 2, 3, 4]
. Now we can conditionally render this 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) {
const i = Math.floor(idx / 5) * 2;
acc[i] = [curr];
} else {
const parentIdx = Math.floor(idx / 5) * 2 + 1;
acc[parentIdx] = acc[parentIdx] || [];
acc[parentIdx] = [...acc[parentIdx], curr];
}
return acc;
}, []);
console.log(transformed);
Render this in React
using Array.map()
<div className="App">
{transformed.map((item, idx) => {
return (
<>
{idx % 2 === 0 && (
<div key={idx} className="Menu">
{item}
</div>
)}
{idx % 2 === 1 && (
<div key={idx} className="MenuRight">
{item.map((menuRight, i) => (
<div key={i} className="Menu">
{menuRight}
</div>
))}
</div>
)}
</>
);
})}
</div>
Upvotes: 1
Reputation: 179
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 (also on CodeSandbox):
function TemplateMenu({ index, id }: { index: number; id: any }) {
return (
<div id={`m${index}`} className="Menu">
{id}
</div>
);
}
/*export default*/ function App() {
const menuIds = ["single 1", 2, 3, 4, 5, "single 2", "a", "b"];
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;
}, []);
return (
<div className="App">
{/*{menuIds.map((id, idx) => {
return <TemplateMenu key={id} index={idx} id={id} />;
})}*/}
{groupedIds.map((group, idx) => {
if (group.length === 1) {
return <TemplateMenu key={group[0]} index={idx} id={group[0]} />;
} else {
return (
<div className="MenuRight">
{group.map((id, idx) => {
return <TemplateMenu key={id} index={idx} id={id} />;
})}
</div>
);
}
})}
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
.Menu {
outline: 1px solid red;
}
.MenuRight {
outline: 1px solid blue;
padding: 4px;
}
<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>
Upvotes: 2
Reputation: 1074989
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 class="MenuRight">`
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 class="MenuRight">`
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>
Upvotes: 1