React Data Grid UI crashes after running npm run build and npm run start

82 Views Asked by At

I am facing an issue with my Next.js app where the React Data Grid table UI crashes after building and deploying with npm run build and npm run start. During development (npm run dev), everything works fine. I'm seeking assistance to identify and resolve this issue.

While debugging npm run dev

After deploying with npm run build and npm run start

"use client";

import { useMemo, useState } from "react";
import { createPortal } from "react-dom";
import { faker } from "@faker-js/faker";
import "../../styles/globals.css";

import DataGrid, {
  SelectCellFormatter,
  SelectColumn,
  textEditor,
  type Column,
  type SortColumn,
} from "react-data-grid";

const dateFormatter = new Intl.DateTimeFormat("en");
const currencyFormatter = new Intl.NumberFormat("en", {
  style: "currency",
  currency: "eur",
});

interface SummaryRow {
  id: string;
  totalCount: number;
  yesCount: number;
}

interface Row {
  id: number;
  title: string;
  client: string;
  area: string;
  country: string;
  contact: string;
  assignee: string;
  progress: number;
  startTimestamp: number;
  endTimestamp: number;
  budget: number;
  transaction: string;
  account: string;
  version: string;
  available: boolean;
}

function getColumns(direction: any): readonly Column<Row, SummaryRow>[] {
  return [
    SelectColumn,
    {
      key: "id",
      name: "ID",
      frozen: true,
      resizable: false,
      renderSummaryCell() {
        return <strong>Total</strong>;
      },
    },
    {
      key: "title",
      name: "Task",
      frozen: true,
      renderEditCell: textEditor,
      renderSummaryCell({ row }) {
        return `${row.totalCount} records`;
      },
    },
    {
      key: "client",
      name: "Client",
      width: "max-content",
      draggable: true,
      renderEditCell: textEditor,
    },
    {
      key: "area",
      name: "Area",
      renderEditCell: textEditor,
    },
    {
      key: "contact",
      name: "Contact",
      renderEditCell: textEditor,
    },
    {
      key: "assignee",
      name: "Assignee",
      renderEditCell: textEditor,
    },
    {
      key: "progress",
      name: "Completion",
      renderCell(props) {
        const value = props.row.progress;
        return (
          <>
            <progress max={100} value={value} style={{ inlineSize: 50 }} />{" "}
            {Math.round(value)}%
          </>
        );
      },
      renderEditCell({ row, onRowChange, onClose }) {
        return createPortal(
          <div
            dir={direction}
            className={"dialogContainerClassname"}
            onKeyDown={(event) => {
              if (event.key === "Escape") {
                onClose();
              }
            }}
          >
            <dialog open>
              <input
                autoFocus
                type="range"
                min="0"
                max="100"
                value={row.progress}
                onChange={(e) =>
                  onRowChange({ ...row, progress: e.target.valueAsNumber })
                }
              />
              <menu>
                <button type="button" onClick={() => onClose()}>
                  Cancel
                </button>
                <button type="button" onClick={() => onClose(true)}>
                  Save
                </button>
              </menu>
            </dialog>
          </div>,
          document.body
        );
      },
      editorOptions: {
        displayCellContent: true,
      },
    },
    {
      key: "startTimestamp",
      name: "Start date",
      renderCell(props) {
        return dateFormatter.format(props.row.startTimestamp);
      },
    },
    {
      key: "endTimestamp",
      name: "Deadline",
      renderCell(props) {
        return dateFormatter.format(props.row.endTimestamp);
      },
    },
    {
      key: "budget",
      name: "Budget",
      renderCell(props) {
        return currencyFormatter.format(props.row.budget);
      },
    },
    {
      key: "transaction",
      name: "Transaction type",
    },
    {
      key: "account",
      name: "Account",
    },
    {
      key: "version",
      name: "Version",
      renderEditCell: textEditor,
    },
    {
      key: "available",
      name: "Available",
      renderCell({ row, onRowChange, tabIndex }) {
        return (
          <SelectCellFormatter
            value={row.available}
            onChange={() => {
              onRowChange({ ...row, available: !row.available });
            }}
            tabIndex={tabIndex}
          />
        );
      },
      renderSummaryCell({ row: { yesCount, totalCount } }) {
        return `${Math.floor((100 * yesCount) / totalCount)}% ✔️`;
      },
    },
  ];
}

function rowKeyGetter(row: Row) {
  return row.id;
}

