Declare a query, bind its loading and error state, and let a shared keyed cache dedupe the requests. Mutations invalidate keys so every component showing that data refetches itself, and navigating away cancels what's in flight — down to the server. Blazor WebAssembly and Server, dependency-free.
@page "/todos"
@inherits DejaComponentBase
@inject TodoApi Api
@if (_todos.IsLoading) { <Spinner/> }
@if (_todos.IsError) { <Alert>@_todos.ErrorMessage</Alert> }
<ul>
@foreach (var todo in _todos.Data ?? [])
{
<li>@todo.Title</li>
}
</ul>
@code {
private readonly Query<List<Todo>> _todos = new();
protected override Task OnInitializedAsync()
=> _todos.Execute("todos", Api.GetTodosAsync);
}Declare a Query<T>, bind IsLoading, IsError
and Data in markup. No flags, no try/catch, no StateHasChanged.
Components sharing a key render instantly from cache, join one in-flight request, and revalidate in the background when stale.
Navigating away aborts in-flight requests down to the server. A newer load supersedes and cancels an older one, so stale responses never win.
State notifies exactly one owner. Two side-by-side components can never re-render each other — provable in the isolation demo.
Mutation<T> tracks writes and invalidates query keys by prefix on
success, so every affected list refetches itself.
No JSON serializers, no reflection-heavy magic — trimming- and AOT-safe, multi-targeting net8.0 through net10.0.