@void-snippets/react
Optimistic Updates
Cache transforms with rollback, effectiveBase stacking, and lifecycle hooks.
#What it does
Makes create, update, and delete feel instant by updating the UI cache immediately — before the API has responded. If the request fails, the change is automatically rolled back. If it succeeds, the real server data replaces the optimistic version.
Configure this by passing an optimistic object to createResourceHooks.
#Configuration
typescript
export const contactHooks = createResourceHooks('contacts', ContactsApis, {
optimistic: {
// Apply the update to the matching item. Return a new array — never mutate.
update: (cache, { _id, payload }) =>
cache.map(item => item._id === _id ? { ...item, ...payload } : item),
// Filter out the deleted item.
remove: (cache, id) =>
cache.filter(item => item._id !== id),
// Insert the new item. tempId is a UUID generated by the library.
create: (cache, { payload, tempId }) => [
{ ...payload, _id: tempId as Contact.Id },
...cache,
],
// Called after a rollback completes. Cache is already restored when this fires.
onError: (error, operation) =>
toast.error(`Failed to ${operation.kind}: ${error.message}`),
// Called after the server confirms the operation.
onSuccess: (operation) => {
if (operation.kind === 'create') analytics.track('contact_created');
},
},
});#Handler reference
| Handler | Inputs | Returns | Purpose |
|---|---|---|---|
update(cache, { _id, payload }) | TBase[], mutation args | TBase[] | Apply the update to the matching item. |
updateSingle(current, payload) | TDetail, TUpdate | TDetail | Override the shallow-merge on the useGet cache. Use when TDetail has nested objects that need deep merging. |
remove(cache, id) | TBase[], TId | TBase[] | Filter out the deleted item. Pagination totals adjust automatically. |
create(cache, { payload, tempId }) | TBase[], mutation args | TBase[] | Insert a new item. tempId is a UUID — use it as _id. |
onError(error, operation) | Error, VSOptimisticOperation | void | Notification after rollback. Cache is already correct. |
onSuccess(operation) | VSOptimisticOperation | void | Notification after server confirmation. |
#VSOptimisticOperation — available in callbacks
typescript
type VSOptimisticOperation<TId, TCreate, TUpdate> =
| { kind: 'create'; payload: TCreate; tempId: string }
| { kind: 'update'; _id: TId; payload: TUpdate }
| { kind: 'remove'; _id: TId }#Concurrent rollback
If you fire three mutations simultaneously and one fails, the library restores the cache to the pre-operation snapshot and then replays the two successful operations in order. None of the other in-flight changes are lost.