Advanced Blazor State Management Using Fluxor, part 4 - Interactions between Features
Published: 2021-03-07 19:20:43

>This is the fourth in a short series of blog posts where I will go beyond the introductory level and dig a bit deeper into using the Fluxor library in a Blazor Wasm project.

So far in this series everything we've explored has been limited to stateful interaction within a single Feature and its Store.

But what about interaction *between* Features? That's possible too, and here's one way to do it.

Imagine a scenario where we want to trigger an action in one Feature based on the state of another Feature. For instance:

>Refresh the Weather Forecasts on every 10th Increment of the Counter.

One approach might be to put code in the `Counter.razor` page that inspects the `CounterState` and dispatches a `WeatherLoadForecastsAction` at the appropriate time. 

```csharp
@code {
    private void IncrementCount()
    {
        Dispatcher.Dispatch(new CounterIncrementAction());

        // every tenth increment
        if (CounterState.Value.CurrentCount % 10 == 0)
        {
            Dispatcher.Dispatch(new WeatherLoadForecastsAction());
        }
    }
}

```

But I don't prefer that approach because it puts application logic for the Weather Feature in the Counter Feature's UI component.

We *could* change the CounterStore so that the `CounterIncrementAction` is handled by an `Effect` rather than a `Reducer`, so that the `Effect` can dispatch a `WeatherLoadForecastsAction`. I don't prefer this approach either, because it complicates the Counter Feature unnecessarily with Weather Feature concerns.

This functionality *really* belongs in the WeatherStore. The WeatherStore should be able to *react* to a dispatch of `CounterIncrementAction` and respond appropriately.

So how would we do that?

Since we can inject dependencies into Effects classes, we can adjust the `WeatherEffects` class to have the CounterState injected:

```csharp
private readonly HttpClient Http;
private readonly IState<CounterState> CounterState;

public WeatherEffects(HttpClient http, IState<CounterState> counterState)
{
    Http = http;
    CounterState = counterState;
}
```

Then we can add an Effect method to handle the dispatched action:

```csharp
[EffectMethod(typeof(CounterIncrementAction))]
public async Task LoadForecastsOnIncrement(IDispatcher dispatcher) 
{
    // every tenth increment
    if (CounterState.Value.CurrentCount % 10 == 0) 
    {
        dispatcher.Dispatch(new WeatherLoadForecastsAction());
    }
}
```

This Effect method in turn triggers the `LoadForecasts` Effect method we created previously (via the `WeatherLoadForecastsAction`), and the forecasts are updated even though we're not interacting directly with the Weather page in any way.

If we run the app now, it's not apparent when the forecasts are updated unless we're looking at the weather forecasts page. So, let's change the NavMenu so that the Fetch Data menu item is displayed in bold text when the forecasts are loading.

In the `NavMenu.razor` file add:

```csharp
@using BlazorWithFluxor.Client.Features.Weather.Store
@inject IState<WeatherState> WeatherState
``` 

In the `@code` block, let's add a variable for the css class:

```csharp
private string WeatherItemClass => WeatherState.Value.Loading ? "font-weight-bold" : null;
```

And change the NavMenu label for the weather page to wrap it in a `<span>` so the css class can be applied:

```csharp
<span class="@WeatherItemClass">Weather</span>
```

Now, if we build and run the application, we can see that the Weather nav menu link displays bold while the forecasts are loading:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4lwhchiyi4gv0hhvh57g.gif)

And that the forecast loading is triggered by every 10th increment of the Counter:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/im6u4wrg1006278oerjb.gif)

All this using just the functionality provided by `Actions`, `Reducers`, and `Effects`.

### IActionSubscriber

Now that we're triggering the updates of the weather forecasts based on activity in other application features, our users have asked for us to provide better notification than just turning a menu link bold. They have handed us this requirement:

>When weather forecasts are updated, show a temporary popup message in the top right of the screen informing that the forecasts have been updated.

In this scenario we don't need to update the state of the application, we just need to trigger a notification. In scenarios like this, we can use a Fluxor feature called an [IActionSubscriber](https://github.com/mrpmorris/Fluxor/tree/master/Tutorials/01-BasicConcepts/01E-ActionSubscriber). This allows a razor component to react to a dispatched Action without having to go through the Store to do it.

We're going to use the `IActionSubscriber` and [Blazored.Toast](https://github.com/Blazored/Toast) to accommodate this new feature request.

First, let's install the `Blazored.Toast` NuGet package:

```csharp
Install-Package Blazored.Toast
```

In `Program.cs` we need to add `using Blazored.Toast;` and this line:

```csharp
builder.Services.AddBlazoredToast();
```

For convenience, we'll add a few using statements to the `_Imports.razor` file:

