Inline Cell Editing
Relevant source files
The following files were used as context for generating this wiki page:
Inline Cell Editing allows users to modify data directly within the table interface by interacting with individual cells. This feature is designed to reduce boilerplate by providing declarative configuration via the .editable() flag on column builders. It supports a variety of data types, including text, numbers, options, booleans, and dates, while managing the complexities of validation, optimistic UI updates, and data persistence.
Sources: plans/053-editable-cells.md:25-34, README.md:124-129
The system follows a tiered save resolution strategy, prioritizing rich client-side callbacks (onCellEdit), then serializable server actions (saveAction), and finally direct adapter updates through the updateRecord method. This architecture ensures that inline editing is compatible with both monolithic frameworks like Next.js and separated frontend/backend deployments using an HTTP Adapter.
Sources: plans/053-editable-cells.md:15-20, plans/055-direct-save-path.md:23-32
Core Architecture and Validation
The engine for cell editing is split between a shared validation core in @better-tables/core and a stateful hook in @better-tables/ui. The core logic involves building a CellEditPolicy derived from the table definition, which handles field allow-listing and type coercion.
Cell Edit Policy
The CellEditPolicy serves as the security and integrity boundary for all writes. It walks the column definitions to identify which fields are editable and defines coercers for different data types (e.g., converting an ISO string back to a Date object).
Sources: packages/core/src/lib/cell-edit-core.ts:13-35, plans/055-direct-save-path.md:129-145
Data Flow Diagram
The following diagram illustrates the flow from a user edit to data persistence:
The diagram shows the sequence of validation, coercion, and the prioritized save resolution order.
Sources: plans/053-editable-cells.md:131-158, plans/055-direct-save-path.md:173-185
UI Components and Interaction
The UI layer utilizes the useEditableCells hook to manage the lifecycle of an edit session, including optimistic updates and rollback state.
Triggering Edits
Editing is triggered by a double-click on a cell or pressing Enter when the cell is focused. The system maintains a pendingEdits map to store optimistic values before they are committed.
Sources: packages/ui/src/hooks/use-editable-cells.ts:15-30, plans/053-editable-cells.md:22-24
Supported Editors
The EditableCell component dynamically swaps the display value for a type-specific editor based on the column configuration.
| Type | Editor Component | Commit Gesture |
|---|---|---|
text | Input / Textarea | Enter / Blur |
number | Input (type="number") | Enter / Blur |
option | Select / Dropdown | Selection |
boolean | Switch / Toggle | Toggle |
date | Calendar / Popover | Date Selection |
custom | editRenderer | Custom |
Sources: packages/ui/src/components/table/editable-cell.tsx:10-50, plans/053-editable-cells.md:175-195
Save Resolution and Persistence
The system supports three primary persistence paths, configured via the BetterTable props.
Save Path Priority Table
| Path | Prop/Method | Best For | Requirement |
|---|---|---|---|
| Callback | onCellEdit | Custom client logic | Manual implementation |
| Action | saveAction | Monoliths (Server Actions) | Serializable function |
| Direct | updateRecord | Standard DB operations | Supported Adapter |
Sources: plans/053-editable-cells.md:15-20, plans/055-direct-save-path.md:173-185
Server Action Integration
For Next.js or TanStack Start, the instance.cellEditAction(tableDef) method generates a serializable function that can be exported directly as a server action.
Sources: plans/055-direct-save-path.md:150-170
// Example Server Action creation
export const saveTicketCell = tables.cellEditAction(ticketsTable);
// Usage in UI
<BetterTable saveAction={saveTicketCell} ... />
Sources: plans/055-direct-save-path.md:209-222
Relationship and Joined-Table Editing
Inline editing supports dot-notation paths for joined tables (e.g., t.text('customer.company').editable()). To enable this, the adapter must implement the resolveCellWriteTarget capability to map the column ID to the specific related table and record ID.
Sources: plans/055-direct-save-path.md:88-115
This sequence illustrates how related table writes are resolved through the adapter.
Sources: plans/055-direct-save-path.md:105-115, packages/ui/src/hooks/use-editable-cells.ts:75-90
Configuration Summary
Inline editing is enabled at the column level using the .editable() method on column builders.
| Option | Type | Description |
|---|---|---|
when | (row) => boolean | Per-row gate to enable/disable editing. |
field | string | Manual storage field mapping (overrides column ID). |
multiline | boolean | Renders a Textarea for text types. |
editRenderer | ReactNode | Escape hatch for providing a custom editor component. |
Sources: packages/core/src/lib/cell-edit-core.ts:30-45, plans/053-editable-cells.md:105-125
Inline Cell Editing significantly streamlines data management by combining declarative column definitions with a robust, multi-path save pipeline, ensuring data integrity through centralized policies while providing a responsive user experience with optimistic updates.