BluePrint
BluePrint

Reputation: 2134

React: Multiple table rows in array.map during render

I have this problem. I want to loop an array and display the values as a table where each element is a row in the table. So far, no problems. But depending on a value in each element, I want to show an additional row with more values for each array element. I have this code:

<tbody>
  {myList.map((item, i) => {
    return (
      <div>
        <tr key={i} onClick={toggleMobileOpen.bind(this, i)}>
          <td className="toggler">
            {item.mobile_open && <ArrowUp />}
            {!item.mobile_open && <ArrowDown />}
          </td>
          <td>{item.elem_one}</td>
          <td>{item.elem_two}</td>
          <td>{item.elem_three}</td>
        </tr>
        {item.mobile_open &&
          <tr className="test-td">
            <td>...</td>
          </tr>
        }
      </div>
    );
  })}
</tbody>

My problem is that I get:

Warning: validateDOMNesting(...): Text nodes cannot appear as a child of <tr>.

and

Warning: validateDOMNesting(...): <div> cannot appear as a child of <tbody>

When I try the above approach. Removing the div gives me Syntax error which I already knew but tried anyway.

Does anyone have an idea how to solve this?

Upvotes: 11

Views: 63471

Answers (3)

Jo&#227;o Cunha
Jo&#227;o Cunha

Reputation: 10307

You're doing

{item.mobile_open &&
          <tr className="test-td">
            <td>...</td>
          </tr>
        }

that prints false like <tr>false</tr> if mobile_open is false.

try

{item.mobile_open ?
              (<tr className="test-td">
                <td>...</td>
              </tr>) : null
            }

As for the div warning consider using React 16 Fragments

using React 16.0 fragment syntax

<tbody>
  {myList.map((item, i) => {
    return [
        <tr key={i} onClick={toggleMobileOpen.bind(this, i)}>
          <td className="toggler">
            {item.mobile_open && <ArrowUp />}
            {!item.mobile_open && <ArrowDown />}
          </td>
          <td>{item.elem_one}</td>
          <td>{item.elem_two}</td>
          <td>{item.elem_three}</td>
        </tr>,
        {item.mobile_open &&
          <tr className="test-td">
            <td>...</td>
          </tr>
        }

    ];
  })}
</tbody>

But I prefer the most recent React 16.2 Fragment Syntax

import React, { Fragment } from "react";

<tbody>
      {myList.map((item, i) => {
        return (
          <Fragment>
            <tr key={i} onClick={toggleMobileOpen.bind(this, i)}>
              <td className="toggler">
                {item.mobile_open && <ArrowUp />}
                {!item.mobile_open && <ArrowDown />}
              </td>
              <td>{item.elem_one}</td>
              <td>{item.elem_two}</td>
              <td>{item.elem_three}</td>
            </tr>
            {item.mobile_open &&
              <tr className="test-td">
                <td>...</td>
              </tr>
            }
          </Fragment>
        );
      })}
    </tbody>

More on fragments here

Upvotes: 22

Stuart Casarotto
Stuart Casarotto

Reputation: 1187

In React16 you are now able to return an array of components allowing you to remove the <div />.

The code would look something like :

 <tbody>
      {myList.map((item, i) => {
        return [
            <tr key={i} onClick={toggleMobileOpen.bind(this, i)}>
              <td className="toggler">
                {item.mobile_open && <ArrowUp />}
                {!item.mobile_open && <ArrowDown />}
              </td>
              <td>{item.elem_one}</td>
              <td>{item.elem_two}</td>
              <td>{item.elem_three}</td>
            </tr>,

            //This inline conditional makes it weird but this works
            ...[item.mobile_open &&
              <tr className="test-td">
                <td>...</td>
              </tr>
            ]
        ];
      })}
    </tbody>

Upvotes: 1

mb21
mb21

Reputation: 39528

This says it all: <div> cannot appear as a child of <tbody>

In myList.map(f), f should return exactly one <tr> each time it's called. So you could preprocess myList to contain JSON items than can then be rendered as you wish.

However, it's probably better to output one row for each data item you have, and display the additional information in a different way. Maybe insert a div inside the <td> when you click your arrow icon?

Upvotes: 0

Related Questions