Skip to main content

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:

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);
Testing Advantage

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

  • StoreComponent is 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.
  • InletComponent builds 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