StoreComponent
Overview
StoreComponent is an abstract Blazor base class that integrates components with the Reservoir state management system. It provides protected helpers for dispatching actions and reading state, and automatically subscribes to store changes to trigger re-renders.
Core Responsibilities
StoreComponent bridges Blazor's component model with the Reservoir store:
- Inherits from
ComponentBase(StoreComponent.cs#L20-L23) - Implements
IDisposableand disposes the store subscription inDispose(bool)(StoreComponent.cs#L52-L72) - Injects
IStore(StoreComponent.cs#L30-L31) - Auto-subscribes in
OnInitializedand callsStateHasChangedviaInvokeAsync(StoreComponent.cs#L81-L90, StoreComponent.cs#L146-L150)
API Reference
Store Property
The injected store instance, available to derived components:
[Inject]
protected IStore Store { get; set; }
Source: StoreComponent.cs#L30-L31
Dispatch
Dispatches an action to the store:
protected void Dispatch(IAction action)
This method delegates directly to Store.Dispatch(action). Use it to trigger state changes from user interactions or component logic.
Source: StoreComponent.cs#L40-L46
GetState
Retrieves the current value of a feature state:
protected TState GetState<TState>() where TState : class, IFeatureState
This method delegates to Store.GetState<TState>(). The generic constraint ensures only registered feature states can be retrieved.
Source: StoreComponent.cs#L76-L79
Select
Derives computed values from state using selector functions:
// Single-state selector
protected TResult Select<TState, TResult>(Func<TState, TResult> selector)
where TState : class, IFeatureState;
// Two-state selector
protected TResult Select<TState1, TState2, TResult>(
Func<TState1, TState2, TResult> selector)
where TState1 : class, IFeatureState
where TState2 : class, IFeatureState;
// Three-state selector
protected TResult Select<TState1, TState2, TState3, TResult>(
Func<TState1, TState2, TState3, TResult> selector)
where TState1 : class, IFeatureState
where TState2 : class, IFeatureState
where TState3 : class, IFeatureState;
These methods delegate to Store.Select() extension methods. Use selectors to encapsulate derived-state logic:
// Instead of inline logic:
private bool IsDisconnected => GetState<SignalRConnectionState>().Status != SignalRConnectionStatus.Connected;
// Use a selector:
private bool IsDisconnected => Select<SignalRConnectionState, bool>(SignalRConnectionSelectors.IsDisconnected);
Extracting logic into selectors makes business rules trivially testable as pure functions-no component rendering, mocking, or DI setup required. For enterprise applications requiring high test coverage, selectors are the primary mechanism for testing client-side logic.
See Why Use Selectors? for detailed guidance on achieving high coverage without complex UI tests.
See Selectors for detailed patterns and memoization.
Lifecycle
OnInitialized
When the component initializes, StoreComponent subscribes to the store:
protected override void OnInitialized()
{
base.OnInitialized();
storeSubscription?.Dispose(); // Dispose any existing subscription
storeSubscription = Store.Subscribe(OnStoreChanged);
}
Source: StoreComponent.cs#L81-L90
State Change Notification
When the store notifies subscribers, the component re-renders:
private void OnStoreChanged()
{
_ = InvokeAsync(StateHasChanged);
}
Source: StoreComponent.cs#L146-L150
Dispose
When the component is disposed, the store subscription is cleaned up:
protected virtual void Dispose(bool disposing)
{
if (disposed) return;
disposed = true;
if (disposing)
{
storeSubscription?.Dispose();
storeSubscription = null;
}
}
The disposed flag short-circuits repeated disposal attempts.
Source: StoreComponent.cs#L52-L72
Repository Examples
Spring Sample Page Component
The Spring sample's Index page demonstrates a typical usage pattern:
@page "/"
@inherits InletComponent
The code-behind uses GetState<TState>() to read state and Dispatch() to trigger actions:
// Reading state (Index.razor.cs)
private string? SelectedEntityId => Select<EntitySelectionState, string?>(EntitySelectionSelectors.GetEntityId);
// Dispatching actions (Index.razor.cs)
private void Deposit() => Dispatch(new DepositFundsAction(SelectedEntityId!, depositAmount));
Source: Index.razor.cs#L124 and Index.razor.cs#L159
Spring Sample Button Markup
In the Spring sample, buttons are simple HTML elements that call methods on the page:
<!-- Index.razor - buttons call methods, they don't inherit StoreComponent -->
<button type="button" @onclick="Deposit" disabled="@(IsExecutingOrLoading || !IsAccountOpen)">
Deposit £
</button>
The Deposit method on the page (which inherits InletComponent) dispatches the action:
private void Deposit() => Dispatch(new DepositFundsAction(SelectedEntityId!, depositAmount));
Source: Index.razor#L92
Extending StoreComponent
For specialized scenarios, you can create intermediate base classes that extend StoreComponent. Mississippi provides InletComponent as an example:
public abstract class InletComponent : StoreComponent
{
[Inject]
protected IInletStore InletStore { get; set; } = default!;
protected T? GetProjection<T>(string entityId) where T : class =>
InletStore.GetProjection<T>(entityId);
// Additional projection helpers...
}
InletComponent adds server-synced projection capabilities while inheriting the core store integration from StoreComponent.
Source: InletComponent.cs#L16-L122
Testing StoreComponent Derivatives
When testing components that derive from StoreComponent, you need to provide a mock or real store instance. The test suite demonstrates the pattern:
// Create a test component that exposes protected members
private sealed class TestStoreComponent : StoreComponent
{
public void SetStore(IStore store) => Store = store;
public void TestDispatch(IAction action) => Dispatch(action);
public TState TestGetState<TState>() where TState : class, IFeatureState => GetState<TState>();
public void TestOnInitialized() => OnInitialized();
}
Source: StoreComponentTests.cs#L75-L118
Summary
StoreComponentis the Blazor bridge into Reservoir state, dispatch, and selectors.- It owns store subscription and re-render wiring so derived components can stay focused on feature logic.
InletComponentbuilds on the same base pattern when a component also needs projection helpers.
Next Steps
- Reservoir Overview - Learn where StoreComponent fits in the system
- Selectors - Derive computed values via the
Select()method - Store - The central state container that StoreComponent wraps
- Actions - The action types dispatched via
Dispatch() - Feature State - The state slices retrieved via
GetState<TState>() - Reducers - How dispatched actions transform state