Why State Management Matters in Blazor
State in a Blazor app is any data that must survive beyond a single method call and be visible to more than one component—things like the logged-in user, cart contents, or a wizard's current step. Because Blazor UIs are trees of independently rendered components, passing state as parameters works for direct parent-child relationships, but sibling or deeply nested components need a shared mechanism: cascading values, injected state container services, or browser storage, each with different scope and lifetime trade-offs.
Cricket analogy: A DRS decision must be visible to the umpires, both captains, and the broadcast graphics simultaneously, not just passed privately between two fielders—much like Blazor state that must reach components beyond a direct parent-child link.
Cascading Parameters and CascadingValue
CascadingValue is a built-in Blazor component that makes a value available to every descendant in its render subtree without explicitly threading it through each intermediate component's parameters. A component anywhere below the CascadingValue in the tree opts in with [CascadingParameter], and Blazor automatically re-renders subscribers whenever the cascaded value changes, which is well suited to app-wide concerns like the current theme, authenticated user, or a wizard step, but becomes unwieldy for state with complex mutation logic.
Cricket analogy: A stadium-wide PA announcement about a rain delay reaches every stand automatically without the announcer visiting each section individually, just as CascadingValue reaches every descendant component without explicit parameter threading.
State Container Services with Events
A dedicated state container is a plain injected service, typically Scoped, that exposes properties plus a NotifyStateChanged event or C# event delegate; components subscribe in OnInitialized and call StateHasChanged in the event handler, then unsubscribe in Dispose to avoid memory leaks. This pattern decouples state mutation from any specific component, keeps the mutation logic testable in isolation from the UI, and scales better than cascading values once the state has real behavior, like validation or undo history.
Cricket analogy: A team's official scorer service maintains the single source of truth for runs and wickets, and every broadcaster's graphics team subscribes to updates from it rather than each maintaining a private tally, just like a state container service.
public class CartStateService
{
private readonly List<CartItem> _items = new();
public IReadOnlyList<CartItem> Items => _items;
public event Action? OnChange;
public void AddItem(CartItem item)
{
_items.Add(item);
OnChange?.Invoke();
}
}@implements IDisposable
@inject CartStateService CartState
<p>Items in cart: @CartState.Items.Count</p>
@code {
protected override void OnInitialized()
{
CartState.OnChange += StateHasChanged;
}
public void Dispose()
{
CartState.OnChange -= StateHasChanged;
}
}CascadingValue works well for read-mostly, app-wide values like the current culture or authenticated user, but for state with real mutation logic — like a shopping cart with add/remove/undo — a dedicated injected state container service scales better and is easier to unit test.
Forgetting to unsubscribe from a state container's OnChange event in Dispose leaks a reference to the component, keeping it (and its render tree) alive in memory even after Blazor has removed it from the UI, especially damaging in long-running Blazor Server circuits.
- State that crosses component boundaries needs a mechanism beyond simple parent-to-child parameters
- CascadingValue + [CascadingParameter] broadcasts a value to every descendant in the render subtree automatically
- Dedicated state container services (usually Scoped) expose state plus a change-notification event
- Components subscribe to state changes in OnInitialized and must unsubscribe in Dispose to prevent leaks
- State containers decouple mutation logic from the UI, making it independently testable
- Cascading values suit simple, app-wide, read-mostly data; state containers suit state with real behavior
- In Blazor Server, Scoped state containers live for the circuit; in WASM they live for the app session
Practice what you learned
1. What must a component do to receive a value published via CascadingValue?
2. Why must a component unsubscribe from a state container's OnChange event in Dispose?
3. Which pattern is generally better suited for state with complex mutation logic like undo/redo?
4. What must a component call inside a state container's OnChange handler to actually re-render the UI?
5. In Blazor Server, what is the typical lifetime scope for a state container backing per-user cart data?
Was this page helpful?
You May Also Like
Dependency Injection in Blazor
Learn how Blazor's built-in DI container registers and resolves services, and how lifetimes affect component behavior.
Local Storage and Session Storage
Persist Blazor app state in the browser using localStorage and sessionStorage via JS interop or community libraries.
HttpClient and Calling APIs
Learn how to configure and use HttpClient in Blazor Server and WebAssembly to call REST APIs safely and efficiently.
Related Reading
Related Study Notes in Microsoft Technologies
Browse all study notesWindows 10 / UWP Development Study Notes
.NET · 30 topics
Microsoft TechnologiesWindows Batch Scripting Study Notes
Batch · 30 topics
Microsoft TechnologiesMFC (Microsoft Foundation Classes) Study Notes
C++ · 30 topics
Microsoft TechnologiesSilverlight Study Notes
.NET · 30 topics
Microsoft TechnologiesXAML Study Notes
.NET · 30 topics
Microsoft TechnologiesWPF (Windows Presentation Foundation) Study Notes
.NET · 30 topics