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.
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 devMention 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
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:
To solve this issue, generate the rows with Faker.js on the server and pass it to the
<Home />component as prop.Furthermore I have noticed that this example does not import the style sheet for the library. You may want to do this too: