How do I wrap a React component that returns multiple table rows and avoid the "<tr> cannot appear as a child of <div>" error?
I have a component called OrderItem that takes an object with multiple objects (at least two) inside it, and renders them as multiple rows inside a table. There will be multiple OrderItem components inside the table. The problem is that in the component's render function, I can't return multiple lines. I can only return a single component, and if I wrap them in a div, it says " <tr>
cannot appear as a child of <div>
"
The code looks something like this (I left some stuff out for easier readability)
Parent() {
render() {
return (
<table>
<tbody>
{
_.map(this.state.orderItems, (value, key) => {
return <OrderItem value={value} myKey={key}/>
})
}
</tbody>
</table>
)
}
}
class OrderItem extends React.Component {
render() {
return (
<div> // <-- problematic div
<tr key={this.props.myKey}>
<td> Table {this.props.value[0].table}</td>
<td> Item </td>
<td> Option </td>
</tr>
{this.props.value.map((item, index) => {
if (index > 0) { // skip the first element since it's already used above
return (
<tr key={this.props.myKey + index.toString()}>
<td><img src={item.image} alt={item.name} width="50"/> {item.name}</td>
<td>{item.selectedOption}</td>
</tr>
)
}
})}
</div>
)
}
}
Is there a way I can return those multiple rows and have them be in the same table without wrapping them in a div and getting an error? I realize I can make a separate table for each component, but that throws my formatting off a bit.
Solution 1:
React 16
is now here to rescue, you can now use React.Fragment
to render list of elements without wrapping it into a parent element. You can do something like this:
render() {
return (
<React.Fragment>
<tr>
...
</tr>
</React.Fragment>
);
}
Solution 2:
Yes!! It is possible to map items to multiple table rows inside a table. A solution which doesn't throw console errors and semantically is actually correct, is to use a tbody
element as the root component and fill with as many rows as required.
items.map(item => (
<tbody>
<tr>...</tr>
<tr>...</tr>
</tbody>
))
The following post deals with the ethical questions about it and explains why yes we can use multiple tbody
elements
Can we have multiple <tbody> in same <table>?
Solution 3:
One approach is to split OrderItem
into two components, moving the rendering logic into a method Parent.renderOrderItems
:
class Parent extends React.Component {
renderOrderItems() {
const rows = []
for (let orderItem of this.state.orderItems) {
const values = orderItem.value.slice(0)
const headerValue = values.shift()
rows.push(
<OrderItemHeaderRow table={headerValue.table} key={orderItem.key} />
)
values.forEach((item, index) => {
rows.push(
<OrderItemRow item={item} key={orderItem.key + index.toString()} />
)
})
}
return rows
}
render() {
return (
<table>
<tbody>
{ this.renderOrderItems() }
</tbody>
</table>
)
}
}
class OrderItemHeaderRow extends React.Component {
render() {
return (
<tr>
<td> Table {this.props.table}</td>
<td> Item </td>
<td> Option </td>
</tr>
)
}
}
class OrderItemRow extends React.Component {
render() {
const { item } = this.props
return (
<tr>
<td>
<img src={item.image} alt={item.name} width="50"/>
{item.name}
</td>
<td>
{item.selectedOption}
</td>
</tr>
)
}
}