ASP.NET Core Blazor component rendering

Components must render when they're first added to the component hierarchy by their parent component. This is the only time that a component strictly must render.

Components may choose to render at any other time according to their own logic and conventions.

Conventions for ComponentBase

By default, Razor components (.razor) inherit from the ComponentBase base class, which contains logic to trigger rerendering at the following times:

  • After applying an updated set of parameters from a parent component.
  • After applying an updated value for a cascading parameter.
  • After notification of an event and invoking one of its own event handlers.
  • After a call to its own StateHasChanged method.

Components inherited from ComponentBase skip rerenders due to parameter updates if either of the following are true:

  • All of the parameter values are of known immutable primitive types (for example, int, string, DateTime) and haven't changed since the previous set of parameters were set.
  • The component's ShouldRender method returns false.

For more information on ShouldRender, see ASP.NET Core Blazor WebAssembly performance best practices.

Control the rendering flow

In most cases, ComponentBase conventions result in the correct subset of component rerenders after an event occurs. Developers aren't usually required to provide manual logic to tell the framework which components to rerender and when to rerender them. The overall effect of the framework's conventions is that the component receiving an event rerenders itself, which recursively triggers rerendering of descendant components whose parameter values may have changed.

For more information on the performance implications of the framework's conventions and how to optimize an app's component hierarchy, see ASP.NET Core Blazor WebAssembly performance best practices.

When to call StateHasChanged

The ComponentBase.StateHasChanged method allows you to trigger a render at any time. However, be careful not to call StateHasChanged unnecessarily, which is a common mistake, because it imposes unnecessary rendering costs.

You should not need to call StateHasChanged when:

  • Routinely handling events, whether synchronously or asynchronously, since ComponentBase triggers a render for most routine event handlers.
  • Implementing typical lifecycle logic, such as OnInitialized or OnParametersSetAsync, whether synchonrously or asynchronously, since ComponentBase triggers a render for typical lifecycle events.

However, it might make sense to call StateHasChanged in the cases described by the following sections:

An asynchronous handler involves multiple asynchronous phases

Consider the following Counter component, which updates the count four times on each click.

Pages/Counter.razor:

@page "/counter"

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}

Due to the way that tasks are defined in .NET, a receiver of a Task can only observe its final completion, not intermediate asynchronous states. Therefore, ComponentBase can only trigger rerendering when the Task is first returned and when the Task finally completes. It can't know to rerender at other intermediate points. If you want to rerender at intermediate points, use StateHasChanged.

Receiving a call from something external to the Blazor rendering and event handling system

ComponentBase only knows about its own lifecycle methods and Blazor-triggered events. ComponentBase doesn't know about other events that may occur in your code. For example, any C# events raised by a custom data store are unknown to Blazor. In order for such events to trigger rerendering to display updated values in the UI, use StateHasChanged.

In another use case, consider the following Counter component that uses System.Timers.Timer to update the count at a regular interval and calls StateHasChanged to update the UI.

Pages/CounterWithTimerDisposal.razor:

@page "/counter-with-timer-disposal"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer = new Timer(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void IDisposable.Dispose() => timer.Dispose();
}

In the preceding example:

  • OnTimerCallback must call StateHasChanged because Blazor isn't aware of the changes to currentCount in the callback. OnTimerCallback runs outside of any Blazor-managed rendering flow or event notification.
  • The component implements IDisposable, where the Timer is disposed when the framework calls the Dispose method. For more information, see ASP.NET Core Blazor lifecycle.

Similarly, because the callback is invoked outside Blazor's synchronization context, it's necessary to wrap the logic in ComponentBase.InvokeAsync to move it onto the renderer's synchronization context. StateHasChanged can only be called from the renderer's synchronization context and throws an exception otherwise. This is equivalent to marshalling to the UI thread in other UI frameworks.

To render a component outside the subtree that's rerendered by a particular event

Your UI might involve dispatching an event to one component, changing some state, and needing to rerender a completely different component that isn't a descendant of the one receiving the event.

One way to deal with this scenario is to have a state management class, perhaps as a DI service, injected into multiple components. When one component calls a method on the state manager, the state manager can raise a C# event that's then received by an independent component.

Since these C# events are outside the Blazor rendering pipeline, call StateHasChanged on other components you wish to render in response to the state manager's events.

This is similar to the earlier case with System.Timers.Timer in the previous section section. Since the execution call stack typically remains on the renderer's synchronization context, InvokeAsync isn't normally required. InvokeAsync is only required if the logic escapes the synchronization context, such as calling ContinueWith on a Task or awaiting a Task with ConfigureAwait(false).