Optimistic Update

Update the UI immediately, roll back on failure.

Live Demo

Toggling the third item simulates a server error and rolls back.

Source

HTML + JavaScript
<div data-component="optimistic-update">
    <div data-list="items" data-key="id">
        <template>
            <div>
                <input type="checkbox" data-model="done"
                       data-action="change:toggleDone">
                <span data-bind="label"
                      data-bind-style="done ? { textDecoration: 'line-through' } : {}"></span>
                <span data-show="saving">saving...</span>
                <span data-show="error" data-bind="error"></span>
            </div>
        </template>
    </div>
</div>

<script>
wildflower.component('optimistic-update', {
    state: {
        items: [
            { id: 1, label: 'Deploy staging', done: false, saving: false, error: '' },
            { id: 2, label: 'Write tests', done: true, saving: false, error: '' },
            { id: 3, label: 'Update docs (will fail)', done: false, saving: false, error: '' }
        ]
    },
    toggleDone(e, el, { index }) {
        // data-model already toggled done: capture the NEW value
        const current = this.items[index].done;
        this.items[index].saving = true;
        this.items[index].error = '';

        const self = this;
        setTimeout(function() {          // Simulated API call
            if (index === 2) {           // Simulate failure: roll back
                self.items[index].done = !current;
                self.items[index].error = 'Save failed: reverted';
            }
            self.items[index].saving = false;
        }, 800);
    }
});
</script>

Key Points

  • Update state immediately for instant UI feedback, then confirm or roll back after the async call
  • data-model="done" two-way binds the checkbox. State changes automatically sync the checked property
  • data-action="change:toggleDone" fires alongside the model to handle the async save/revert logic
  • Per-item saving and error flags give each item its own loading/error state via data-show
  • Nested mutation (self.items[index].done = !current) triggers reactivity, no spread needed