Search code examples
reactjsdictionarycomponents

React : wrap every 3 components into a div


I am new to React (and JS) and in order to train I am trying to rewrite an old website I've done. On this website I display a list of X products. for very row there are 3 products to display.

I face an issue about styling. I want to use flexbox and thus I have to wrap every 3 products in a div.

This is what I try to do in the render of a component called GiftList :

 render() {
    const { list } = this.state;

    return (
      <div className="GiftsList">
       {
          list.map((item, index) =>
            {
             
              if(index % 3 === 0){<div>}
              <Product />
              if(index % 3 === 0){</div>}
              
            })   
        }
      
      </div>
    );
  }

I also saw this advice : How to use if within a map return? but without success. I know I messed up with the '{' and '(' and 'return' ... but I don't get it. Can someone explain me how to do it ?

By the way, is this the best way to achived what I want ? like this :

<div>
<Product />
<Product />
<Product />
</div>
<div>
<Product />
<Product />
<Product />
</div>
...

Thanks a lot !!


Solution

  • AFAIK you can't conditionally render opening/closing tags within array::map in react, each element mapped in JSX needs to be a complete node.

    You can achieve the grouping by first splitting your array into an array of chunks, then map both the outer array of chunks and the inner chunk arrays.

    Splitting an array into length-3 chunks there is this solution (closed but points here), though this would require some "pre-processing" before rendering.

    Given some data array [0..15]

    const data = [...Array(16).keys()];
    

    You could copy into a temp array and splice off chunks of 3 elements

    const data2 = [...data];
    const groupedData1 = [];
    while(data2.length) {
      groupedData1.push(data2.splice(0, 3));
    }
    

    const data = [...Array(16).keys()];
    
    const data2 = [...data];
    const groupedData1 = [];
    while(data2.length) {
      groupedData1.push(data2.splice(0, 3));
    }
    console.log(groupedData1);

    If you prefer the more functional, in-line approach commonly used in JSX. Here an array::reduce is used to reduce the data array into chunks of 3, and the final array::filter is used to clear out any ending empty array (as is the case when the chunk length divides the data length.

    const groupedData2 = data
      .reduce((groups, curr) => {
        const arr = groups[groups.length - 1];
        arr.push(curr);
        if (arr.length === 3) groups.push([]);
        return groups;
      }, [[]])
      .filter((chunk) => chunk.length);
    

    const data = [...Array(16).keys()];
    
    const groupedData2 = data
      .reduce((groups, curr) => {
        const arr = groups[groups.length - 1];
        arr.push(curr);
        if (arr.length === 3) groups.push([]);
        return groups;
      }, [[]])
      .filter((chunk) => chunk.length);
    
    console.log(groupedData2);

    Now to render the chunked arrays

    data.<inlineChunkProcess>.map((group, i) => (
      <div key={i}>
        {group.map((product, i) => (
          <Product key={i} value={product} />
        ))}
      </div>
    ))
    

    This is a high-level overview, in actuality you'll not want to use the array index as a react key. When chunking/grouping your data you'll want to generate a unique id for that group and you'll want each product to have its own unique id (which hopefully is already the case). This will help if/when you need to add or remove any single product from your list.

    Example:

    const Group = () => ({
      id: uuidv4(),
      data: []
    });
    
    ...
    
    {list
      .reduce(
        (groups, curr) => {
          const group = groups[groups.length - 1];
          group.data.push(curr);
          if (group.data.length === 3) {
            groups.push(new Group());
          }
          return groups;
        },
        [new Group()]
      )
      .filter(({ data }) => data.length)
      .map(({ data, id }, i) => (
        <div key={id} className="myDiv">
          Group Id: {id}
          <div>
            {data.map(({ id, value }, i) => (
              <Product key={id} value={value} />
            ))}
          </div>
        </div>
      ))}
    

    Edit react-wrap-every-3-components-into-a-div