Skip to content

React table inline cell editing — opt-in onCellEdit

Try it live: open a Mantine starter in StackBlitz — this page’s feature is already wired in src/App.tsx (editable columns + onCellEdit); edit it in the browser, no install. Other UI kits →

See it working: edit cells in the live demo — a real table you can type into, not a recording.

Edit a cell in place by passing onCellEdit and marking columns editable. Omit onCellEdit and the table never opens an editor — even if columns declare editable. The table never mutates rows; your handler applies the change.

import { useState } from "react";
import { DataTable } from "@adapttable/mantine"; // or mui, chakra, antd, radix, base-ui, shadcn, unstyled
interface Person {
id: string;
name: string;
role: string;
status: "Active" | "Planned" | "Blocked";
}
const SEED: Person[] = [
{ id: "1", name: "Aisha", role: "Engineer", status: "Active" },
{ id: "2", name: "Jonas", role: "Designer", status: "Planned" },
];
export function People() {
const [rows, setRows] = useState(SEED);
return (
<DataTable
data={rows}
columns={[
{ key: "name", sortable: true, editable: true },
{
key: "status",
editable: true,
editor: {
type: "select",
options: ["Active", "Planned", "Blocked"],
},
},
{ key: "role", editable: (row) => row.status !== "Blocked" },
]}
rowKey={(r) => r.id}
onCellEdit={(row, key, nextValue) => {
setRows((prev) =>
prev.map((r) =>
r.id === row.id ? { ...r, [key]: nextValue as never } : r
)
);
}}
/>
);
}
  • Opt-in. onCellEdit is the switch. Without it, cells stay plain display (package DNA: nothing is pushed on the developer).
  • Per-column. editable is true, false, or (row) => boolean. The editor defaults to "text"; use "number" or { type: "select", options } for the other kits.
  • Keyboard. Double-click / Enter / F2 begins; Enter commits; Escape cancels and restores focus; Tab / Shift+Tab commits and advances to the next editable cell.
  • One-way data flow. The commit payload is onCellEdit(row, key, nextValue) — adapters render kit-native inputs; core owns the state machine so every kit behaves the same.
  • Out of scope (by design): row-level edit mode, validation UI, and optimistic-update helpers — persistence stays with the host.
Prop / fieldTypeDefaultDescription
onCellEdit(row: TRow, key: string, nextValue: unknown) => voidChange channel; its presence enables editing.
editableboolean | ((row: TRow) => boolean)Whether this column can open an editor (still requires onCellEdit).
editor"text" | "number" | { type: "select"; options }"text"Widget for the active cell.
editValue(row: TRow) => stringDraft seed when the displayed cell is formatted but editing needs the raw.
labelsTableLabelsEnglishOverride editCell for the activate control’s accessible name.
  • Works on desktop rows and mobile cards, LTR and RTL.
  • Custom Cell / accessor still render in display mode; the editor replaces them only while that cell is active.
  • Prefer updating your row list immutably in onCellEdit so React sees a new data / source identity.

See it live in the demo — double-click the Email column.