"Understanding the Blazor component lifecycle is the difference between writing reliable components and chasing ghost bugs."
Quick Answer
The Blazor component lifecycle is a set of methods (.NET calls automatically) that execute at specific stages of a component's existence — from initialization to disposal. Knowing when each method runs prevents double-loading data, memory leaks, and async race conditions.
What is the Blazor Component Lifecycle?
Every Blazor component goes through a defined sequence of lifecycle events managed by the Blazor runtime. These are virtual methods you can override in your component class to hook into specific moments — before rendering, after rendering, when parameters change, or when the component is removed from the DOM.
Unlike JavaScript frameworks where lifecycle hooks vary by framework, Blazor's lifecycle is part of the .NET runtime and follows C# patterns with full async/await support.
The 6 Core Lifecycle Methods
- SetParametersAsync — Called before anything else. Receives parameters from the parent. Override to validate parameters before they're set.
- OnInitialized / OnInitializedAsync — Called once when the component is first created. Perfect for initial data loading (use the async version for API calls).
- OnParametersSet / OnParametersSetAsync — Called every time parameters are set or changed. Use this when your component needs to react to parent prop changes.
- OnAfterRender / OnAfterRenderAsync — Called after the component renders to the DOM. The
firstRenderparameter tells you if it's the first render. Use this for JavaScript interop. - ShouldRender — Override to return false and skip unnecessary re-renders. Critical for performance optimization.
- Dispose / DisposeAsync — Implement IDisposable or IAsyncDisposable to clean up event handlers, timers, and subscriptions.
Common Mistakes and How to Fix Them
- Using OnInitialized for HTTP calls — OnInitialized is synchronous. If you call an async API without awaiting properly, your component renders before data arrives. Fix: always use OnInitializedAsync.
- Missing Dispose for event subscriptions — If you subscribe to an event (like a state container change) in OnInitialized but don't unsubscribe in Dispose, you get memory leaks and multiple handlers. Fix: implement IDisposable and unsubscribe.
- JavaScript interop in OnInitialized — JS interop requires the DOM to exist. OnInitialized runs before the first render. Fix: use OnAfterRenderAsync with the firstRender check.
- Calling StateHasChanged unnecessarily — Blazor auto-detects changes after event handlers. Calling StateHasChanged manually on every render is wasteful. Fix: only call it when updating state from an async background operation.
Lifecycle Execution Order — The Complete Flow
Understanding the order matters especially for async methods:
Component created
↓
SetParametersAsync(ParameterView)
↓
OnInitialized() / await OnInitializedAsync()
↓
OnParametersSet() / await OnParametersSetAsync()
↓
[Renders to DOM]
↓
OnAfterRender(firstRender: true) / await OnAfterRenderAsync(true)
↓
[On parameter change from parent:]
OnParametersSet() / await OnParametersSetAsync()
↓
[Re-renders]
↓
OnAfterRender(firstRender: false) / await OnAfterRenderAsync(false)
↓
[On Dispose:]
Dispose() / DisposeAsync()
Practical Example — Loading Data Correctly
@page "/products/{CategoryId:int}"
@implements IAsyncDisposable
@inject HttpClient Http
@if (products == null) {
<p>Loading...</p>
} else {
<ul>@foreach (var p in products) { <li>@p.Name</li> }</ul>
}
@code {
[Parameter] public int CategoryId { get; set; }
private List<Product>? products;
private CancellationTokenSource? cts;
// ✅ Use async for initial load
protected override async Task OnInitializedAsync()
{
cts = new CancellationTokenSource();
await LoadProducts();
}
// ✅ React to parameter changes (e.g., user navigates to different category)
protected override async Task OnParametersSetAsync()
{
if (products != null) // skip on initial render (handled by OnInitializedAsync)
{
await LoadProducts();
}
}
private async Task LoadProducts()
{
products = null; // show loading state
StateHasChanged();
products = await Http.GetFromJsonAsync<List<Product>>(
$"api/products?category={CategoryId}",
cts!.Token);
}
// ✅ Clean up to prevent memory leaks
public async ValueTask DisposeAsync()
{
cts?.Cancel();
cts?.Dispose();
}
}
ShouldRender — Performance Optimization
By default Blazor re-renders a component after every event. For components with expensive renders (large data grids, complex charts), override ShouldRender to return false when nothing meaningful has changed:
private bool shouldRender = true;
protected override bool ShouldRender()
{
if (!shouldRender) {
shouldRender = true; // reset for next event
return false;
}
return true;
}
Key Takeaways
- Use OnInitializedAsync for initial API calls — never OnInitialized for async work
- Use OnParametersSetAsync to react when parent parameters change
- Use OnAfterRenderAsync with firstRender check for JavaScript interop
- Always implement IDisposable or IAsyncDisposable if you subscribe to events
- Use ShouldRender to prevent unnecessary re-renders in performance-critical components
- The async variants always run after their sync counterpart — Blazor awaits them before proceeding