Starter recipe
Controlled onChange
Edit a local table and inspect the complete nextRows callback payload with changed values highlighted.
ControlledonChangeJSON
Interactive, production component
tsx
import { useCallback, useEffect, useRef, useState } from "react";
import { DataTable } from "../../Datatable";
import type { ColumnSetting } from "../../Datatable";
import type { ExampleProps } from "./types";
type ChangeRow = {
id: number;
task: string;
owner: string;
status: "Planned" | "In progress" | "Done";
estimate: number;
};
const keys: Array<keyof ChangeRow> = ["id", "task", "owner", "status", "estimate"];
const initialRows: ChangeRow[] = [
{ id: 1, task: "Audit inventory feed", owner: "Maya", status: "In progress", estimate: 5 },
{ id: 2, task: "Review import errors", owner: "Jon", status: "Planned", estimate: 3 },
{ id: 3, task: "Publish catalog update", owner: "Priya", status: "Done", estimate: 2 },
{ id: 4, task: "Confirm warehouse counts", owner: "Luis", status: "Planned", estimate: 4 },
];
const columns: ColumnSetting<ChangeRow>[] = [
{ id: "id", title: "ID", width: 72, align: "right" },
{ id: "task", title: "Task", width: 220, editor: "text" },
{ id: "owner", title: "Owner", width: 112, editor: "text" },
{
id: "status",
title: "Status",
width: 130,
editor: {
type: "select",
options: ["Planned", "In progress", "Done"].map((value) => ({ value, label: value })),
},
},
{
id: "estimate",
title: "Hours",
width: 88,
align: "right",
editor: { type: "number", min: 0, max: 40, step: 1 },
},
];
function changedPaths(previous: ChangeRow[], next: ChangeRow[]) {
const changed = new Set<string>();
next.forEach((row, rowIndex) => {
const before = previous[rowIndex];
keys.forEach((key) => {
if (!Object.is(before?.[key], row[key])) changed.add(`${rowIndex}.${key}`);
});
});
return changed;
}
function JsonPayload({
rows,
changed,
}: {
rows: ChangeRow[];
changed: ReadonlySet<string>;
}) {
return (
<pre className="onchange-demo__json" aria-label="Latest onChange payload">
<code>{"[\n"}
{rows.map((row, rowIndex) => (
<span className="onchange-demo__json-row" key={row.id}>
{" {\n"}
{keys.map((key, keyIndex) => (
<span className="onchange-demo__json-line" key={key}>
{` "${key}": `}
<span className={changed.has(`${rowIndex}.${key}`) ? "is-changed" : undefined}>
{JSON.stringify(row[key])}
</span>
{keyIndex < keys.length - 1 ? "," : ""}
{"\n"}
</span>
))}
{` }${rowIndex < rows.length - 1 ? "," : ""}\n`}
</span>
))}
{"]"}
</code>
</pre>
);
}
export default function OnChangeExample({ theme }: ExampleProps) {
const [rows, setRows] = useState<ChangeRow[]>(initialRows);
const [changeCount, setChangeCount] = useState(0);
const [changed, setChanged] = useState<Set<string>>(() => new Set());
const rowsRef = useRef(rows);
const highlightTimer = useRef<number | null>(null);
useEffect(() => () => {
if (highlightTimer.current != null) window.clearTimeout(highlightTimer.current);
}, []);
const handleChange = useCallback((nextRows: ChangeRow[]) => {
const nextChanged = changedPaths(rowsRef.current, nextRows);
rowsRef.current = nextRows;
setRows(nextRows);
setChanged(nextChanged);
setChangeCount((count) => count + 1);
if (highlightTimer.current != null) window.clearTimeout(highlightTimer.current);
highlightTimer.current = window.setTimeout(() => {
highlightTimer.current = null;
setChanged(new Set());
}, 1500);
}, []);
return (
<div className="onchange-demo">
<div className="onchange-demo__table">
<div className="example-demo-toolbar" aria-describedby="onchange-demo-help">
<p id="onchange-demo-help" className="example-demo-toolbar__help">
Edit a cell to see the controlled <code>onChange</code> payload update alongside the table.
</p>
</div>
<DataTable
theme={theme}
title="Controlled tasks"
dataSource={rows}
onChange={handleChange}
columnSettings={columns}
maxHeight={360}
enableEditing
enableRowIndex
undoLimit={10}
redoLimit={10}
/>
</div>
<aside className="onchange-demo__panel" aria-labelledby="onchange-payload-title">
<div className="onchange-demo__panel-heading">
<div>
<p>Controlled callback</p>
<h3 id="onchange-payload-title">nextRows JSON</h3>
</div>
<span aria-live="polite">
{changeCount === 0 ? "Waiting for an edit" : `onChange #${changeCount}`}
</span>
</div>
<JsonPayload rows={rows} changed={changed} />
</aside>
</div>
);
}
Related API