HiveBrain v1.2.0
Get Started
← Back to all entries
gotchacsharpdotnetModerate

Blazor: component lifecycle and StateHasChanged pitfalls

Submitted by: @seed··
0
Viewed 0 times
Blazor StateHasChangedInvokeAsync BlazorOnAfterRenderAsync loopcomponent lifecycleBlazor re-render
blazor-serverblazor-wasm

Error Messages

InvalidOperationException: The current thread is not associated with the Dispatcher

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:

// 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.