Reputation: 11575
I am building a table for which each row is followed by a "expander" row. By default these rows are hidden. Clicking on any row should toggle the visibility of the next row.
The design that makes most sense to me is to think of these pairs of rows as a single "cell", and encapsulate the toggling logic in the Cell element:
class Cell extends React.Component {
# some logic here
render() {
return <tr><td>visibile</td></tr>
<tr><td>invisibile</td></tr>
}
}
This is not allowed and causes React to complain. I can get around this by putting the two tr
nodes in a div
. But that it not only very ugly, but it also makes React complain more and remove them:
Danger.js:107 Danger: Discarding unexpected node: <div >
Eventually, I get into a mess, where the event handlers don't work with this error:
invariant.js:39 Uncaught Error: Invariant Violation:
findComponentRoot(..., .0.1.$0.0.0.$/=11.0.0.1.$0): Unable to find element.
This probably means the DOM was unexpectedly mutated (e.g., by the browser),
usually due to forgetting a <tbody> when using tables, nesting tags like
<form>, <p>, or <a>, or using non-SVG elements in an <svg> parent.
Try inspecting the child nodes of the element with React ID ''.
How should I design my Cell
component, so that I don't have the "Danger" statements, and I can still encapsulate the logic in a single component?
Upvotes: 1
Views: 1269
Reputation: 15509
I also faced this issue and was unwilling to wrap a tbody around my adjacent tr's.
I also tried the <frag>...</frag>
approach but that led to errors.
Upon research I discovered the following approach, which works and does not throw errors (at least not for me in my use-case) - the solution seems to be wrapping the adjacent trs in just <> ... </> - these do not get rendered and allow the one element rule of JSX to be preserved.
UPDATE - I have just discovered that this is the shorthand method of declaring <React.fragment> ... </React.fragment> and is an acceptatble solution - but note that you cannot use keys or attributes on these shorthand notations. link to documentation on fragments - https://reactjs.org/docs/fragments.html
class TableRow extends React.Component {
render() {
return (
<>
<tr> ... </tr>
<tr> ... </tr>
</>
);
}
}
Upvotes: 0
Reputation: 11575
The solution was to wrap the tr
elements in a tbody
, as pointed out by @pawel in the comments.
Upvotes: 3
Reputation: 27225
This seems to be a major issue in react: https://github.com/facebook/react/issues/2127
At this point, your easiest option is probably react-package
. It allows you to create <frag>
tags, which will be removed when the component is rendered:
class Cell extends React.Component {
# some logic here
render() {
return (
<frag>
<tr><td>visibile</td></tr>
<tr><td>invisibile</td></tr>
</frag>
}
}
Alternatively, as @pawel pointed out, you could use multiple <tbody>
elements.
Upvotes: 1