Cykl życia komponentu Blazor: kompletny przewodnik

· 5 min czytania

I’ve been using Blazor for a while now and honestly, the lifecycle methods confused me at first. OnInitialized vs OnInitializedAsync? OnParametersSet vs OnAfterRender? When does StateHasChanged actually trigger a re-render? After a lot of trial and error, I finally have a solid mental model for all of it.

The lifecycle at a glance

When a Blazor component renders, it goes through these methods in order:

  1. SetParametersAsync
  2. OnInitialized / OnInitializedAsync
  3. OnParametersSet / OnParametersSetAsync
  4. OnAfterRender / OnAfterRenderAsync
  5. Dispose / DisposeAsync

Let’s walk through each one.

SetParametersAsync

This is the very first method called. It receives the raw ParameterView from the parent component. Most of the time you don’t override this — Blazor handles mapping parameters automatically. But if you need custom parameter handling or validation before they’re assigned:

public override async Task SetParametersAsync(ParameterView parameters)
{
    // Custom logic before parameters are set
    if (parameters.TryGetValue<string>("Title", out var title))
    {
        Console.WriteLine($"Title is being set to: {title}");
    }

    await base.SetParametersAsync(parameters);
}

I’ve used this exactly once in a real project. Most of the time you’ll skip it.

OnInitialized / OnInitializedAsync

This is where you do your setup work — load data, initialize services, set default values. It runs once when the component is first created.

@code {
    private List<Product> products = new();

    protected override async Task OnInitializedAsync()
    {
        products = await Http.GetFromJsonAsync<List<Product>>("api/products");
    }
}

One thing that tripped me up: in Blazor Server, OnInitializedAsync gets called twice during prerendering. The first time during server-side prerender, and the second time when the SignalR connection is established. If your API call is expensive, you might want to handle that:

private bool isPrerendering = true;

protected override async Task OnInitializedAsync()
{
    products = await Http.GetFromJsonAsync<List<Product>>("api/products");
    isPrerendering = false;
}

Or even better, use PersistentComponentState to avoid the double call entirely.

OnParametersSet / OnParametersSetAsync

This fires every time the parent component re-renders and passes new parameter values. It also fires after OnInitialized. This is the right place to react to parameter changes:

[Parameter]
public int CategoryId { get; set; }

private int previousCategoryId;
private List<Product> products = new();

protected override async Task OnParametersSetAsync()
{
    if (CategoryId != previousCategoryId)
    {
        previousCategoryId = CategoryId;
        products = await Http.GetFromJsonAsync<List<Product>>(
            $"api/products?category={CategoryId}");
    }
}

The check for CategoryId != previousCategoryId is important — without it, you’d reload data every time the parent re-renders, even if the category didn’t change.

OnAfterRender / OnAfterRenderAsync

This fires after the component has rendered to the DOM. The firstRender parameter tells you if it’s the initial render. This is the place for JS Interop calls since the DOM elements exist at this point:

[Inject]
private IJSRuntime JS { get; set; }

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        await JS.InvokeVoidAsync("initializeChart", chartElement);
    }
}

I use firstRender a lot to avoid re-initializing JavaScript libraries on every re-render. If you’re setting up event listeners, chart libraries, or anything that touches the DOM directly, this is where it goes.

ShouldRender

This one is less known but super useful. It controls whether a component re-renders when StateHasChanged is called:

private bool shouldRender = true;

protected override bool ShouldRender() => shouldRender;

private void HeavyOperation()
{
    shouldRender = false;

    // Do a bunch of state changes without triggering renders
    for (int i = 0; i < 1000; i++)
    {
        items[i].Process();
    }

    shouldRender = true;
    StateHasChanged(); // Now render once with all changes
}

I’ve used this to optimize components that process large lists. Instead of re-rendering on every item change, you batch the updates and render once at the end.

Dispose / DisposeAsync

When a component is removed from the UI, you should clean up any resources. Implement IDisposable or IAsyncDisposable:

@implements IAsyncDisposable

@code {
    private Timer? timer;
    private IJSObjectReference? jsModule;

    protected override void OnInitialized()
    {
        timer = new Timer(OnTick, null, 0, 1000);
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            jsModule = await JS.InvokeAsync<IJSObjectReference>("import", "./timer.js");
        }
    }

    public async ValueTask DisposeAsync()
    {
        timer?.Dispose();

        if (jsModule is not null)
        {
            await jsModule.DisposeAsync();
        }
    }
}

Common things to dispose: timers, event subscriptions, JS module references, CancellationTokenSource, and any IDisposable services you created.

StateHasChanged

This isn’t a lifecycle method, but it’s closely related. It tells Blazor “hey, my state changed, please re-render me.” Blazor calls it automatically after event handlers, but sometimes you need to call it manually — typically when state changes from outside the normal Blazor event flow:

private async Task StartPolling()
{
    while (!cts.Token.IsCancellationRequested)
    {
        data = await Http.GetFromJsonAsync<Data>("api/data");
        StateHasChanged(); // Manual call needed since this isn't a Blazor event
        await Task.Delay(5000, cts.Token);
    }
}

One important note: if you’re updating from a background thread in Blazor Server, use InvokeAsync:

await InvokeAsync(() =>
{
    data = newData;
    StateHasChanged();
});

The full picture

Here’s the order everything happens in:

Component created
  └─ SetParametersAsync
       └─ OnInitialized / OnInitializedAsync
            └─ OnParametersSet / OnParametersSetAsync
                 └─ Render
                      └─ OnAfterRender(firstRender: true)

Parameter change from parent
  └─ SetParametersAsync
       └─ OnParametersSet / OnParametersSetAsync
            └─ ShouldRender?
                 └─ Render
                      └─ OnAfterRender(firstRender: false)

Component removed
  └─ Dispose / DisposeAsync

Once you have this flow in your head, debugging lifecycle issues becomes a lot easier.

Hope you liked the post! Feel free to contact me on any social media at @emimontesdeoca.

Resources