reactjsreact-window

react-window: How to use actual table tags


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>
  );
};

Solution

  • 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.