You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js生产环境下React Data Grid UI崩溃问题求助

Next.js生产环境中React Data Grid UI崩溃问题求助

执行npm run build和npm run start构建部署后,React Data Grid表格UI崩溃,但开发环境(npm run dev)下一切正常,现寻求帮助定位并解决该问题。

状态截图

  • 开发环境正常状态:开发环境正常状态
  • 生产环境崩溃状态:生产环境崩溃状态

代码示例

"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;

已尝试操作

  • 确认npm run dev本地运行一切正常
  • 确认问题仅在部署后出现,本地开发无此问题

预期结果

  • 生产环境下的表现与本地开发环境一致

环境信息

  • React版本:18
  • Next.js版本:14.1.0
  • react-data-grid版本:7.0.0-beta.42
  • 包管理器:npm

内容的提问来源于stack exchange,提问作者Abin Das

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 13:44:53