Advanced Blazor State Management Using Fluxor, part 3 - Effects
Tags: csharp, blazor
Published: 2021-03-07 02:01:20

>This is the third 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.

### What about Effects?

You may have noticed that I've mentioned a few times that Fluxor Stores are composed of 5 pieces: State, Feature, Actions, Reducers, and Effects. But so far we've only used 4 of those pieces. We haven't touched Effects yet. Why not?

An [Effect in Fluxor](https://github.com/mrpmorris/Fluxor/blob/master/Tutorials/01-BasicConcepts/01B-EffectsTutorial/README.md) is used when a dispatched Action needs to access resources outside of the `Store` in a way that a "pure" `Reducer` method cannot. A common example is making an HTTP call to an API. A Reducer method only has access to the current `State` and the `Action` it's subscribing to.

An `Effect` method *can* access outside resources, and in turn dispatch `Actions` itself that will be processed by `Reducers` to emit new `State`.

A class that contains `Effect` methods is an instance class (non-static) that can, through its constructor, have resources injected into it on instantiation. Anything that can be injected into a component using the standard dependency injection mechanism in Blazor can be injected into an `Effect` class instance.

### Weather Forecasts

The "Fetch Data" component in the Blazor WebAssembly Hosted template that we're using is a component that, since it makes an API call to retrieve Weather Forecast data, can make use of an `Effect` method, so that's what we're going to do.

Consider the following scenario:

>The Weather Forecasts should only be automatically retrieved the first time the page is loaded. On subsequent views, the already-retrieved forecasts should be displayed, not new ones.

>New Weather Forecasts should be retrieved only when clicking a "Refresh Forecasts" button on the page.

Currently, the page will load new forecasts every time the page is viewed:

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

This is because the `WeatherForecast[]` is populated whenever the component is initialized, which is every time it's rendered. 

```csharp
@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
    }
}

```

What we *really* want is some state to hold on to the Forecasts and redisplay when the page is loaded, and a way to track whether the State has been initialized. Perhaps something like this:

```csharp
public record WeatherState 
{
    public bool Initialized { get; init; }
    public bool Loading { get; init; }
    public WeatherForecast[] Forecasts { get; init; }
}
```
So lets turn this component into a Fluxor feature.

Step one, make a place for the Weather feature. Create a `\Features\Weather` folder, and *inside that* create a `Pages` folder and a `Store` folder. Move the `FetchData.razor` to the newly created `Pages` folder. In the `Store` folder create a `WeatherStore.cs` file.

In the WeatherStore goes the 5 pieces of a Fluxor Store: State, Feature, Actions, Reducers, Effects.

We have a WeatherState, as shown above.

The Feature:

```csharp
public class WeatherFeature : Feature<WeatherState>
{
    public override string GetName() => "Weather";

    protected override WeatherState GetInitialState()
    {
        return new WeatherState
        {
            Initialized = false,
            Loading = false,
            Forecasts = Array.Empty<WeatherForecast>()
        };
    }
}
```

The Actions:

```csharp
public class WeatherSetInitializedAction { }

public class WeatherSetForecastsAction
{
    public WeatherForecast[] Forecasts { get; }

    public WeatherSetForecastsAction(WeatherForecast[] forecasts)
    {
        Forecasts = forecasts;
    }
}

public class WeatherSetLoadingAction
{
    public bool Loading { get; }

    public WeatherSetLoadingAction(bool loading)
    {
        Loading = loading;
    }
}
```

The Reducers:

```csharp
public static class WeatherReducers 
{
    [ReducerMethod]
    public static WeatherState OnSetForecasts(WeatherState state, WeatherSetForecastsAction action) 
    {
        return state with 
        {
            Forecasts = action.Forecasts
        };
    }

    [ReducerMethod]
    public static WeatherState OnSetLoading(WeatherState state, WeatherSetLoadingAction action)
    {
        return state with
        {
            Loading = action.Loading
        };
    }

    [ReducerMethod(typeof(WeatherSetInitializedAction))]
    public static WeatherState OnSetInitialized(WeatherState state)
    {
        return state with
        {
            Initialized = true
        };
    }
}
```

Which will allow us to change the `FetchData.razor` file like by adding the Fluxor requirements up top:

```csharp
@inherits FluxorComponent

@using BlazorWithFluxor.Shared
@using BlazorWithFluxor.Client.Features.Weather.Store

@inject IDispatcher Dispatcher
@inject IState<WeatherState> WeatherState
@inject HttpClient Http
```

Change the `@code` block to something like:

```csharp
@code {

    private WeatherForecast[] forecasts => WeatherState.Value.Forecasts;
    private bool loading => WeatherState.Value.Loading;

    protected override async Task OnInitializedAsync()
    {
        if (WeatherState.Value.Initialized == false)
        {
            await LoadForecasts();
            Dispatcher.Dispatch(new WeatherSetInitializedAction());
        }
        await base.OnInitializedAsync();
    }

    private async Task LoadForecasts()
    {
        Dispatcher.Dispatch(new WeatherSetLoadingAction(true));
        var forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
        Dispatcher.Dispatch(new WeatherSetForecastsAction(forecasts));
        Dispatcher.Dispatch(new WeatherSetLoadingAction(false));
    }

}
```

Exchange this awkward null check in the markup

```csharp
@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
```

with something more meaningful:

```csharp
@if (_loading)
{
    <p><em>Loading...</em></p>
}
```

And add a button to trigger a reload of the forecasts:

```html
</table>
<br />
<button class="btn btn-outline-info" @onclick="LoadForecasts">Refresh Forecasts</button>
```

Build, run, and it works:

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

However, this part is still a bit awkward:

```csharp
private async Task LoadForecasts()
{
    Dispatcher.Dispatch(new WeatherSetLoadingAction(true));
    var forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
    Dispatcher.Dispatch(new WeatherSetForecastsAction(forecasts));
    Dispatcher.Dispatch(new WeatherSetLoadingAction(false));
}
```

There is a lot of state-manipulation going on here, which seems like it would be more appropriately placed in the `WeatherStore`. What we really want is something more like this:

```csharp
private void LoadForecasts()
{
    Dispatcher.Dispatch(new WeatherLoadForecastsAction());
}
```

The handler of that `Action`, in the `WeatherStore`, would take care care of all of the State manipulation and leave the page to *react* to the changing State (see what I did there 😉) rather than drive it.

This is where `Effect` methods come into play. Since a `Reducer` cannot call out to the API, we need an `Effect` method to do it.

So, back to the `WeatherStore` we go, to add a new Action:

```csharp
public class WeatherLoadForecastsAction { }
```

And a `WeatherEffects` class with one `EffectMethod`:

```csharp
public class WeatherEffects 
{
    private readonly HttpClient Http;

    public WeatherEffects(HttpClient http)
    {
        Http = http;
    }
	 
   [EffectMethod(typeof(WeatherLoadForecastsAction))]
    public async Task LoadForecasts(IDispatcher dispatcher) 
    {
        dispatcher.Dispatch(new WeatherSetLoadingAction(true));
        var forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
        dispatcher.Dispatch(new WeatherSetForecastsAction(forecasts));
        dispatcher.Dispatch(new WeatherSetLoadingAction(false));
    }
}
```

It is similar to the `WeatherReducers` class, but with one big and obvious difference: this one has a constructor into which we can inject dependencies, and which we can use in each `EffectMethod`.

Like a `[ReducerMethod]`, an `[EffectMethod]` can be declared with the Action Type in the attribute (in the case of Actions with no payload), or as a method parameter:

```csharp
[EffectMethod]
public async Task LoadForecasts(WeatherLoadForecastsAction action, IDispatcher dispatcher) 
{
    // code
}
```

In this case there is no payload so we declare the Action in the attribute.

Now we can go back to the `FetchData.razor` file, remove the `@inject HttpClient Http` since we no longer need it, and the page's `@code` block now looks like:

```csharp
@code {

    private WeatherForecast[] forecasts => WeatherState.Value.Forecasts;
    private bool loading => WeatherState.Value.Loading;

    protected override void OnInitialized()
    {
        if (WeatherState.Value.Initialized == false)
        {
            LoadForecasts();
            Dispatcher.Dispatch(new WeatherSetInitializedAction());
        }
        base.OnInitialized();
    }

    private void LoadForecasts()
    {
        Dispatcher.Dispatch(new WeatherLoadForecastsAction());
    }

}
```

Build, run, and we still should see the same behavior as above. The forecasts load once on component initialize, the component maintains its state between views, and reloads the forecasts when the 'Refresh Forecasts' button is clicked.

As for the `WeatherEffects` class... What did we gain besides moving some code around? In the next part of this series, we'll explore how having the "Load Forecasts" functionality in the `WeatherLoadForecastsAction` opens up some opportunities for more complex interactions between application components. 

After all of these steps, the solution should look like [this branch of the demo repository](https://github.com/eric-king/BlazorWithFluxor/tree/005_Featurize_Weather_Component).

Please leave a comment if you find this helpful, have a suggestion, or want to ask a question.

Happy coding!

---

>**Edit**: Peter, in his comment below, suggests an adjustment to the Store that simplifies a few things. Here is what the Actions/Reducers/Effects would look like after the changes:

```csharp
public static class WeatherReducers 
{
    [ReducerMethod]
    public static WeatherState OnSetForecasts(WeatherState state, WeatherSetForecastsAction action) 
    {
        return state with 
        {
            Forecasts = action.Forecasts,
            Loading = false
        };
    }

    [ReducerMethod(typeof(WeatherSetInitializedAction))]
    public static WeatherState OnSetInitialized(WeatherState state)
    {
        return state with
        {
            Initialized = true
        };
    }

    [ReducerMethod(typeof(WeatherLoadForecastsAction))]
    public static WeatherState OnLoadForecasts(WeatherState state)
    {
        return state with
        {
            Loading = true
        };
    }
}

public class WeatherEffects 
{
    private readonly HttpClient Http;
    private readonly IState<CounterState> CounterState;

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

    [EffectMethod(typeof(WeatherLoadForecastsAction))]
    public async Task LoadForecasts(IDispatcher dispatcher) 
    {
        var forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
        dispatcher.Dispatch(new WeatherSetForecastsAction(forecasts));
    }
}

#region WeatherActions
public class WeatherSetInitializedAction { }
public class WeatherLoadForecastsAction { }
public class WeatherSetForecastsAction
{
    public WeatherForecast[] Forecasts { get; }

    public WeatherSetForecastsAction(WeatherForecast[] forecasts)
    {
        Forecasts = forecasts;
    }
}
#endregion

```

This has the effect of simplifying the `LoadForecasts` EffectMethod, and moving the update of the `Loading` state to reducers that subscribe directly to `WeatherLoadForecastsAction` and `WeatherSetForecastsAction`, removing the need for the Effect to dispatch extra Actions.