function createRows(): readonly Row[] {
  const now = Date.now();
  const rows: Row[] = [];

  for (let i = 0; i < 1000; i++) {
    rows.push({
      id: i,
      title: `Task #${i + 1}`,
      client: faker.company.name(),
      area: faker.person.jobArea(),
      country: faker.location.country(),
      contact: faker.internet.exampleEmail(),
      assignee: faker.person.fullName(),
      progress: Math.random() * 100,
      startTimestamp: now - Math.round(Math.random() * 1e10),
      endTimestamp: now + Math.round(Math.random() * 1e10),
      budget: 500 + Math.random() * 10500,
      transaction: faker.finance.transactionType(),
      account: faker.finance.iban(),
      version: faker.system.semver(),
      available: Math.random() > 0.5,
    });
  }

  return rows;
}

type Comparator = (a: Row, b: Row) => number;

function getComparator(sortColumn: string): Comparator {
  switch (sortColumn) {
    case "assignee":
    case "title":
    case "client":
    case "area":
    case "country":
    case "contact":
    case "transaction":
    case "account":
    case "version":
      return (a, b) => {
        return a[sortColumn].localeCompare(b[sortColumn]);
      };
    case "available":
      return (a, b) => {
        return a[sortColumn] === b[sortColumn] ? 0 : a[sortColumn] ? 1 : -1;
      };
    case "id":
    case "progress":
    case "startTimestamp":
    case "endTimestamp":
    case "budget":
      return (a, b) => {
        return a[sortColumn] - b[sortColumn];
      };
    default:
      throw new Error(`unsupported sortColumn: "${sortColumn}"`);
  }
}

function Home() {
  const [rows, setRows] = useState(createRows);
  const [sortColumns, setSortColumns] = useState<readonly SortColumn[]>([]);
  const [selectedRows, setSelectedRows] = useState(
    (): ReadonlySet<number> => new Set()
  );

  const columns = useMemo(() => getColumns("ltr"), []);

  const summaryRows = useMemo((): readonly SummaryRow[] => {
    return [
      {
        id: "total_0",
        totalCount: rows.length,
        yesCount: rows.filter((r) => r.available).length,
      },
    ];
  }, [rows]);

  const sortedRows = useMemo((): readonly Row[] => {
    if (sortColumns.length === 0) return rows;

    return [...rows].sort((a, b) => {
      for (const sort of sortColumns) {
        const comparator = getComparator(sort.columnKey);
        const compResult = comparator(a, b);
        if (compResult !== 0) {
          return sort.direction === "ASC" ? compResult : -compResult;
        }
      }
      return 0;
    });
  }, [rows, sortColumns]);

  const gridElement = (
    <DataGrid
      rowKeyGetter={rowKeyGetter}
      columns={columns}
      rows={sortedRows}
      defaultColumnOptions={{
        sortable: true,
        resizable: true,
      }}
      selectedRows={selectedRows}
      onSelectedRowsChange={setSelectedRows}
      onRowsChange={setRows}
      sortColumns={sortColumns}
      onSortColumnsChange={setSortColumns}
      topSummaryRows={summaryRows}
      bottomSummaryRows={summaryRows}
      className="fill-grid"
      direction="ltr"
    />
  );

  return (
    <div>
      <div className={"toolbarClassname"}></div>
      {gridElement}
    </div>
  );
}

export default Home;

What I've Tried:

  • Working fine locally means that when I run npm run dev

  • Mention whether the issue persists only after deployment or also locally.

Expected Outcome:

  • It should be like how it works locally.

Next.js Environment:

  • React version: 18

  • Next.js version: 14.1.0

  • react-data-grid version: 7.0.0-beta.42

  • Package manager: npm

1

There are 1 best solutions below

3
Fabio Nettis On

After creating a new project and rendering the code you have provided I identified the issue to be a hydration error.

This issue occurs because there is a mismatch between the React tree rendered during the first render in the browser (called hydration), and the React tree that was pre-rendered from the server. Specifically this few lines cause the error:

function createRows(): readonly Row[] {
  const now = Date.now();
  const rows: Row[] = [];

  for (let i = 0; i < 1000; i++) {
    rows.push({
      id: i,
      title: `Task #${i + 1}`,
      client: faker.company.name(),
      area: faker.person.jobArea(),
      // ....
    });
  }

  return rows;
}

To solve this issue, generate the rows with Faker.js on the server and pass it to the <Home /> component as prop.

import "react-data-grid/lib/styles.css";

Furthermore I have noticed that this example does not import the style sheet for the library. You may want to do this too: