UpdateDebounced
Eliminate timer boilerplate with debounced state updates. Perfect for search inputs, form fields, and any user input that should only trigger updates after the user stops typing.
What is Debouncing?
Debouncing delays the execution of a function until after a specified time has passed since the last invocation. This is essential for performance when handling high-frequency events like typing, where you only want to update state after the user has finished their input.
When you call UpdateDebounced, it starts a timer. If called again before the timer expires,
the timer resets. Only when the timer completes without interruption does the state update execute.
Code Reduction: 94%
UpdateDebounced eliminates the manual timer management pattern that typically requires 17+ lines of boilerplate code.
Before
17 lines of timer management
After
1 line with UpdateDebounced
Reduction
94% less code
Before & After Comparison
See how UpdateDebounced simplifies timer management:
@page "/search"
@inherits StoreComponent<SearchState>
@implements IDisposable
<input @oninput="OnInput" placeholder="Search..." />
@code {
private System.Timers.Timer? _debounceTimer;
void OnInput(ChangeEventArgs e)
{
// Stop existing timer
_debounceTimer?.Stop();
_debounceTimer?.Dispose();
// Create new timer
_debounceTimer = new System.Timers.Timer(300);
_debounceTimer.Elapsed += async (sender, args) =>
{
await Update(s => s.SetQuery(e.Value?.ToString() ?? ""));
_debounceTimer?.Dispose();
};
_debounceTimer.AutoReset = false;
_debounceTimer.Start();
}
public void Dispose() => _debounceTimer?.Dispose();
}
@page "/search"
@inherits StoreComponent<SearchState>
<input @oninput="@(e => UpdateDebounced(s => s.SetQuery(e.Value?.ToString() ?? ""), 300))"
placeholder="Search..." />
UpdateDebounced automatically handles timer creation, cancellation, and disposal.
No need to implement IDisposable or manage timer lifecycle.
Complete Search Example
A real-world search implementation with debounced input and API calls:
Define Search State
public record SearchState(
string Query,
AsyncData<List<Product>> Results)
{
public static SearchState Initial =>
new("", AsyncData<List<Product>>.NotAsked());
public SearchState SetQuery(string query) =>
this with { Query = query };
}
Implement Search Component
@page "/products/search"
@inherits StoreComponent<SearchState>
@inject IProductService ProductService
<div class="search-box">
<input @oninput="@(e => HandleSearchInput(e.Value?.ToString() ?? ""))"
value="@State.Query"
placeholder="Search products..." />
</div>
@if (State.Results.IsLoading)
{
<p>Searching...</p>
}
else if (State.Results.HasData)
{
@foreach (var product in State.Results.Data)
{
<div class="product-card">
<h3>@product.Name</h3>
<p>@product.Price.ToString("C")</p>
</div>
}
}
@code {
async Task HandleSearchInput(string query)
{
// Update query immediately (no debounce for UI feedback)
await Update(s => s.SetQuery(query));
// Debounce the actual search API call
await UpdateDebounced(async s =>
{
if (string.IsNullOrWhiteSpace(query))
return s with { Results = AsyncData<List<Product>>.NotAsked() };
var results = await ProductService.SearchAsync(query);
return s with { Results = AsyncData<List<Product>>.Success(results) };
}, 300);
}
}
Common Use Cases
Where UpdateDebounced shines:
Search Input
Wait for user to finish typing before searching
@oninput="@(e =>
UpdateDebounced(
s => s.SetQuery(e.Value?.ToString() ?? ""),
300))"
Form Auto-Save
Auto-save form data after user stops editing
@oninput="@(e =>
UpdateDebounced(
s => s.UpdateField(field, e.Value),
1000))"
Live Preview
Update preview when user pauses typing
@oninput="@(e =>
UpdateDebounced(
s => s.SetMarkdown(e.Value),
500))"
Input Validation
Validate expensive operations after input stabilizes
@oninput="@(e =>
UpdateDebounced(
async s => await s.ValidateAsync(e.Value),
400))"
API Reference
protected Task UpdateDebounced(
Func<TState, TState> updater,
int delayMs,
string? action = null)
Parameters
| Parameter | Type | Description |
|---|---|---|
updater |
Func<TState, TState> |
Pure function that transforms current state to new state |
delayMs |
int |
Delay in milliseconds before executing update (typical: 200-500ms) |
action |
string? |
Optional action name for DevTools/logging (defaults to "DEBOUNCED_UPDATE") |
UpdateDebounced also supports async updater functions:
await UpdateDebounced(
async s => await s.ValidateAndUpdateAsync(value),
300,
"VALIDATE_INPUT");
Best Practices
✅ Do
// ✅ Use appropriate delays (200-500ms for search, 1000ms+ for auto-save)
await UpdateDebounced(s => s.SetQuery(query), 300);
// ✅ Combine with immediate updates for responsive UI
await Update(s => s.SetQuery(query)); // Immediate UI feedback
await UpdateDebounced(async s => await s.PerformSearch(), 300); // Debounced API call
// ✅ Use descriptive action names for debugging
await UpdateDebounced(s => s.SetFilter(value), 400, "FILTER_PRODUCTS");
// ✅ Handle empty/invalid input before debouncing
if (string.IsNullOrWhiteSpace(query))
{
await Update(s => s.ClearResults());
return;
}
await UpdateDebounced(s => s.Search(query), 300);
❌ Don't
// ❌ Don't use very short delays (defeats the purpose)
await UpdateDebounced(s => s.Update(), 10); // Too short!
// ❌ Don't use for critical immediate updates
await UpdateDebounced(s => s.SubmitForm(), 300); // Use Update() instead!
// ❌ Don't debounce state that needs instant feedback
await UpdateDebounced(s => s.ToggleCheckbox(), 200); // Should be immediate
// ❌ Don't use excessively long delays
await UpdateDebounced(s => s.SetQuery(q), 5000); // Too long, poor UX
- Search/Filter: 200-400ms (responsive feel)
- Form validation: 300-500ms (not too eager)
- Auto-save: 1000-2000ms (reduce API calls)
- Preview/Live updates: 300-700ms (balance performance and responsiveness)
Next Steps
Explore other async helpers to further reduce boilerplate:
UpdateThrottled
Limit update frequency for high-frequency events like scroll or resize
AsyncData<T>
Eliminate loading/error state boilerplate (95% reduction)
ExecuteAsync
Automatic try-catch error handling for async operations
LazyLoad
Automatic caching with request deduplication
📚 Learn More:
- Async Helpers Overview - Complete guide to all helpers
- Best Practices - Patterns and anti-patterns
- Examples - Real-world implementations