Async Cell Commits
Save cell edits to a backend with built-in optimistic updates, error handling, retry, and conflict detection. No other React grid ships this feature.
How to Use
import { createColumnHelper } from '@zvndev/yable-core'
import { CommitError } from '@zvndev/yable-core'
const table = useTable({
data,
columns,
enableCellEditing: true,
autoCommit: true, // fire onCommit after each edit (default)
onCommit: async (patches) => {
// patches is CellPatch[] — one per edited cell
const res = await fetch('/api/save', {
method: 'POST',
body: JSON.stringify(patches.map(p => ({
rowId: p.rowId,
column: p.columnId,
value: p.value,
}))),
signal: patches[0].signal, // wire abort signal
})
if (!res.ok) {
// Throw CommitError for per-cell error messages
throw new CommitError({
[patches[0].rowId]: {
[patches[0].columnId]: 'Server rejected the update',
},
})
}
},
})
Cell Status Lifecycle
Each cell has a status: idle | pending | error | conflict.
| Status | Meaning | Cell renders |
|---|---|---|
idle | No in-flight commit | Saved value |
pending | Commit in flight | Pending value + spinner |
error | Commit failed | Pending value + error badge with retry |
conflict | Saved value changed underneath | Pending value + conflict indicator |
Table Options
| Option | Type | Default | Description |
|---|---|---|---|
onCommit | OnCommitFn<TData> | -- | Async handler for saving cell edits. Resolve = success, throw = failure. |
autoCommit | boolean | true | Fire onCommit after each edit; if false, batch edits until table.commit() |
rowCommitRetryMode | 'failed' | 'batch' | 'failed' | On retry: resend only failed cells or entire batch |
Table API
| Method | Return | Description |
|---|---|---|
getCellStatus(rowId, colId) | CellStatus | Get commit status of a cell |
getCellRenderValue(rowId, colId) | unknown | Get display value (pending or saved) |
getCellErrorMessage(rowId, colId) | string | undefined | Get error message for failed cell |
getCellConflictWith(rowId, colId) | unknown | Get conflicting server value |
commit() | Promise<void> | Commit all pending edits (when autoCommit: false) |
retryCommit(rowId, colId) | Promise<void> | Retry a failed commit |
dismissCommit(rowId, colId) | void | Dismiss an error/conflict and revert |
dismissAllCommits() | void | Dismiss all errors/conflicts |
Per-Column Commit Handler
Override the table-level onCommit for specific columns:
columnHelper.accessor('price', {
header: 'Price',
editable: true,
commit: async (patch) => {
await updatePrice(patch.rowId, patch.value)
},
})
What Yable Handles Automatically
- Stale settlement -- older in-flight commits are silently dropped if a newer commit lands first
- Auto-clear -- when refetched data matches the pending value, the pending state clears automatically
- Orphaned GC -- if a row disappears while a commit is in flight, the record is cleaned up
- Conflict detection -- if the saved value changes between dispatch and settlement, the cell enters
conflictstatus - Abort signals -- each patch carries an
AbortSignalthat fires when a newer edit supersedes it
Notes
- See the Async Commits Consumer Guide for a full walkthrough
- The
@zvndev/yable-reactadapter renders cell status badges automatically via<TableCell> - Conflict resolution is left to the consumer -- dismiss to revert, or apply the pending value again