@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

HandlerInputsReturnsPurpose
update(cache, { _id, payload })TBase[], mutation argsTBase[]Apply the update to the matching item.
updateSingle(current, payload)TDetail, TUpdateTDetailOverride the shallow-merge on the useGet cache. Use when TDetail has nested objects that need deep merging.
remove(cache, id)TBase[], TIdTBase[]Filter out the deleted item. Pagination totals adjust automatically.
create(cache, { payload, tempId })TBase[], mutation argsTBase[]Insert a new item. tempId is a UUID — use it as _id.
onError(error, operation)Error, VSOptimisticOperationvoidNotification after rollback. Cache is already correct.
onSuccess(operation)VSOptimisticOperationvoidNotification 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.