Client and UI code must explicitly follow the real API/component contract—both for what state transitions are allowed and which side effects/transport paths are actually executed.
Apply this by:
onXChange/callback can suppress the underlying action.Example pattern for transition gating:
// pseudo: fetch/derive allowed destinations for the client
const allowed = await api.getAllowedKanbanTransitions(taskId)
function onDrop(toStatus: KanbanStatus) {
if (!allowed.includes(toStatus)) return // backend will reject; avoid optimistic move
// then call backend and update UI
await api.updateKanbanTask(taskId, { status: toStatus })
await refreshBoard()
}
Outcome: fewer “optimistic” failures, fewer integration drift bugs (wrong transport), and predictable behavior when APIs/components have non-cancelable lifecycle hooks.