Optimistic Update with data-query
Let write() apply the change now and roll it back for you if the server says no.
The plain Optimistic Update pattern keeps the saving flag, the revert and the error message in component state by hand.
When the list comes from a data-query (Full build, v1.5 and later), the query already does that work: a write appears on screen the moment you call it, a rejection undoes exactly the fields that write changed, and the query counts the writes still in flight.
Live Demo
The demo runs in its own frame on the Full build. Open it on its own ↗
Source
HTML + JavaScript
<div data-component="task-list">
<p data-show="$tasks.isLoading">Loading tasks…</p>
<div data-list="tasks" data-key="id">
<template>
<div>
<button data-action="toggle" data-bind="done ? 'Undo' : 'Done'"></button>
<span data-bind="label" data-bind-class="done ? 'done' : ''"></span>
</div>
</template>
</div>
<p data-show="$tasks.pendingWrites > 0">Saving…</p>
<p data-show="lastError" data-bind="lastError"></p>
</div>
<script>
wildflower.query('tasks', {
from: () => fetch('/api/tasks').then(r => r.json()),
key: 'id',
// Runs after write() has already applied the change on screen.
// Resolve with the saved record and the row is confirmed; reject
// (or answer with a 4xx/5xx) and the claimed fields roll back.
to: item => fetch('/api/tasks/' + item.id, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(item)
}).then(r => r.json())
});
wildflower.component('task-list', {
state: { lastError: '' },
computed: {
tasks() { return wildflower.getQuery('tasks').rows; }
},
async toggle(event, element, { item }) {
this.lastError = '';
try {
// On screen immediately; settles when the server answers.
await wildflower.getQuery('tasks').write({ id: item.id, done: !item.done });
} catch (e) {
// The rollback has already happened; only the message is left.
this.lastError = item.label + ': ' + e.message + '. Reverted.';
}
}
});
</script>
Key Points
write({ id, done })merges into the row immediately, so the user sees the change before the request is even sent; the request runs through the query'sto- A rejected write, or a not-ok response from a URL destination, rolls back on its own, and only the fields that write claimed: a rename in flight on the same row survives a failed checkbox
$tasks.pendingWritesis the saving indicator for the whole list; it rises as writes dispatch and falls as each settles, success or failurewrite()returns a promise, so a plaintry/catchis where the message comes from; the failure is also on$tasks.syncError, and the rows never go blank- Resolving
towith the saved record confirms the row with no second request; resolving with nothing makes the query refetch instead - Needs the Full build. On the other tiers, or where the list is component state, use the plain pattern; the docs cover the rules in Optimistic Updates and Rollback and Writes