Mahesh Dama
Mahesh Dama

Reputation: 103

It takes too long to display 300+ columns using @nextui-org/react Table

Using @nextui-org/react I want to display records in a Table with 300+ columns. The server response time of the API to fetch the data is around 200 ms. It takes around 10 seconds to display the records having a page size of 25 (I've used serverside pagination). When I reduce the column size to 15 or so, it loads the data within a second.

I tried the react-virtualized package to virtualize the columns but, I think it doesn't suppport @nextui-org/react Table. I am getting an error...

TypeError: rowGetter is not a function

Here is my full code.

import React, { useCallback, useEffect, useMemo, useState } from "react";
    import {
      TableHeader,
      TableColumn,
      TableRow,
      TableCell,
      Pagination,
      Spinner,
    } from "@nextui-org/react";
    import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/react/24/outline";
    import { AutoSizer, Column, Table as VirtualizedTable } from 'react-virtualized';

    const ROWS_PER_PAGE = 25;

    const DataTable = ({
      columns,
      items,
      isLoading,
      error,
      refetch,
      params,
      setParams,
      totalRows,
    }) => {
      const [sortDescriptors, setSortDescriptors] = useState(() =>
        params.sortColumns.map((item, i) => ({
          column: item,
          direction: params.sortDirections[i] ?? "ASC",
        }))
      );

      const [page, setPage] = useState(1);

      const pages = totalRows ? Math.ceil(totalRows / ROWS_PER_PAGE) : 0;

      useEffect(() => {
        refetch(params);
      }, [params]);

      useEffect(() => {
        const offset = (page - 1) * ROWS_PER_PAGE;
        setParams((prev) => ({ ...prev, offset }));
      }, [page]);

      useEffect(() => {
        setParams((prev) => ({
          ...prev,
          sortColumns: sortDescriptors.map((i) => i.column),
          sortDirections: sortDescriptors.map((i) => i.direction),
        }));
      }, [sortDescriptors]);

      const handleSorting = useCallback(
        (e, column) => {
          if (column?.sortable) {
            setSortDescriptors((prev) => {
              const existingDescriptor = prev.find(
                (desc) => desc.column === column.uid
              );
              if (existingDescriptor) {
                existingDescriptor.direction =
                  existingDescriptor.direction === "ASC" ? "DESC" : "ASC";
                return [...prev];
              } else {
                return [...prev, { column: column.uid, direction: "ASC" }];
              }
            });
          }
        },
        [sortDescriptors]
      );

      const rowGetter = ({ index }) => items[index];

      const renderHeader = ({ columnData }) => {
        const { column } = columnData;
        return (
          <TableColumn
            key={column.uid}
            allowsSorting={false}
            onClick={(e) => handleSorting(e, column)}
            style={{
              width: column.width ? column.width : "",
              minWidth: column.minWidth ? column.minWidth : "",
              maxWidth: column.maxWidth ? column.maxWidth : "",
              backgroundColor: "#bfdbfe",
              ...column.style,
            }}
            className={`${
              column?.sortable ? "cursor-pointer" : "cursor-default"
            }`}
          >
            <div className={`inline-block w-full ${column.align ? column.align : ""}`}>
              {column.name}
            </div>
            {column.sortable && (
              <SortIcon
                column={column}
                direction={
                  sortDescriptors.find((desc) => desc.column === column.uid)
                    ?.direction
                }
              />
            )}
          </TableColumn>
        );
      };

      const renderRow = ({ index, key, style }) => {
        const item = rowGetter({ index });
        return (
          <TableRow key={key} style={style}>
            {columns.map((column) => (
              <TableCell
                key={column.uid}
                className={`${column.align ? column.align : ""}`}
                style={{
                  width: column.width ? column.width : "",
                  minWidth: column.minWidth ? column.minWidth : "",
                  maxWidth: column.maxWidth ? column.maxWidth : "",
                }}
              >
                {column.hasOwnProperty("renderCell")
                  ? column.renderCell(item, column.uid)
                  : item[column.uid]}
              </TableCell>
            ))}
          </TableRow>
        );
      };

      return (
        <div style={{ width: "100%", height: "100%" }}>
          <AutoSizer>
            {({ height, width }) => (
              <VirtualizedTable
                width={width}
                height={height}
                headerHeight={50}
                rowHeight={40}
                rowCount={items.length}
                rowGetter={rowGetter}
                rowRenderer={renderRow}
                overscanRowCount={5}
              >
                {columns.map((column) => (
                  <Column
                    key={column.uid}
                    label={column.name}
                    dataKey={column.uid}
                    width={column.width || 100}
                    minWidth={column.minWidth || 50}
                    maxWidth={column.maxWidth || 200}
                    headerRenderer={renderHeader}
                    columnData={{ column }}
                  />
                ))}
              </VirtualizedTable>
            )}
          </AutoSizer>
          <Pagination
            showControls
            classNames={{ cursor: "bg-primary text-background" }}
            color="default"
            page={page}
            total={pages}
            variant="light"
            onChange={setPage}
          />
          {isLoading && <Spinner label="Loading..." />}
          {error && <div>{error.message}</div>}
        </div>
      );
    };

    const SortIcon = ({ column, direction }) => {
      const iconClassName = `w-3 mb-px text-inherit inline-block transition-transform-opacity data-[visible=true]:opacity-100 group-data-[hover=true]:opacity-100 data-[direction=ascending]:rotate-180 ml-0 absolute ${
        !direction ? "opacity-0" : "opacity-100"
      }`;

      return direction === "DESC" ? (
        <ChevronDownIcon className={iconClassName} />
      ) : (
        <ChevronUpIcon className={iconClassName} />
      );
    };
export default DataTable;
  

Upvotes: 0

Views: 171

Answers (0)

Related Questions