UICommander and UIActionTracker
When users interact with your UI, you typically need to execute commands that modify server-side state. UICommander and UIActionTracker work together to make the UI responsive during and after these actions.
Optional Components
Both UICommander and UIActionTracker are optional. You can replace them with your own abstractions if you prefer a different approach to command execution and UI responsiveness. The default implementations provide a well-tested pattern, but Fusion doesn't require them.
UICommander
UICommander is a wrapper around ICommander that provides two additional features:
Reports command execution to
UIActionTracker: Every command started throughUICommanderis registered withUIActionTracker, which tracks running commands and their completion.Enables instant UI updates: The scoped
IUpdateDelayer(when not aFixedDelayer) monitorsUIActionTrackerand resets update delays to zero while commands are running and for a short period after completion.
This means the UI is highly responsive when a user performs actions (showing results immediately), but more resource-efficient when displaying updates from other users or background processes.
Key Methods
| Method | Returns | Description |
|---|---|---|
Call<TResult>(command) | Task<TResult> | Executes command, returns the result value |
Run<TResult>(command) | Task<UIActionResult<TResult>> | Executes command, returns full result with metadata |
Start<TResult>(command) | UIAction<TResult> | Starts command (fire-and-forget style), returns immediately |
Usage Example
From TodoApp/UI/Shared/TodoItemView.razor:
@inherits ComputedStateComponent<TodoItem?>
@* UICommander is available via CircuitHubComponentBase *@
// Toggle completion status
private Task InvertDone()
{
var item = State.LatestNonErrorValue with { IsDone = !item.IsDone };
return UICommander.Run(new Todos_AddOrUpdate(Session, item));
}
// Remove item
private Task Remove()
=> UICommander.Run(new Todos_Remove(Session, Item.Id));The Run method returns a Task that completes when the command finishes. By discarding the result (_ = UICommander.Run(...)), you can fire-and-forget the command while still getting the benefits of instant updates.
UIActionTracker
UIActionTracker monitors all commands executed through UICommander:
RunningActionCount: Number of currently executing commandsLastAction: The most recent action that was started (asAsyncState<UIAction?>)LastResult: The most recent action result (asAsyncState<IUIActionResult?>)AreInstantUpdatesEnabled(): Returnstrueif commands are running or completed recentlyWhenInstantUpdatesEnabled(): Returns aTaskthat completes when instant updates should begin
Configuration Options
public sealed record Options {
// How long after command completion to keep instant updates enabled
public TimeSpan InstantUpdatePeriod { get; init; } = TimeSpan.FromMilliseconds(300);
public MomentClock? Clock { get; init; }
}UpdateDelayer vs FixedDelayer
The IUpdateDelayer abstraction controls how long ComputedState<T> waits before recomputing after invalidation.
FixedDelayer always waits for the configured delay, regardless of user activity:
// Always waits the minimum delay (~32ms) before updates
services.AddScoped<IUpdateDelayer>(_ => FixedDelayer.MinDelay);UpdateDelayer integrates with UIActionTracker to provide responsive updates:
// Normally waits 0.25s, but updates instantly when user is active
services.AddScoped<IUpdateDelayer>(c => new UpdateDelayer(c.UIActionTracker(), 0.25));When using UpdateDelayer:
- If no commands are running and none completed recently → waits the full delay (e.g., 0.25s)
- If a command is running → updates as soon as possible (respects
MinDelayof ~32ms) - If a command completed within
InstantUpdatePeriod(default 300ms) → updates immediately
This behavior makes the UI feel snappy when users perform actions while being efficient when only observing changes from other sources.
Typical Configuration
From TodoApp/UI/ClientStartup.cs:
services.AddScoped<IUpdateDelayer>(c => new UpdateDelayer(c.UIActionTracker(), 0.25));DI Registration
UICommander and UIActionTracker are registered when you call AddBlazor():
services.AddFusion().AddBlazor();| Service | Lifetime | Description |
|---|---|---|
UICommander | Scoped | Command execution wrapper |
UIActionTracker | Scoped | Tracks running/completed UI actions |
You can also use them without Blazor by registering them manually:
services.AddScoped(c => new UIActionTracker(new UIActionTracker.Options(), c));
services.AddScoped(c => new UICommander(c));