Day 7: State Management
Learn to manage global state, persist data, and share state across components.
1. Types of State
Component State: Local to a single component (e.g.,
@codeblock variables).App State: Shared across multiple components (e.g., user preferences, shopping cart).
2. State Container Pattern
Create a centralized class to hold and manage app state. Components subscribe to state changes.
Step 1: Create a State Container
// State/CounterState.cs
public class CounterState
{
public int Count { get; private set; }
public event Action? OnChange;
public void Increment()
{
Count++;
NotifyStateChanged();
}
private void NotifyStateChanged() => OnChange?.Invoke();
}
Step 2: Register the State Container
// Program.cs
builder.Services.AddSingleton<CounterState>();
3. Use State in Components
Component A (Increment)
@inject CounterState CounterState
<button @onclick="Increment">Increment</button>
@code {
private void Increment() => CounterState.Increment();
}
Component B (Display)
@inject CounterState CounterState
@implements IDisposable
<p>Global Count: @CounterState.Count</p>
@code {
protected override void OnInitialized()
{
// Subscribe to state changes
CounterState.OnChange += StateHasChanged;
}
public void Dispose() => CounterState.OnChange -= StateHasChanged;
}
Key Points:
StateHasChanged: Manually trigger UI refresh when state changes.IDisposable: Unsubscribe from events to avoid memory leaks.
4. Persisting State to localStorage
Use JavaScript interop to save state in the browser’s localStorage.
Step 1: Create a Storage Service
// Services/ILocalStorageService.cs
public interface ILocalStorageService
{
Task<T?> GetItem<T>(string key);
Task SetItem<T>(string key, T value);
}
// Services/LocalStorageService.cs
public class LocalStorageService : ILocalStorageService
{
private readonly IJSRuntime _jsRuntime;
public LocalStorageService(IJSRuntime jsRuntime) => _jsRuntime = jsRuntime;
public async Task<T?> GetItem<T>(string key) =>
await _jsRuntime.InvokeAsync<T>("localStorage.getItem", key);
public async Task SetItem<T>(string key, T value) =>
await _jsRuntime.InvokeVoidAsync("localStorage.setItem", key, value);
}
Step 2: Register Service
builder.Services.AddScoped<ILocalStorageService, LocalStorageService>();
Step 3: Persist Counter State
Modify CounterState.cs to save/load from localStorage:
public class CounterState
{
private readonly ILocalStorageService _localStorage;
public CounterState(ILocalStorageService localStorage) => _localStorage = localStorage;
public async Task LoadStateAsync() =>
Count = await _localStorage.GetItem<int>("count") ?? 0;
public async Task SaveStateAsync() =>
await _localStorage.SetItem("count", Count);
}
5. Advanced: Fluxor (Redux Pattern)
For large apps, use Fluxor (a Redux-like library) for predictable state management.
Install Fluxor:
dotnet add package Fluxor.Blazor.WebDefine Actions, Reducers, and State:
// State/CounterFeature.cs public class CounterFeature : Feature<CounterState> { public override string GetName() => "Counter"; protected override CounterState GetInitialState() => new CounterState(0); } // State/IncrementCounterAction.cs public record IncrementCounterAction; // State/CounterReducers.cs public static class CounterReducers { [ReducerMethod] public static CounterState ReduceIncrementCounterAction(CounterState state, IncrementCounterAction _) => new CounterState(state.Count + 1); }Use in Components:
@inject IState<CounterState> CounterState @inject IDispatcher Dispatcher <p>Count: @CounterState.Value.Count</p> <button @onclick="() => Dispatcher.Dispatch(new IncrementCounterAction())">Increment</button>
6. Practice Task
Extend the CounterState:
Add a
Decrementmethod to theCounterState.Create a component to decrement the counter.
Persist the counter value to
localStorageon decrement.
7. Common Issues (For Beginners)
Memory Leaks: Forgot to unsubscribe from
OnChangeevents.Async State Loading: Use
OnInitializedAsyncto load persisted state.Overengineering: Start with simple state containers before Fluxor.
8. Key Takeaways
State Containers: Centralize app state and notify components of changes.
Persistence: Use
localStorageto retain state across page reloads.Fluxor: Advanced state management for complex apps (optional).