How to implement horizontal virtualisation in tanstack table v8?

1.7k Views Asked by At

I am building a full scale, editable, sortable table using tanstack table. I have already added virtualisation to rows using the example provided at tanstack table website. However, I also want to implement horizontal virtualisation, and I am lost on ideas. The only thing that I could think of was virtualising th but that would virtualise the headers and not the entire table. Any ideas on how this could be solved?


`import {
  Cell,
  Column,
  ColumnDef,
  flexRender,
  getCoreRowModel,
  Header,
  Row,
  useReactTable,
} from '@tanstack/react-table';
import React, { useRef, useState } from 'react';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { useVirtual } from 'react-virtual';
import { useListViewContext } from './context/ListViewContext';
import DraggableRow from './DraggableRow';
import './index.css';

export type Props = {
  columns: ColumnDef<any>[];
};

const Table: React.FC<Props> = ({ columns }) => {
  const { data, reorderRow } = useListViewContext();
  const [rowSelection, setRowSelection] = useState({});
  const [hoveredRow, setHoveredRow] = useState<string | null>(null);
  const [focusedCell, setFocusedCell] = useState<Cell<any, any> | null>(null);
  const tableContainerRef = useRef<HTMLDivElement>(null);
  const table = useReactTable({
    data,
    columns,
    state: {
      rowSelection,
    },
    enableRowSelection: true,
    onRowSelectionChange: setRowSelection,
    columnResizeMode: 'onChange',
    getCoreRowModel: getCoreRowModel(),
  });

  const { rows } = table.getRowModel();
  const [{ headers }] = table.getHeaderGroups();
  const mainHeaders = headers.slice(0, 3);
  const virtualizedHeaders = headers.slice(3);
  console.log(mainHeaders, virtualizedHeaders);
  const rowVirtualizer = useVirtual({
    parentRef: tableContainerRef,
    size: rows.length,
    overscan: 10,
  });
  // const colVirtualizer = useVirtual({
  //   horizontal: true,
  //   size: headers.length,
  //   // estimateSize: React.useCallback(() => {
  //   //   let totalWidth = 0;
  //   //   headerGroups[0].headers.forEach((header) => {
  //   //     totalWidth += header.getSize();
  //   //   });
  //   //   return totalWidth;
  //   // }, [headerGroups]),
  //   parentRef: tableContainerRef,
  //   overscan: 10,
  // });
  // const { virtualItems: virtualCols, totalSize: totalWidth } = colVirtualizer;

  const { virtualItems: virtualRows, totalSize } = rowVirtualizer;
  const paddingTop = virtualRows.length > 0 ? virtualRows?.[0]?.start || 0 : 0;
  const paddingBottom =
    virtualRows.length > 0
      ? totalSize - (virtualRows?.[virtualRows.length - 1]?.end || 0)
      : 0;

  // const paddingLeft = virtualCols.length > 0 ? virtualCols?.[0]?.start || 0 : 0;
  // const paddingRight =
  //   virtualCols.length > 0
  //     ? totalWidth - (virtualCols?.[virtualCols.length - 1]?.end || 0)
  //     : 0;
  return (
    <DndProvider backend={HTML5Backend}>
      <div
        ref={tableContainerRef}
        className={`h-full mt-4 mx-5 relative overflow-x-auto`}
      >
        <table
          className="table-fixed absolute left-0 border-separate border-spacing-0"
          {...{
            style: {
              width: table.getCenterTotalSize(),
            },
          }}
        >
          <thead>
            {table?.getHeaderGroups().map((headerGroup) => (
              <tr key={headerGroup.id}>
                <th className="th w-6"></th>
                {headerGroup.headers.map((header) => (
                  <th
                    key={header.id}
                    {...{
                      className:
                        'th border-r-[0.4px] border-t-[0.4px] border-b border-grey5 text-xs 
                         p-2 bg-grey2 hover:bg-grey3 text-grey6',
                      style: {
                        width: header.getSize(),
                      },
                    }}
                  >
                    {header.isPlaceholder
                      ? null
                      : flexRender(
                          header.column.columnDef.header,
                          header.getContext(),
                        )}
                    <div
                      {...{
                        onMouseDown: header.getResizeHandler(),
                        onTouchStart: header.getResizeHandler(),
                        className: `resizer ${
                          !header.column.getCanResize() ? 'hidden' : 'block'
                        } ${header.column.getIsResizing() ? 'isResizing' : ''}`,
                      }}
                    />
                  </th>
                ))}
              </tr>
            ))}
          </thead>
          <tbody>
            {paddingTop > 0 && (
              <tr>
                <td style={{ height: `${paddingTop}px` }} />
              </tr>
            )}
            {virtualRows.map((virtualRow) => {
              const row = rows[virtualRow.index] as Row<any>;
              return (
                <DraggableRow
                  key={row.id}
                  row={row}
                  hoveredRow={hoveredRow}
                  focusedCell={focusedCell}
                  setFocusedCell={setFocusedCell}
                  setHoveredRow={setHoveredRow}
                  reorderRow={reorderRow}
                />
              );
            })}
            {paddingBottom > 0 && (
              <tr>
                <td style={{ height: `${paddingBottom}px` }} />
              </tr>
            )}
          </tbody>
        </table>
      </div>
    </DndProvider>
  );
};

export default Table;`

Expecting column virtualisation your text

1

There are 1 best solutions below

0
On

Could be too late, but still: There is an example in official documentation https://tanstack.com/virtual/v3/docs/examples/react/fixed, check for GridVirtualizerFixed implementation.

Tables are not so flexible as block elements, so the only option to scroll both head and content is to change the markup.