developer guide
Data Grid Optimistic Updates and Rollback
An edit appears the moment it is made, so the grid never feels like it is waiting on the network, and it carries a pending mark until the server answers. A refusal rolls the cell back to what it was and says why, and a conflict is surfaced rather than swallowed.
Developer guide › Editing › Data Grid Optimistic Updates and Rollback
Optimistic writes and rollback
The grid has always written optimistically without calling it that: an edit lands in the
model and is painted before anything else happens. What edit.commit adds is
durability: whether the write reached your server, and what to put back when it
did not.
Nothing changes unless you ask for it. With no commit hook
the grid behaves exactly as before: the value is written, history is recorded,
cell:changed fires, and there is no pending state to think about. Subscribe to
cell:changed, fire your request and ignore the result: that keeps working and
costs nothing.
The usual case, the promise is the answer
edit: {
enabled: true,
commit: async ({ key, colId, value }) => {
const res = await fetch(`/api/rows/${key}`, {
method: 'PATCH',
body: JSON.stringify({ [colId]: value }),
});
if (!res.ok) throw new Error(await res.text()); // throw → rolled back
},
}
Resolving confirms the write; throwing rolls it back and fires cell:reverted
with your error message as reason. A synchronous hook works too: returning
normally confirms, throwing reverts.
When the answer arrives elsewhere
edit: {
enabled: true,
confirm: 'manual', // the return value is ignored
commit: ({ id, key, colId, value }) => {
socket.send(JSON.stringify({ id, key, colId, value }));
},
}
socket.onmessage = (m) => {
const { id, ok, reason } = JSON.parse(m.data);
grid.edit.settle(id, ok, reason);
};
The mode is declared, never guessed. A websocket or event-sourced backend
acknowledges on a different channel from the one the write went out on, so there is no
promise to resolve. confirm: 'manual' says so explicitly. The grid does not infer
it from what commit returns, because then a synchronous hook that happens to
return nothing would leave every cell pending for ever with nothing in your code that looks
wrong. If a write does stay pending, you get a console warning naming the cell: tune the
threshold with pendingTimeout.
The states a write moves through
| State | Means | Event |
|---|---|---|
| pending | Applied and painted, not yet acknowledged. | cell:pending |
| confirmed | The server accepted it. Nothing is written back. | cell:confirmed |
| reverted | The server refused it; the cell is rolled back. | cell:reverted |
| conflict | The write was accepted but the server row had moved underneath it. Last-write-wins: your value stands and the server's truth is surfaced so you can reconcile it. | cell:conflict |
| superseded | A newer edit replaced it while it was in flight. | either, with superseded: true |
Rollback goes to the last confirmed value, not the previous one. This is
the part that is easy to get wrong by hand. Suppose a cell holding 1 is edited to
2, then to 3, then to 4, all before any answer comes
back. If the second write fails, restoring “the value before it” would put back
2, a value the server never held, and one the user has since replaced twice.
So each cell remembers the newest value a confirmation has actually vouched for, and a write
that a later edit has superseded reports its failure without writing anything back. You will
see cell:reverted with applied: false for those.
The rejected value travels on the event as rejected, so you can offer a retry
rather than losing what the user typed.
Two behaviours worth knowing. An unconfirmed edit goes through the normal pipeline, so if it changes a sorted or filtered column the row moves immediately and moves back if the write fails. And undo of an in-flight edit issues a compensating write, a fresh write back to the previous value, itself tracked: rather than pretending to cancel a request that has already gone out.
Asking what is outstanding
grid.edit.pending(); // [{ id, key, colId, value, before, state, age }]
grid.edit.status('r1', 'cap'); // 'pending' | null
Pending cells are marked with --lattice-pending-background and rolled-back ones
flash --lattice-reverted-background; restyle either through the tokens. Both use
the same highlight model as everything else, so the marks survive scrolling, sorting and row
recycling.