Published on

Blazor state management with Fluxor

6 min read
Table of Contents

What is Fluxor

Fluxor is a zero boilerplate Flux/Redux library for .Net. The aim of Fluxor is to create a multi-UI, single-state store approach to front-end development without the headaches typically associated with other implementations, such as the overwhelming amount of boiler-plate code required just to add a very basic feature. Repo: https://github.com/mrpmorris/Fluxor Docs: https://github.com/mrpmorris/Fluxor/blob/master/Docs/README.md

When to use Fluxor

Fluxor uses Flux/Redux approach. It helps you to manage your app’s state in a single place and keep changes in your app more predictable and traceable. It makes it easier to manage the state. But all of these benefits come with tradeoffs and constraints. One might feel it adds up boilerplate code, making simple things a little overwhelming; but that depends upon the architecture decisions. If you are wondering if you need state management or not then you don't need it. When your app grows to the scale where managing app state becomes a hassle; and you start looking out for making it easy and simple. That is where you will find Fluxor helpful.

Creating Blazor Wasm project

dotnet new blazorwasm -n FluxorWithBlazor

This will create a blazor wasm app.

Install Fluxor Packages

Install Nuget packages

<PackageReference Include="Fluxor" Version="5.4.0" />
<PackageReference Include="Fluxor.Blazor.Web" Version="5.4.0" />
<PackageReference Include="Fluxor.Blazor.Web.ReduxDevTools" Version="5.4.0" />

Update the Program.cs

using Fluxor;

builder.Services.AddFluxor(o =>
{
    o.ScanAssemblies(typeof(Program).Assembly);
    o.UseReduxDevTools(rdt =>
      {
          rdt.Name = "My application";
      });
});

Update the App.razor

<Fluxor.Blazor.Web.StoreInitializer />

Now we are ready to create state in our Blazor application.

Fluxor Rules

  • State should always be read-only.
  • To alter state our app should dispatch an action.
  • Every reducer that processes the dispatched action type will create new state to reflect the old state combined with the changes expected for the action.
  • The UI then uses the new state to render its display.

Core Concepts (State, Actions, and Reducers)

State

Imagine your app’s state is described as a plain object. For example, the state of a counter app might look like this:

[FeatureState]
public class CounterState
{
  public int ClickCount { get; }

  public CounterState(int clickCount)
  {
    ClickCount = clickCount;
  }
}

This object is like a “model” except that there are no setters. This is so that different parts of the code can’t change the state arbitrarily, causing hard-to-reproduce bugs. To change something in the state, you need to dispatch an action.

Action

An action is a plain c# object (notice how we don’t introduce any magic?) that describes what happened. Here are a few example actions:

public class IncrementCounterAction
{
}

Enforcing that every change is described as an action lets us have a clear understanding of what’s going on in the app. If something changed, we know why it changed. Actions are like breadcrumbs of what has happened.

Reducers

Finally, to tie state and actions together, we write a function called a reducer. Again, nothing magical about it—it’s just a function that takes state and action as arguments, and returns the next state of the app. It would be hard to write such a function for a big app, so we write smaller functions managing parts of the state:

public static class Reducers
{
    [ReducerMethod]
    public static CounterState ReduceIncrementCounterAction(CounterState state, IncrementCounterAction action) =>
        new(clickCount: state.ClickCount + 1);
}

Using the State

To use the state management we will update the counter page which comes with the default app.

@page "/counter" @using FluxorWithBlazor.State.Counter @inject IDispatcher dispatcher @inject
IState<CounterState>
  counterState
  <PageTitle>Counter</PageTitle>

  <h1>Counter</h1>

  <p role="status">Current count: @counterState.Value.ClickCount</p>

  <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

  @code { private void IncrementCount() { dispatcher.Dispatch(new IncrementCounterAction()); }
  }</CounterState
>

In the IncrementCount method we are dispatching an action and the current count is displayed buy injecting the state.

