Documenteert het patroon dat in Stories 1-8 is opgeleverd, zodat een volgende workspace-store (sprint, of een nieuwe bounded context) hetzelfde recept volgt. - docs/patterns/workspace-store.md (nieuw): wanneer een workspace-store, de vijf state-slices, selectors-regels (G1/G2), race-safe ensure*Loaded met activeRequestId-guard (G4), SSE-hook + applyRealtimeEvent met unknown-event filter, hidden-tab + reconnect resync via useWorkspaceResync, restore-hint flow met await-chain en URL-prioriteit, optimistic mutations (applyOptimisticMutation/rollback/settle), API endpoint-vereisten (force-dynamic, cache: no-store), test-setup met MemoryStorage + originalActions snapshot + mockImplementation, gotchas G1-G8 als comment-template, en het 8-staps migratiepad. - docs/patterns/zustand-optimistic.md: bijgewerkt voor de nieuwe workspace-store API; verwijst voor het bredere patroon naar workspace-store.md. Voorbeelden voor pbi-order + entity-patch. - CLAUDE.md: patterns quickref aangevuld met workspace-store-rij. Verify: typecheck clean. Refs: PBI-74 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3.4 KiB
3.4 KiB
| title | status | audience | language | last_updated | when_to_read | ||
|---|---|---|---|---|---|---|---|
| Zustand optimistische update + rollback | active |
|
nl | 2026-05-10 | When adding client-side state mutations that need optimistic UI and rollback (DnD, status toggles). |
Patroon: Zustand optimistische update + rollback
Sinds PBI-74 lopen optimistic mutations via applyOptimisticMutation/
rollbackMutation/settleMutation op de workspace-store. Het bredere
patroon (store-design, SSE-integratie, restore-hints, tests) staat in
workspace-store.md. Dit document beschrijft het
DnD/status-mutation flow specifiek.
Patroon
- Snapshot rollback-info via
applyOptimisticMutation— krijgtmutationId. - Pas state direct aan via
setState. - Server-actie aanroepen.
- Op success:
settleMutation(mutationId)(ruimt pending-record op). - Op error:
rollbackMutation(mutationId)(herstelt vorige state + toast).
Cross-priority drag vereist twee mutaties (order + entity-patch) die samen settlen of rollbacken.
Voorbeeld — PBI reorder
import { useProductWorkspaceStore } from '@/stores/product-workspace/store'
function handleDragEnd(event: DragEndEvent) {
const { active, over } = event
if (!over || active.id === over.id) return
const store = useProductWorkspaceStore.getState()
const prevOrder = [...store.relations.pbiIds]
const oldIndex = prevOrder.indexOf(active.id as string)
const newIndex = prevOrder.indexOf(over.id as string)
if (oldIndex === -1 || newIndex === -1) return
const newOrder = arrayMove([...prevOrder], oldIndex, newIndex)
// 1. Snapshot rollback-info
const mutationId = store.applyOptimisticMutation({
kind: 'pbi-order',
prevPbiIds: prevOrder,
})
// 2. Optimistisch toepassen
useProductWorkspaceStore.setState((s) => {
s.relations.pbiIds = newOrder
})
// 3-5. Server bevestigt of niet
startTransition(async () => {
const result = await reorderPbisAction(productId, newOrder)
const st = useProductWorkspaceStore.getState()
if (result.success) {
st.settleMutation(mutationId)
} else {
st.rollbackMutation(mutationId)
toast.error('Volgorde opslaan mislukt')
}
})
}
Voorbeeld — entity-patch (priority-wijziging)
const prevPbi = store.entities.pbisById[id]
const patchMutationId = store.applyOptimisticMutation({
kind: 'entity-patch',
entity: 'pbi',
id,
prev: prevPbi,
})
useProductWorkspaceStore.setState((s) => {
const pbi = s.entities.pbisById[id]
if (pbi) pbi.priority = newPriority
})
// settle/rollback identiek aan order-flow
Mutation-soorten
kind |
Rollback-data | Use-case |
|---|---|---|
pbi-order |
prevPbiIds |
DnD reorder van PBI's |
story-order |
pbiId + prevStoryIds |
DnD reorder van stories binnen een PBI |
task-order |
storyId + prevTaskIds |
DnD reorder van tasks binnen een story |
entity-patch |
entity + id + prev (volledig vorig record of undefined voor delete-rollback) |
Property-wijzigingen (priority, status), of optimistic delete/undelete |
SSE-echo idempotent verwerken
Wanneer de server bevestigt en de NOTIFY-trigger het bijbehorende event
emitteert, mag applyRealtimeEvent geen dubbele insert veroorzaken en
geen rollback triggeren. INSERTs checken bestaan; UPDATEs mergen
into-existing. Zie applyRealtimeEvent in stores/product-workspace/store.ts.