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.

StatusMeaningCell renders
idleNo in-flight commitSaved value
pendingCommit in flightPending value + spinner
errorCommit failedPending value + error badge with retry
conflictSaved value changed underneathPending value + conflict indicator

Table Options

OptionTypeDefaultDescription
onCommitOnCommitFn<TData>--Async handler for saving cell edits. Resolve = success, throw = failure.
autoCommitbooleantrueFire 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

MethodReturnDescription
getCellStatus(rowId, colId)CellStatusGet commit status of a cell
getCellRenderValue(rowId, colId)unknownGet display value (pending or saved)
getCellErrorMessage(rowId, colId)string | undefinedGet error message for failed cell
getCellConflictWith(rowId, colId)unknownGet 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)voidDismiss an error/conflict and revert
dismissAllCommits()voidDismiss 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 conflict status
  • Abort signals -- each patch carries an AbortSignal that fires when a newer edit supersedes it

Notes

  • See the Async Commits Consumer Guide for a full walkthrough
  • The @zvndev/yable-react adapter renders cell status badges automatically via <TableCell>
  • Conflict resolution is left to the consumer -- dismiss to revert, or apply the pending value again