Reputation: 2134
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
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
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
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