Optimistisches Schreiben

Schalte die UI sofort um — mit der Updater-Form von SetData —, führe den Request danach aus, und rolle bei einem Fehler auf einen Snapshot zurück.

Optimistisches Umschalten mit Rollback
List query IsLoading IsReFetching IsError IsCachedData IsStale ReFetchCount: 1UpdatedAt: 20:33:22
Update mutation IsLoading IsError Data: null
  • delectus aut autem
  • quis ut nam facilis et officia qui
  • fugiat veniam minus
  • et porro tempora
  • laboriosam mollitia et enim quasi adipisci quia provident illum
  • qui ullam ratione quibusdam voluptatem quia omnis

Toggling flips the checkbox immediately via SetData's updater form; the request runs afterwards. Arm a failure first and the rollback restores the snapshot.

@inherits DejaComponentBase
@inject JsonPlaceholderApi Api
@inject DejaClient Client
@inject FailureSwitch Failure

<div class="demo-toolbar">
    <button class="demo-button danger" @onclick="() => Failure.Arm(FailureSwitch.FailureKind.Generic)">
        @(Failure.Armed != FailureSwitch.FailureKind.None ? "Next request will fail ✓" : "Arm a failure, then toggle")
    </button>
</div>

<StateInspector Query="_todos" Label="List query"/>
<MutationInspector Mutation="_update" Label="Update mutation"/>

@if (_todos.Data is { } todos)
{
    <ul class="demo-list">
        @foreach (var todo in todos)
        {
            <li>
                <input type="checkbox" checked="@todo.Completed" @onchange="() => Toggle(todo)"/>
                <span class="@(todo.Completed ? "done" : null)">@todo.Title</span>
            </li>
        }
    </ul>
}
else if (_todos.IsLoading)
{
    <p class="demo-note">Loading…</p>
}

@if (_rolledBack)
{
    <div class="demo-error">The write failed — the optimistic update was rolled back.</div>
}

<p class="demo-note">
    Toggling flips the checkbox <em>immediately</em> via <code>SetData</code>'s updater form; the
    request runs afterwards. Arm a failure first and the rollback restores the snapshot.
</p>

@code {
    private readonly Query<IReadOnlyList<TodoDto>> _todos = new();
    private readonly Mutation<TodoDto> _update = new();
    private bool _rolledBack;

    private static QueryKey Key => DocsKeys.TodoList(6);

    protected override Task OnInitializedAsync()
        => _todos.Execute(Key, token => Api.GetTodosAsync(6, token), p => p.OnError = _ => { });

    private async Task Toggle(TodoDto todo)
    {
        _rolledBack = false;
        var toggled = todo with { Completed = !todo.Completed };

        // 1. Snapshot, 2. write the cache optimistically — every subscriber re-renders now.
        var snapshot = Client.GetData<IReadOnlyList<TodoDto>>(Key);
        Client.SetData(Key, (IReadOnlyList<TodoDto>? current)
            => [.. (current ?? []).Select(t => t.Id == toggled.Id ? toggled : t)]);

        // 3. Run the real request; on failure, restore the snapshot.
        await _update.Execute(token => Api.UpdateTodoAsync(toggled, token), p =>
        {
            p.OnError = _ =>
            {
                _rolledBack = true;
                if (snapshot is not null)
                {
                    Client.SetData(Key, snapshot);
                }
            };
        });
    }
}

Das Muster#

  1. SnapshotClient.GetData<T>(key) hält den aktuellen Wert fest.
  2. Optimistischer SchreibvorgangClient.SetData(key, updater) transformiert den gecachten Wert; jede abonnierte Query rendert sofort neu, noch vor jeglichem Netzwerkverkehr.
  3. Der echte Request — eine ganz normale Mutation<T>.
  4. Rollback bei Fehler — der OnError-Callback schreibt den Snapshot zurück.
Probiere den Fehlerpfad aus

Klicke auf „Arm a failure, then toggle“: Die Checkbox schaltet sofort um, der Request schlägt etwa eine Sekunde später fehl (erhöhe die Latenz, um das zu strecken), und der Rollback schnappt sie zurück.

Für die nicht-optimistische Alternative — auf den Server warten und ihn entscheiden lassen — nutze stattdessen InvalidateKeys auf der Mutation, wie im Mutations-Guide.