'How to deal with "Warning: validateDOMNesting(...): <tr> cannot appear as a child of <div>. " when using react-window to render table rows
I'm using react-window
to create virtual tables with react-table 7
(and material UI tables).
I'm embedding FixedSizeList instead TableBody. Something like this:
<TableBody {...getTableBodyProps()}>
<FixedSizeList
height={listHeight}
itemCount={rows.length}
itemSize={rowHeight}
>
{RenderRow}
</FixedSizeList>)
</TableBody>
and RenderRow returns the TableRows. Something like this:
const RenderRow = React.useCallback( ({ index, style }) =>
{
const row = rows[index];
prepareRow(row);
const rowProps = row.getRowProps();
return (<TableRow
{...row.getRowProps({
style,
})} />);
}
Because of how react-window
works, it creates a couple of div
s to implement the list scrolling, dynamically embedding the needed TableRows as required, causing a react js warning to be output.
webpack-internal:///490:506 Warning: validateDOMNesting(...): <tr> cannot appear as a child of <div>
Just ignoring this warning, isn't something I want to do, as it may cause other warning not to be noticed. (nor do I want to use a release build while testing)
So is it possible to either prevent this warning from being emitted?
Or is it possible to use react-window
for table rows, without getting this warning?
Update:
Trying the setting innerElementType
to tbody
suggestion.
This changes the inner div that FixedSizeList
renders.
from:
<div style="position: relative; height: 96px; overflow: auto; will-change: transform; direction: ltr;">
<div style="height: 96px; width: 100%;">
to
<div style="position: relative; height: 96px; overflow: auto; will-change: transform; direction: ltr;">
<tbody style="height: 96px; width: 100%;">
So the are now included inside tbody.
So I guess I also need to use outerElementType
to change the outer div
, to deal with the div
in table
warning, but I can't think of anything valid that will work...
If I didn't want to include a thead
I could set outerElementType
to table
and innerElementType
to tbody
Solution 1:[1]
FixedSizeList
accepts an innerElementType
prop to let you specify the HTML tag to use instead of div
. As far as I can tell from reading the code, it more or less needs to be a tag string.
You'd probably want this innerElementType
to be tbody
, which would mean re-working the parent elements a bit; I'm guessing you would not want to continue using TableBody
.
Solution 2:[2]
The only workaround I found was to completely remove the semantic html table markup (th, tr, tbody, ...). When using a Material-UI component you can specify as which HTML element it should be rendered via the "component"-prop.
I passed to all table-elements the component="div" prop, which solved the issue.
<TableHead component="div">
Good to know: For SEO and Accessibility this is not a good implementation - but since we are using virtualized lists we already sacrificed those aspects for better performance.
Full Example
Main table
<TableContainer component="section">
<TableToolbar tableInstance={tableInstance} />
<MuiTable {...tableInstance.getTableProps()} component="div">
<TableHead component="div">
{tableInstance.headerGroups.map((headerGroup) => (
<TableRow {...headerGroup.getHeaderGroupProps()} component="div">
{headerGroup.headers.map((column) => (
<TableCell
component="div"
{...(column.id === "selection"
? column.getHeaderProps()
: column.getHeaderProps(column.getSortByToggleProps()))}
>
{column.render("Header")}
{column.id !== "selection" ? (
<TableSortLabel
active={column.isSorted}
// react-table has a unsorted state which is not treated here
direction={column.isSortedDesc ? "desc" : "asc"}
/>
) : null}
</TableCell>
))}
</TableRow>
))}
</TableHead>
<TableBody component="div" ref={tableBodyRef} style={{ width: "100%" }}>
<FixedSizeList
height={tableBodyHeight_inPx}
itemCount={tableInstance.rows.length}
itemSize={rowHeight_inPx}
width={tableBodyWidth} // tableInstance.totalColumnsWidth + 46
className={classnames("virtualized-list", "bigScrollbars")} // showScrollbars is used for css, virtualized-list for clean(er) markup only
>
{RenderRow}
</FixedSizeList>
</TableBody>
<TableFooter component="div">
{/* <TablePagination tableInstance={tableInstance} /> */}
</TableFooter>
</MuiTable>
</TableContainer>
Render Row callback
const RenderRow = React.useCallback(
({ index, style }: { index: number; style: React.CSSProperties }) => {
const row = props.tableInstance.rows[index]
props.tableInstance.prepareRow(row)
return (
<TableRow {...row.getRowProps({ style })} className="tr" component="div">
{row.cells.map((cell: Cell<TRow>) => {
return (
<TableCell {...cell.getCellProps()} className="td" component="div">
{cell.render("Cell")}
</TableCell>
)
})}
</TableRow>
)
},
[
tableInstance.prepareRow,
tableInstance.rows,
tableInstance.state.selectedRowIds,
]
)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | backtick |
Solution 2 | LeonMueller - OneAndOnly |