ASP.NET - Blazor Component Lifecycle and State Persistence
Blazor is a modern web framework in ASP.NET Core that allows developers to build interactive web applications using C# instead of JavaScript. At the core of every Blazor application are components, which are reusable building blocks that define the user interface and its behavior. Understanding the component lifecycle and state persistence is essential for creating responsive, maintainable, and efficient applications. The lifecycle determines how components are created, updated, rendered, and destroyed, while state persistence ensures that important data is retained during user interactions or page navigation.
What is a Blazor Component?
A Blazor component is a self-contained unit that contains HTML markup, C# code, and event-handling logic. Components are stored in files with the .razor extension. Each component can receive input from other components, display dynamic content, respond to user actions, and communicate with services.
For example, a product card, navigation menu, login form, shopping cart, or weather widget can each be implemented as separate Blazor components. This modular approach improves code organization and encourages reusability.
Example:
<h3>Welcome</h3>
<p>Hello, @UserName!</p>
@code {
private string UserName = "Student";
}
In this example, the component displays a personalized greeting using a C# variable.
Understanding the Blazor Component Lifecycle
Every Blazor component follows a sequence of events from its creation until it is removed from memory. These events are known as lifecycle methods.
The lifecycle helps developers execute code at the appropriate time during the component's existence.
The main lifecycle stages are:
-
Component initialization
-
Receiving parameters
-
Rendering
-
After rendering
-
Component disposal
Each stage provides specific methods that developers can override.
Component Initialization
Initialization occurs when the component is first created. This is the ideal place to load initial data or configure resources.
Blazor provides two methods:
OnInitialized()
Runs synchronously during initialization.
Example:
protected override void OnInitialized()
{
Message = "Component Initialized";
}
Use this method when loading data that does not require asynchronous operations.
OnInitializedAsync()
Runs asynchronously.
Example:
protected override async Task OnInitializedAsync()
{
Products = await ProductService.GetProductsAsync();
}
This method is commonly used when retrieving data from databases or APIs.
Typical tasks include:
-
Loading user profiles
-
Fetching product lists
-
Reading configuration files
-
Initializing services
Receiving Parameters
Components often receive values from parent components.
Example Parent Component:
<StudentCard Name="John" Age="22" />
Child Component:
@code {
[Parameter]
public string Name { get; set; }
[Parameter]
public int Age { get; set; }
}
Whenever parameter values change, Blazor calls lifecycle methods responsible for updating the component.
OnParametersSet()
Called every time parameters are assigned.
protected override void OnParametersSet()
{
FullName = Name.ToUpper();
}
This method is useful when the component needs to recalculate values based on incoming parameters.
OnParametersSetAsync()
Asynchronous version of parameter processing.
protected override async Task OnParametersSetAsync()
{
Student = await StudentService.GetStudentAsync(Id);
}
This is useful when parameter changes require asynchronous operations.
Component Rendering
Rendering is the process of generating HTML for display in the browser.
Whenever data changes, Blazor automatically updates only the affected parts of the user interface.
Rendering occurs after:
-
Parameter changes
-
Event execution
-
State changes
-
Explicit refresh requests
Example:
<button @onclick="IncreaseCount">Increase</button>
<p>@Count</p>
@code {
int Count = 0;
void IncreaseCount()
{
Count++;
}
}
Each button click updates the value and causes the component to re-render automatically.
Controlling Rendering
Sometimes automatic rendering is unnecessary.
Blazor provides:
ShouldRender()
Example:
protected override bool ShouldRender()
{
return IsDataChanged;
}
Returning false prevents unnecessary rendering, improving performance.
Use cases include:
-
Large dashboards
-
Data grids
-
Frequently updated pages
-
Performance optimization
After Rendering
After HTML is rendered, developers may need to perform additional tasks.
Blazor provides:
OnAfterRender()
protected override void OnAfterRender(bool firstRender)
{
if(firstRender)
{
Console.WriteLine("First Render Completed");
}
}
OnAfterRenderAsync()
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if(firstRender)
{
await JS.InvokeVoidAsync("initializeChart");
}
}
Common uses include:
-
Calling JavaScript functions
-
Initializing charts
-
Loading maps
-
Configuring animations
-
Setting keyboard focus
The firstRender parameter ensures that initialization logic runs only once.
Component Disposal
When a component is removed, resources should be released to prevent memory leaks.
Blazor supports:
IDisposable
Example:
public void Dispose()
{
Timer.Dispose();
}
Or
IAsyncDisposable
Example:
public async ValueTask DisposeAsync()
{
await Connection.DisposeAsync();
}
Resources that should be disposed include:
-
Timers
-
Database connections
-
SignalR connections
-
Streams
-
File handles
Proper disposal helps maintain application performance and resource efficiency.
What is State Persistence?
State persistence is the process of preserving application data so that it remains available during navigation, page refreshes, or user sessions.
Without state persistence:
-
User selections disappear.
-
Form entries are lost.
-
Shopping carts reset.
-
User preferences are forgotten.
Blazor provides several techniques to persist state depending on the application's needs.
Component State
Component state refers to data stored within a single component.
Example:
private int Counter = 0;
This state exists only while the component remains active.
If the component is destroyed, the state is lost unless it is saved elsewhere.
Cascading Values
Sometimes multiple components require the same data.
Instead of passing parameters through every component, Blazor offers cascading values.
Example:
<CascadingValue Value="CurrentUser">
<MainLayout />
</CascadingValue>
Child component:
[CascadingParameter]
public User CurrentUser { get; set; }
This approach simplifies sharing data such as:
-
Logged-in user information
-
Application themes
-
Language settings
-
Global configuration
State Container Service
A state container is a custom service used to share data across multiple components.
Example:
public class AppState
{
public string UserName { get; set; }
}
Register the service:
builder.Services.AddScoped<AppState>();
Inject it into components:
@inject AppState State
Advantages include:
-
Centralized data management
-
Easy sharing between unrelated components
-
Improved maintainability
Browser Storage
Blazor applications can save data in the browser.
Two common storage options are:
Local Storage
-
Data remains after the browser is closed.
-
Suitable for user preferences, themes, and cached data.
Example:
await localStorage.SetItemAsync("Theme", "Dark");
Session Storage
-
Data exists only until the browser tab is closed.
-
Suitable for temporary information like active forms or shopping sessions.
Example:
await sessionStorage.SetItemAsync("User", User);
State Persistence in Blazor Server
Blazor Server keeps state on the server while maintaining a real-time connection with the browser using SignalR.
Advantages include:
-
Small client downloads
-
Centralized processing
-
Better security
Limitations include:
-
Requires a stable network connection
-
Server memory usage increases with more connected users
State Persistence in Blazor WebAssembly
Blazor WebAssembly runs entirely in the browser.
Since no server connection is required for execution, state is typically stored using:
-
Local Storage
-
Session Storage
-
IndexedDB
-
Browser memory
This enables offline capabilities and improves responsiveness.
Best Practices for Managing Component Lifecycle and State
To build efficient Blazor applications:
-
Use
OnInitializedAsync()for loading data from APIs or databases. -
Use
OnParametersSet()when responding to parameter changes. -
Avoid unnecessary re-rendering by overriding
ShouldRender()only when beneficial. -
Release resources using
IDisposableorIAsyncDisposable. -
Store shared data in scoped services instead of duplicating state across components.
-
Use cascading values for application-wide information such as themes or user settings.
-
Persist long-term data in Local Storage and temporary session data in Session Storage.
-
Keep components focused on a single responsibility to improve reusability and maintainability.
-
Minimize expensive operations during rendering to ensure a smooth user experience.
-
Choose the appropriate state persistence strategy based on whether the application uses Blazor Server or Blazor WebAssembly.
Conclusion
Blazor's component lifecycle provides a structured approach to managing how components are created, updated, rendered, and destroyed. By understanding lifecycle methods such as OnInitialized, OnParametersSet, OnAfterRender, and Dispose, developers can control application behavior more effectively and optimize performance. State persistence complements the lifecycle by ensuring that important data remains available across interactions, navigation, and sessions. Together, these concepts form the foundation for building scalable, interactive, and user-friendly Blazor applications that deliver a consistent experience across different hosting models.