'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 divs 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