gotchacsharpdotnetModerate
Blazor: component lifecycle and StateHasChanged pitfalls
Viewed 0 times
Blazor StateHasChangedInvokeAsync BlazorOnAfterRenderAsync loopcomponent lifecycleBlazor re-render
blazor-serverblazor-wasm
Error Messages
Problem
Calling StateHasChanged() unnecessarily from background threads or inside OnAfterRenderAsync without guards causes excessive re-renders or InvalidOperationException about the component not being on the render thread.
Solution
Use InvokeAsync to marshal work back to the renderer's synchronization context:
Dispose timers and subscriptions in IAsyncDisposable:
// Safe: call from background thread
protected override async Task OnInitializedAsync()
{
_timer = new Timer(async _ =>
{
_count++;
await InvokeAsync(StateHasChanged); // safe cross-thread call
}, null, 1000, 1000);
}
// OnAfterRenderAsync — guard with firstRender to avoid infinite loop
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await LoadDataAsync();
StateHasChanged();
}
}Dispose timers and subscriptions in IAsyncDisposable:
public async ValueTask DisposeAsync()
=> await _timer.DisposeAsync();Why
Blazor's renderer is not thread-safe. State mutation from background threads without InvokeAsync corrupts the render tree. OnAfterRenderAsync is called after every render — calling StateHasChanged inside it unconditionally triggers another render, causing an infinite loop.
Gotchas
- EventCallback automatically calls StateHasChanged — no need to call it after event handlers
- Cascading parameters don't trigger re-render when the source changes unless the type is CascadingValue with IsFixed=false
- ShouldRender() override can prevent unnecessary renders but also blocks legitimate updates if logic is wrong
Revisions (0)
No revisions yet.