Understanding the Blazor Component Lifecycle
Every Blazor component goes through a predictable lifecycle: it's initialised, it receives parameters, it renders, and eventually it's disposed. Knowing when each lifecycle method fires — and what's safe to do in each — is fundamental to writing correct Blazor code.
The Lifecycle at a Glance
The lifecycle methods fire in this order:
SetParametersAsyncOnInitialized/OnInitializedAsyncOnParametersSet/OnParametersSetAsyncOnAfterRender/OnAfterRenderAsyncDispose/DisposeAsync
Let's walk through each one.
SetParametersAsync
This is the first method called. It receives the raw ParameterView containing all parameters passed to the component. You rarely override this directly — the base implementation assigns parameter values to your [Parameter] properties. But if you need to intercept parameters before they're assigned, this is where you do it.
public override async Task SetParametersAsync(ParameterView parameters)
{
// Inspect parameters before they're assigned
if (parameters.TryGetValue<string>("Title", out var title))
{
Console.WriteLine($"Title is being set to: {title}");
}
await base.SetParametersAsync(parameters);
}
OnInitialized / OnInitializedAsync
These fire once when the component is first created. This is where you load initial data. In Server mode, OnInitializedAsync fires twice during prerendering — once on the server during the static SSR pass, and again when the circuit is established. Keep this in mind if you're calling APIs or writing to databases.
@code {
private List<Product>? products;
protected override async Task OnInitializedAsync()
{
products = await ProductService.GetAllAsync();
}
}
To avoid the double-execution problem, you can either disable prerendering or use PersistentComponentState to carry state across the prerender boundary.
@inject PersistentComponentState ApplicationState
@code {
private List<Product>? products;
private PersistingComponentStateSubscription _subscription;
protected override async Task OnInitializedAsync()
{
_subscription = ApplicationState.RegisterOnPersisting(PersistData);
if (!ApplicationState.TryTakeFromJson<List<Product>>("products", out products))
{
products = await ProductService.GetAllAsync();
}
}
private Task PersistData()
{
ApplicationState.PersistAsJson("products", products);
return Task.CompletedTask;
}
public void Dispose() => _subscription.Dispose();
}
OnParametersSet / OnParametersSetAsync
These fire after OnInitialized on first render, and again every time the parent re-renders and passes new parameter values. Use this when you need to react to parameter changes — for example, reloading data when a route parameter changes.
@page "/product/{ProductId:int}"
@code {
[Parameter]
public int ProductId { get; set; }
private Product? product;
private int lastLoadedId;
protected override async Task OnParametersSetAsync()
{
if (ProductId != lastLoadedId)
{
lastLoadedId = ProductId;
product = await ProductService.GetByIdAsync(ProductId);
}
}
}
The guard pattern (if ProductId != lastLoadedId) prevents unnecessary reloads when the parent re-renders without changing the parameter value.
OnAfterRender / OnAfterRenderAsync
These fire after the component has rendered to the DOM. This is the only lifecycle method where it's safe to interact with JavaScript or DOM elements. The firstRender parameter tells you whether this is the initial render.
@inject IJSRuntime JS
@code {
private ElementReference chartContainer;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JS.InvokeVoidAsync("initChart", chartContainer);
}
}
}
Important: OnAfterRender does not trigger a re-render. If you update state here, you must call StateHasChanged() manually — but be careful not to create an infinite render loop.
Dispose / DisposeAsync
Implement IDisposable or IAsyncDisposable to clean up resources. This is essential for unsubscribing from events, cancelling timers, or releasing unmanaged resources.
@implements IAsyncDisposable
@code {
private Timer? timer;
protected override void OnInitialized()
{
timer = new Timer(_ => InvokeAsync(StateHasChanged), null, 0, 1000);
}
public async ValueTask DisposeAsync()
{
if (timer is not null)
{
await timer.DisposeAsync();
}
}
}
Common Mistakes
Calling StateHasChanged inside OnInitializedAsync. You don't need to — Blazor automatically re-renders after each await in lifecycle methods.
Heavy work in OnParametersSet without guards. This method fires on every parent re-render. Without a check, you might reload data unnecessarily.
Forgetting that OnAfterRender doesn't fire during prerendering. If you're using Server mode with prerendering, JavaScript interop won't work until the circuit is established.
The lifecycle is straightforward once you've internalised the order. The key insight is that each method has a specific purpose, and using the right one avoids bugs that are otherwise difficult to diagnose.