Mastering Blazor Component Lifecycle: A Deep Dive for Advanced Developers
Explore Blazor component lifecycle methods with production-ready C# examples.
20+ years shipping production .NET services in enterprise systems. Lessons pulled from things that broke in production.
- ✓Basic knowledge of C# and ASP.NET Core
- ✓Familiarity with Blazor components and Razor syntax
- ✓Understanding of async/await patterns
- Blazor components have a well-defined lifecycle with methods like OnInitialized, OnParametersSet, OnAfterRender, and Dispose.
- Lifecycle methods run in a specific order: first initialization, then parameter changes, then rendering, and finally disposal.
- StateHasChanged triggers re-rendering; understanding when to call it prevents performance issues.
- Async versions (OnInitializedAsync, OnParametersSetAsync) allow await operations during lifecycle.
- Proper disposal of resources via IDisposable or IAsyncDisposable is critical to avoid memory leaks.
Think of a Blazor component like a stage actor. The actor goes through a script: first they get their costume and props (initialization), then they react to audience cues (parameter changes), perform their scene (render), and finally take a bow and leave (dispose). Each step has a specific time and order, and if you miss a cue, the performance suffers.
Blazor components are the building blocks of modern web UIs in ASP.NET Core. They offer a component-based architecture similar to React or Angular, but with C# instead of JavaScript. Understanding the component lifecycle is crucial for building responsive, efficient, and bug-free applications. Whether you're fetching data from an API, handling user interactions, or managing resources, knowing when and how to hook into lifecycle events can make or break your app's performance and reliability.
Consider a real-world scenario: you're building a dashboard that loads data from a database. If you fetch data in the wrong lifecycle method, you might cause unnecessary re-renders, duplicate network calls, or even memory leaks. This tutorial will guide you through each lifecycle method with production-ready examples, common pitfalls, and debugging strategies. By the end, you'll be able to write Blazor components that are both efficient and maintainable.
1. Overview of Blazor Component Lifecycle
Blazor components go through a predictable lifecycle from creation to disposal. The key methods are:
- SetParametersAsync: Called when the parent component sets parameters.
- OnInitialized / OnInitializedAsync: Called once when the component is initialized.
- OnParametersSet / OnParametersSetAsync: Called after parameters are set, including on first render.
- OnAfterRender / OnAfterRenderAsync: Called after each render of the component.
- ShouldRender: Determines whether the component should render.
- Dispose: Called when the component is removed from the UI.
Understanding the order and purpose of these methods is essential. For example, OnInitializedAsync is ideal for one-time async operations like fetching data, while OnParametersSetAsync is better for reacting to parameter changes. The following sections will dive into each method with code examples.
2. SetParametersAsync: The First Entry Point
SetParametersAsync is called when the parent component sets parameters on the child component. It receives a ParameterView that contains all the parameters. This method is called before OnInitialized and OnParametersSet. You can override it to perform custom parameter validation or transformation.
- Validate parameter values and throw exceptions if invalid.
- Transform parameters before they are used.
- Cancel rendering if parameters haven't changed (by not calling base.SetParametersAsync).
Here's an example where we validate a parameter and prevent unnecessary re-renders:
3. OnInitialized and OnInitializedAsync: One-Time Initialization
OnInitialized and OnInitializedAsync are called once when the component is first created. They are ideal for one-time setup tasks like: - Fetching initial data from a service. - Initializing fields or subscriptions. - Setting up timers.
The async version allows you to await asynchronous operations. During this time, the component will render once with the initial state, and then again after the async operation completes.
Important: These methods are not called again if the component is re-rendered due to parent re-renders. They only run once per component instance.
Example: Fetching data from an API:
4. OnParametersSet and OnParametersSetAsync: Reacting to Parameter Changes
OnParametersSet and OnParametersSetAsync are called every time the component receives new parameters, including the first time. This makes them suitable for: - Re-fetching data when parameters change. - Updating internal state based on parameters. - Performing calculations that depend on parameters.
Unlike OnInitialized, these methods run on every parameter change, so you must be careful to avoid infinite loops or redundant work.
Example: A component that displays a product based on a ProductId parameter:
5. OnAfterRender and OnAfterRenderAsync: Interacting with the DOM
OnAfterRender and OnAfterRenderAsync are called after each render of the component. They receive a boolean parameter firstRender that is true only on the first render. These methods are the only place where you can safely interact with the rendered DOM, such as: - Calling JavaScript interop functions that require DOM elements. - Measuring element sizes or positions. - Focusing an input element. - Initializing third-party JavaScript libraries.
Because the DOM is guaranteed to be updated, you can use ElementReference parameters here.
Example: Focusing an input on first render:
6. ShouldRender: Controlling Render Behavior
ShouldRender is called before each render and returns a boolean. If it returns false, the component will not render, and OnAfterRender will not be called. This is useful for performance optimization when you know the component's output hasn't changed.
- Suppress rendering when parameters haven't changed (though this is better handled in SetParametersAsync).
- Suppress rendering during rapid updates (e.g., while dragging).
- Implement custom change detection.
Example: A component that only renders when a specific condition is met:
7. Dispose: Cleaning Up Resources
When a component is removed from the UI, Blazor calls Dispose if the component implements IDisposable or IAsyncDisposable. This is crucial for releasing unmanaged resources like: - Event handlers or subscriptions. - Timers. - Network streams. - JavaScript interop handles.
Failing to dispose can cause memory leaks and unexpected behavior.
Example: A component that subscribes to a timer:
8. Advanced Patterns: StateHasChanged and Render Trees
StateHasChanged is the method that tells Blazor to re-render the component. It's automatically called after lifecycle events, but you can call it manually to trigger a render. However, overusing it can lead to performance issues.
- Call StateHasChanged only when the component's state has actually changed.
- Avoid calling it inside lifecycle methods that are already followed by a render (like OnInitializedAsync).
- Use ShouldRender to suppress unnecessary renders.
Another advanced concept is the render tree. Blazor builds a render tree from your Razor markup and diffs it with the previous tree to update the DOM efficiently. Understanding this can help you optimize complex UIs.
Example: Manual StateHasChanged call:
The Infinite Loop That Brought Down a Dashboard
- Avoid calling StateHasChanged inside OnParametersSetAsync unless absolutely necessary.
- Use OnInitializedAsync for one-time initialization tasks.
- Implement IDisposable to clean up timers or subscriptions that could cause re-renders.
- Always test with simulated parameter changes to catch infinite loops.
Console.WriteLine($"Render count: {renderCount}");Check if StateHasChanged is called inside OnParametersSetAsync.| File | Command / Code | Purpose |
|---|---|---|
| LifecycleOrder.cs | @page "/lifecycle-order" | 1. Overview of Blazor Component Lifecycle |
| SetParametersAsyncExample.razor | @code { | 2. SetParametersAsync |
| DataFetching.razor | @inject IWeatherService WeatherService | 3. OnInitialized and OnInitializedAsync |
| ProductDisplay.razor | @inject IProductService ProductService | 4. OnParametersSet and OnParametersSetAsync |
| FocusInput.razor | @inject IJSRuntime JS | 5. OnAfterRender and OnAfterRenderAsync |
| ConditionalRender.razor | @code { | 6. ShouldRender |
| TimerComponent.razor | @implements IDisposable | 7. Dispose |
| ManualStateHasChanged.razor | @page "/counter" | 8. Advanced Patterns |
Key takeaways
Common mistakes to avoid
3 patternsCalling StateHasChanged inside OnInitializedAsync
Not implementing IDisposable for timers or event subscriptions
Fetching data in OnInitializedAsync for components that receive parameters
Interview Questions on This Topic
Explain the order of lifecycle methods in a Blazor component.
Frequently Asked Questions
20+ years shipping production .NET services in enterprise systems. Lessons pulled from things that broke in production.
That's ASP.NET. Mark it forged?
3 min read · try the examples if you haven't