Effects

Flux state is supposed to be immutable, and that state replaced only by pure functions, which should only take input from their parameters. With this in mind, we need something that will enable us to access other sources of data such as web services, and then reduce the results into our state. That is where the effects comes in. Effect handlers cannot (and should not) affect state directly. They are triggered when the action they are interested in is dispatched through the store, and as a response they can dispatch new actions.

Fetch data with effects

We will update the fetch data sample provided in the default blazor application with Effects.

Fetch data action

Lets create a action which will initiate the action. this will the be a empty action which will be used to trigger the effect.

public class FetchDataAction
{
}

Fetch data result action

Once the data is received from the server we need a action to update the state. we will use the ´FetchDataResultAction´ for that. It will have a the list of Weather forecast available.

public class FetchDataResultAction
{
    public IEnumerable<WeatherForecast> Forecasts { get; }

    public FetchDataResultAction(IEnumerable<WeatherForecast> forecasts)
    {
        Forecasts = forecasts;
    }
}

Weather state

Weather state will the two property ´Forecasts´ and ´IsLoading´.

[FeatureState]
public class WeatherState
{
  public bool IsLoading { get; }
  public IEnumerable<WeatherForecast> Forecasts { get; }

  private WeatherState() { }
  public WeatherState(bool isLoading, IEnumerable<WeatherForecast> forecasts)
  {
    IsLoading = isLoading;
    Forecasts = forecasts ?? Array.Empty<WeatherForecast>();
  }
}

Weather Reducers

In the reducer we need to manage 2 action which are created by us. One is to trigger the data fetch and the next one is to handle the data result.

public static class Reducers
{
  [ReducerMethod]
  public static WeatherState ReduceFetchDataAction(WeatherState state, FetchDataAction action) =>
    new(isLoading: true, forecasts: null);

  [ReducerMethod]
  public static WeatherState ReduceFetchDataResultAction(WeatherState state, FetchDataResultAction action) =>
    new(isLoading: false, forecasts: action.Forecasts);
}

Weather effects

Effects is where we will make our ´http´ call. We will inject the ´HttpClient´ and use that in the effect method. once the call is successful we will dispatch an action with the data result.

public class Effects
{
    private readonly HttpClient Http;

    public Effects(HttpClient http)
    {
        Http = http;
    }

    [EffectMethod]
    public async Task HandleFetchDataAction(FetchDataAction action, IDispatcher dispatcher)
    {
        var forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json");
        if(forecasts is not null)
        {
            dispatcher.Dispatch(new FetchDataResultAction(forecasts: forecasts!));
        }
    }
}

Update the fetch data page

We need to update the fetch data page to use the weather state.

@page "/fetchdata" @inject IDispatcher dispatcher @inject IState<WeatherState>
  weather @inherits Fluxor.Blazor.Web.Components.FluxorComponent
  <PageTitle>Weather forecast</PageTitle>

  <h1>Weather forecast</h1>

  <p>This component demonstrates fetching data from the server.</p>

  @if (weather.Value.IsLoading) {
  <p><em>Loading...</em></p>
  } else {
  <table class="table">
    <thead>
      <tr>
        <th>Date</th>
        <th>Temp. (C)</th>
        <th>Temp. (F)</th>
        <th>Summary</th>
      </tr>
    </thead>
    <tbody>
      @foreach (var forecast in weather.Value.Forecasts) {
      <tr>
        <td>@forecast.Date.ToShortDateString()</td>
        <td>@forecast.TemperatureC</td>
        <td>@forecast.TemperatureF</td>
        <td>@forecast.Summary</td>
      </tr>
      }
    </tbody>
  </table>
  } @code { protected override void OnInitialized() { base.OnInitialized(); dispatcher.Dispatch(new
  FetchDataAction()); } }</WeatherState
>

Repo : https://github.com/antosubash/blazor-state-management-with-fluxor