Home > Mobile >  How to conditionally group HTML tags in React
How to conditionally group HTML tags in React

Time:12-18

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>

  • Related