```csharp
@using Blazored.Toast
@using Blazored.Toast.Configuration
@using Blazored.Toast.Services
```

Add the css reference to the `wwwroot\index.html` file:

```html
<link href="_content/Blazored.Toast/blazored-toast.min.css" rel="stylesheet" />
```

And finally we'll configure the Toasts component by adding this to the `MainLayout.razor` file:

```html
<BlazoredToasts Position="ToastPosition.TopRight" Timeout="3"/>
```

And with that, we're ready to trigger the Toasts. Since the NavMenu component is always loaded (in this template), I'm going to put the `IActionSubscriber` and the `IToastService` in the `NavMenu.razor` component.

At the top of `NavMenu.razor` inject the services:

```csharp
@inject IToastService toastService
@inject IActionSubscriber ActionSubscriber
```

Add a function in the `@code` block to show the Toast message:

```csharp
private void ShowWeatherToast()
{
    toastService.ShowInfo("Weather Forecasts have been updated!");
}
```

And let's use the `ActionSubscriber` to subscribe to the `WeatherSetForecastsAction` and invoke the `ShowWeatherToast` method:

```csharp
protected override void OnInitialized()
{
    ActionSubscriber.SubscribeToAction<WeatherSetForecastsAction>(this, (action) => ShowWeatherToast());
    base.OnInitialized();
}
```

This line of code will tell the Fluxor Dispatcher that any time the `WeatherSetForecastsAction` is dispatched, invoke the given Action<T>. The first parameter `this` represents the instance of the NavMenu component, and the second parameter represents an Action<WeatherSetForecastsAction> ("Action<T>" [in the .NET sense](https://docs.microsoft.com/en-us/dotnet/api/system.action-1?view=net-5.0), not in the Flux Action sense) to invoke upon dispatch.

We can actually simplify this a little bit if we change the `void ShowWeatherToasts()` method to `void ShowWeatherToasts(WeatherSetForecastsAction action)`, since that effectively turns it into an `Action<WeatherSetForecastsAction>`. The simplified version would look like:

```csharp
protected override void OnInitialized()
{
    ActionSubscriber.SubscribeToAction<WeatherSetForecastsAction>(this, ShowWeatherToast);
    base.OnInitialized();
}

private void ShowWeatherToast(WeatherSetForecastsAction action)
{
    toastService.ShowInfo("Weather Forecasts have been updated!");
}
```

Since the subscription is created for the *instance* of the component (the `this` parameter), it's important to remove the subscription when the instance is disposed. So any time you `SubscribeToAction`, remember to *unsubscribe* when it's appropriate. We'll do so in the component's `Dispose` method:

```csharp
protected override void Dispose(bool disposing)
{
    ActionSubscriber.UnsubscribeFromAllActions(this);
    base.Dispose(disposing);
}
```

The NavMenu.razor's `@code` block should now look like:

```csharp
@code {
    private bool collapseNavMenu = true;

    private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
    private string WeatherItemClass => WeatherState.Value.Loading ? "font-weight-bold" : null;

    protected override void OnInitialized()
    {
        ActionSubscriber.SubscribeToAction<WeatherSetForecastsAction>(this, ShowWeatherToast);
        base.OnInitialized();
    }

    protected override void Dispose(bool disposing)
    {
        ActionSubscriber.UnsubscribeFromAllActions(this);
        base.Dispose(disposing);
    }

    private void ShowWeatherToast(WeatherSetForecastsAction action)
    {
        toastService.ShowInfo("Weather Forecasts have been updated!");
    }

    private void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}
``` 

Build, run, and we can see the `IActionSubscriber` doing its work:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dr5fjpgxxgsipdwfsq8q.gif)

In the next post in the series, we'll tackle the challenge of meshing the read-only nature of State in Flux with the two-way data binding of razor `EditForms`.

Until then, happy coding!

---

>**Edit**: Today I learned (from Peter's comment below) that you don't actually have to inject the IActionSubscriber into a component such as this, which `@inherits FluxorComponent` - the ActionSubscriber is already available and (better yet!) unsubscribing will happen automatically. No need to write that code.
>With that in mind, here is an updated NavMenu `@code` block after removing the `@inject IActionSubscriber ActionSubscriber` line at the top of the file:

```csharp
@code {
    private bool collapseNavMenu = true;

    private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
    private string WeatherItemClass => WeatherState.Value.Loading ? "font-weight-bold" : null;

    protected override void OnInitialized()
    {
        SubscribeToAction<WeatherSetForecastsAction>(ShowWeatherToast);
        base.OnInitialized();
    }

    private void ShowWeatherToast(WeatherSetForecastsAction action)
    {
        toastService.ShowInfo("Weather Forecasts have been updated!");
    }

    private void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}
```

Notice that `SubscribeToAction` is called directly, and with no need for the `this` parameter.