I want to use the semantic HTML tags (instead of using divs) to create a table with react-window.
The problem is that List (FixedSizedList) creates two wrappers. The other one is called outerElementType
and is also a prop to FixedSizedList
with default value div
. This means that I can't create the proper table structure, and that all the td
ends up in the first column . It looks like neither of these ones can be omitted.
How do I get around this?
Current code:
import { FixedSizeList as List } from "react-window";
...
return (
<table className="CargoListTable">
<CargoTableHead />
<List
height={600}
itemCount={cargoList.length}
itemSize={35}
width={900}
itemData={cargoList}
innerElementType="tbody"
>
{Row}
</List>
</table>
)
const Row: React.FC<RowProps> = ({ index, style, data }) => {
const cargo = data[index];
return (
<tr
style={style}
key={index}
>
<td>{cargo.registrationNumber}</td>
<td>{cargo.pol}</td>
<td>{cargo.pod}</td>
</tr>
);
};
One possible solution to this would be to have the entire table inside the list. We can use a modified version of the sticky-header example from react-window for this.
You can view a working example in this CodeSandbox: https://codesandbox.io/s/wild-dust-jtf42?file=/src/index.js
We will need two simple elements to render StickyRow
and Row
elements. You can add td
elements here.
const Row = ({ index, style }) => (
<tr className="row" style={style}>
Row {index}
</tr>
);
const StickyRow = ({ index, style }) => (
<tr className="sticky" style={style}>
<th>Sticky Row {index}</th>
</tr>
);
We wrap the FixedSizeList
in a Context that contains the sticky rows. In this case only the first row would be sticky.
const StickyList = ({ children, stickyIndices, ...rest }) => (
<StickyListContext.Provider value={{ ItemRenderer: children, stickyIndices }}>
<List itemData={{ ItemRenderer: children, stickyIndices }} {...rest}>
{ItemWrapper}
</List>
</StickyListContext.Provider>
);
ItemWrapper
renders only the non-sticky rows using the method passed in the main render function(ie- {Row}
). This takes care of rendering the table data.
const ItemWrapper = ({ data, index, style }) => {
const { ItemRenderer, stickyIndices } = data;
if (stickyIndices && stickyIndices.includes(index)) {
return null;
}
return <ItemRenderer index={index} style={style} />;
};
To render the table headers, we need a custom innerElementType.
const innerElementType = forwardRef(({ children, ...rest }, ref) => (
<StickyListContext.Consumer>
{({ stickyIndices }) => (
<table ref={ref} {...rest}>
{stickyIndices.map(index => (
<StickyRow
index={index}
key={index}
style={{ top: index * 35, left: 0, width: "100%", height: 35 }}
/>
))}
<tbody>
{children}
</tbody>
</table>
)}
</StickyListContext.Consumer>
));
This element is aware of the sticky indices because of the context. And renders the header and the body.
This code could be simplified further if it suites your needs.