0

I am currently working on creating a complex table that contains a lot of data. Because of this, I decided to opt for virtualization (react-window). Everything would be fine, but I need to freeze the first row, left and right column in the table. I found a good example: https://codesandbox.io/s/strange-feistel-kwsed?file=/index.js But only the first row and the left column are fixed in the table. It is also necessary to fix the last column on the right. I'm having trouble with this and would like to ask for your help.

index.js

import React from "react";
import ReactDOM from "react-dom";
import { GridWithStickyCells } from "./grid-with-sticky-cells";

import "./styles.css";

// These cell sizes are arbitrary.
// Yours should be based on the content of the cell.
const columnWidths = new Array(1000)
  .fill(true)
  .map(() => 75 + Math.round(Math.random() * 50));
const rowHeights = new Array(1000)
  .fill(true)
  .map(() => 25 + Math.round(Math.random() * 50));

const Cell = ({ columnIndex, rowIndex, style }) => (
  <div
    className={
      columnIndex % 2
        ? rowIndex % 2 === 0
          ? "GridItemOdd"
          : "GridItemEven"
        : rowIndex % 2
        ? "GridItemOdd"
        : "GridItemEven"
    }
    style={style}
  >
    r{rowIndex}, c{columnIndex}
  </div>
);

const Example = () => (
  <GridWithStickyCells
    className="Grid"
    columnCount={1000}
    columnWidth={(index) => columnWidths[index]}
    height={600}
    rowCount={1000}
    rowHeight={(index) => rowHeights[index]}
    width={600}
  >
    {Cell}
  </GridWithStickyCells>
);

ReactDOM.render(<Example />, document.getElementById("root"));

grid-with-sticky-cells.jsx

import React from "react";
import { VariableSizeGrid as Grid } from "react-window";

function getCellIndicies(child) {
  return { row: child.props.rowIndex, column: child.props.columnIndex };
}

function getShownIndicies(children) {
  let minRow = Infinity;
  let maxRow = -Infinity;
  let minColumn = Infinity;
  let maxColumn = -Infinity;

  React.Children.forEach(children, (child) => {
    const { row, column } = getCellIndicies(child);
    minRow = Math.min(minRow, row);
    maxRow = Math.max(maxRow, row);
    minColumn = Math.min(minColumn, column);
    maxColumn = Math.max(maxColumn, column);
  });

  return {
    from: {
      row: minRow,
      column: minColumn
    },
    to: {
      row: maxRow,
      column: maxColumn
    }
  };
}

function useInnerElementType(Cell, columnWidth, rowHeight) {
  return React.useMemo(
    () =>
      React.forwardRef((props, ref) => {
        function sumRowsHeights(index) {
          let sum = 0;

          while (index > 1) {
            sum += rowHeight(index - 1);
            index -= 1;
          }

          return sum;
        }

        function sumColumnWidths(index) {
          let sum = 0;

          while (index > 1) {
            sum += columnWidth(index - 1);
            index -= 1;
          }

          return sum;
        }

        const shownIndecies = getShownIndicies(props.children);

        const children = React.Children.map(props.children, (child) => {
          const { column, row } = getCellIndicies(child);

          // do not show non-sticky cell
          if (column === 0 || row === 0) {
            return null;
          }

          return child;
        });

        children.push(
          React.createElement(Cell, {
            key: "0:0",
            rowIndex: 0,
            columnIndex: 0,
            style: {
              display: "inline-flex",
              width: columnWidth(0),
              height: rowHeight(0),
              position: "sticky",
              top: 0,
              left: 0,
              zIndex: 4
            }
          })
        );

        const shownColumnsCount =
          shownIndecies.to.column - shownIndecies.from.column;

        for (let i = 1; i <= shownColumnsCount; i += 1) {
          const columnIndex = i + shownIndecies.from.column;
          const rowIndex = 0;
          const width = columnWidth(columnIndex);
          const height = rowHeight(rowIndex);

          const marginLeft = i === 1 ? sumColumnWidths(columnIndex) : undefined;

          children.push(
            React.createElement(Cell, {
              key: `${rowIndex}:${columnIndex}`,
              rowIndex,
              columnIndex,
              style: {
                marginLeft,
                display: "inline-flex",
                width,
                height,
                position: "sticky",
                top: 0,
                zIndex: 3
              }
            })
          );
        }

        const shownRowsCount = shownIndecies.to.row - shownIndecies.from.row;

        for (let i = 1; i <= shownRowsCount; i += 1) {
          const columnIndex = 0;
          const rowIndex = i + shownIndecies.from.row;
          const width = columnWidth(columnIndex);
          const height = rowHeight(rowIndex);

          const marginTop = i === 1 ? sumRowsHeights(rowIndex) : undefined;

          children.push(
            React.createElement(Cell, {
              key: `${rowIndex}:${columnIndex}`,
              rowIndex,
              columnIndex,
              style: {
                marginTop,
                width,
                height,
                position: "sticky",
                left: 0,
                zIndex: 2
              }
            })
          );
        }

        return (
          <div ref={ref} {...props}>
            {children}
          </div>
        );
      }),
    [Cell, columnWidth, rowHeight]
  );
}

export function GridWithStickyCells(props) {
  return (
    <Grid
      {...props}
      innerElementType={useInnerElementType(
        props.children,
        props.columnWidth,
        props.rowHeight
      )}
    />
  );
}

I tried to change the code in the likeness, but it did not work out to make the last column stick to the right edge. I spent a day and a half on this, but could not solve the problem. I will be glad for any help. Thank you in advance!

  • This is well written. You might get more answers by putting your code into codesandbox or a similar site. – tgf May 24 '23 at 01:06

0 Answers0