hellomello
hellomello

Reputation: 8587

Reactjs Looping and Adding Conditional Elements within Loop

How do you deal with conditional within a loop in reactjsx where I want to create a <div> and closing </div> every so often so I can create rows

Right now I have something like this:

renderContent = () => {
  const { classes, product } = this.props;

  if (!product) {
    return (
      <>
        <Content />
        <Space width="10px" />
        <Content />
        <Space width="10px" />
        <Content />
        <Space width="10px" />
        <Content />
        <Space width="10px" />
        <Content />
      </>
    );
  }

  const sizeLength = product.length;
  const content = [];

  for (let i = 0; i < sizeLength; i++) {
    if (i === 0 || i % 5) content.push(<div>)

    content.push(
      <Fragment key={`${i}`}>
        <Content />
        <Space width="10px" />
      </Fragment>,
    );

    if (i === 4 || i % 4 ) content.push(</div>)
  }
  return content;
}

So the code renders 5 <Content /> if product is null. This is all in one row.

What I'm trying to accomplish is to have something like this:

<div>
  <Content />
  <Space width="10px" />
  <Content />
  <Space width="10px" />
  <Content />
  <Space width="10px" />
  <Content />
  <Space width="10px" />
  <Content />
</div>

A wrapping every 5 loop, so if there's a 6th, it would be someting like this:

<div>
  <Content />
  <Space width="10px" />
  <Content />
  <Space width="10px" />
  <Content />
  <Space width="10px" />
  <Content />
  <Space width="10px" />
  <Content />
</div>
<div>
  <Content />
</div>

But the code I have is breaking and doesn't work. Is there something I'm missing? I'm not sure if the content.push is correct, perhaps there's a better way of dealing with this?

Upvotes: 0

Views: 1303

Answers (2)

arseneyr
arseneyr

Reputation: 328

In React JSX, you can't have standalone tags like <div> or </div>. Tags define React elements and must always be self-closing or come in pairs with 0 or more elements in between. For example:

// OK - self closing tag. Creates a React div element with no children
const a = <div />;

// OK - Same as above
const b = <div></div>;

// OK - Creates div element with one child, a br element
const c = <div>
  <br />
</div>;

// OK - Creates a div element and executes JS in the braces
// to create two img children
const d = <div>{
  [<img />, <img />]
}</div>;

// Not OK - Everything between the div tags is treated as text (i.e. `; const e = `)
const d = <div>;
const e = </div>;

As you can see in your code, you are pushing standalone tags into your array which is not OK. Instead, use nested loops and nest the element arrays:

const content = [];
const productLength = product.length || 5;

for (let i = 0; i < productLength; i += 5) {
  const innerContent = [];
  const innerLength = Math.min(5, (productLength - i));
  for (let j = 0; j < innerLength; j++) {
    innerContent.push(
      <Fragment key={i+j}>
        <Content />        
        {
          // Don't render the last space. React ignores null children
          j !== innerLength - 1 ? <Space width="10px" /> : null
        }
      </Fragment>
    );
  }

  content.push(<div>{innerContent}</div>);
}

return content;

It's generally a bad idea to use indices as element keys, so prefer to use an ID that you have on your product object, e.g. key={product[i+j].id}

Upvotes: 1

diedu
diedu

Reputation: 20785

Put the items in a buffer array, then flush them into a div every 5 items and add that div to the rendering array

  const content = [];
  let buffer = [];
  for (let i = 0; i < sizeLength; i++) {
    buffer.push(
      <Fragment key={`${i}`}>
        <Content />
        <Space width="10px" />
      </Fragment>,
    );
    if (i % 5) {
      content.push(<div>{buffer}</div>);
      buffer = [];
    }
  }
  // one last push if there are left over items
  if (buffer.length > 0) content.push(<div>{buffer}</div>);

  return content;

Upvotes: 1

Related Questions