Blazor State Management
Effective state management is crucial for building robust and maintainable Blazor applications. This tutorial explores various techniques to manage state within your Blazor components and across your application.
Introduction
State refers to any data that influences the behavior or appearance of your application. In Blazor, this can include user input, fetched data, UI visibility, and more. Managing this state efficiently prevents data inconsistencies and simplifies development.
Basic State Management
The most fundamental way to manage state is within individual components using properties. When a property's value changes, Blazor's rendering engine automatically updates the UI.
@page "/counter"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
Component Parameters
To share state between parent and child components, you can use parameters. Parent components pass data down to child components via properties marked with the [Parameter]
attribute.
// ParentComponent.razor
<ChildComponent Message="@parentMessage" />
@code {
private string parentMessage = "Hello from parent!";
}
// ChildComponent.razor
<p>@Message</p>
@code {
[Parameter]
public string Message { get; set; }
}
Event Callbacks
To communicate state changes from a child component back to a parent, EventCallback
is used. This allows child components to notify parents when an event occurs or data needs to be updated.
// ParentComponent.razor
<ChildComponent @onChildEvent="HandleChildEvent" />
@code {
private void HandleChildEvent(string dataFromChild)
{
// Update parent state with dataFromChild
}
}
// ChildComponent.razor
<button @onclick="NotifyParent">Notify Parent</button>
@code {
[Parameter]
public EventCallback<string> OnChildEvent { get; set; }
private void NotifyParent()
{
OnChildEvent.InvokeAsync("Data from child.");
}
}
Note: EventCallback
is type-safe and handles asynchronous operations gracefully.
Using Services
For managing state that needs to be shared across multiple components, especially when these components don't have a direct parent-child relationship, Blazor services are ideal. These are regular C# classes registered with the dependency injection container.
You can inject these services into your components using the [Inject]
attribute.
Example: Counter Service
// CounterService.cs
public class CounterService
{
public int Count { get; private set; } = 0;
public void Increment()
{
Count++;
}
}
// Startup.cs (or Program.cs for Blazor WASM)
builder.Services.AddSingleton<CounterService>();
// MyComponent.razor
@inject CounterService CounterService
<p>Shared Count: @CounterService.Count</p>
<button @onclick="CounterService.Increment">Increment Shared Count</button>
Scoped Services
Scoped services live for the duration of a specific client-side Blazor circuit. This means that within a single user session, all components injecting the same scoped service will share the same instance.
In Startup.cs
(or Program.cs`):
builder.Services.AddScoped<MyScopedService>();
Application-Wide State
For truly application-wide state, often combined with data fetching or authentication status, you might use a singleton service. The lifetime of a singleton service is the entire application's lifetime.
Important: Be mindful of the lifetime of your services. Singleton services should not hold client-specific state that needs to be reset per user session, as this can lead to data leaks between users.
Leveraging Local Storage
Blazor applications running in the browser (Blazor WebAssembly) can interact with the browser's local storage to persist state across sessions. You can achieve this using JavaScript interop.
// Example using JavaScript interop
@inject IJSRuntime JSRuntime
<button @onclick="SaveToLocalStorage">Save Data</button>
<button @onclick="LoadFromLocalStorage">Load Data</button>
@code {
private string dataToSave = "My important data";
private async Task SaveToLocalStorage()
{
await JSRuntime.InvokeVoidAsync("localStorage.setItem", "appState", dataToSave);
}
private async Task LoadFromLocalStorage()
{
var retrievedData = await JSRuntime.InvokeAsync<string>("localStorage.getItem", "appState");
if (!string.IsNullOrEmpty(retrievedData))
{
dataToSave = retrievedData;
StateHasChanged(); // Important to re-render the component
}
}
}
Conclusion
Blazor offers a flexible range of state management strategies, from simple component properties to sophisticated service-based approaches. Choosing the right method depends on the scope and complexity of the state you need to manage.
- Component Properties: For state local to a single component.
- Parameters & EventCallbacks: For parent-child communication.
- Services (Scoped/Singleton): For sharing state across components, potentially application-wide.
- JavaScript Interop: For interacting with browser storage like
localStorage
.
By understanding and applying these patterns, you can build more predictable and maintainable Blazor applications.