ADR-003: Optimistic Mutations via Factory Hooks¶
Status¶
Accepted
Context¶
Optimistic UI requires the same TanStack Query choreography everywhere: cancel in-flight queries, snapshot the cache, apply the patch, roll back on error, invalidate on settled. Hand-rolling this per mutation duplicated ~40 lines each time and produced subtle inconsistencies (missing cancellation, missing rollback, divergent invalidation).
Decision¶
Optimistic cache updates go through a four-hook factory family in src/hooks/optimistic/: useOptimisticCreate, useOptimisticUpdate, useOptimisticDelete for single-list CRUD, and the generic useOptimisticPatch<TVariables, TResponse, TData> for any other single-query patch (reorders, toggles, detail-object updates). All share the cancel/snapshot/rollback/invalidate plumbing in optimisticMutationHelpers.ts. Error toasts come from the global MutationCache handler; the hooks roll back silently.
Hand-rolled onMutate/onError implementations remain only for mutations spanning multiple cached queries (marker caches, notes lists, paginated transactions), using the cacheUtils helpers.
Consequences¶
- A mutation declares only its patch (
applyPatch) and optional server re-patch (applyResponse); rollback and invalidation are implemented once mutationKeysupport preservesisMutatingguards used by drag-and-drop reorder UIs- Multi-query mutations stay bespoke by design — forcing them through the factory would make the interface as complex as the implementations it hides