Blazor State Management With Fluxor

In Blazor applications, managing state effectively is crucial for building robust and maintainable applications. As applications grow in complexity, handling state becomes increasingly challenging. This is where Fluxor, a state management library for Blazor, comes into play. In this article, we'll explore how Fluxor simplifies state management in Blazor applications by implementing a simple counter example.

Understanding Fluxor:

Fluxor follows the Flux architecture, a design pattern introduced by Facebook for managing application state in web applications. At its core, Fluxor enforces a unidirectional data flow pattern, which helps in maintaining a predictable state management process.

Key Concepts in Fluxor:

  1. Actions: Actions represent events or commands that occur within the application. They trigger state changes by dispatching to reducers.
  2. Reducers: Reducers are pure functions responsible for updating the application state based on dispatched actions. They take the current state and an action as input and produce a new state as output.
  3. Features: Features are logical groupings of related state, actions, and reducers. Each feature encapsulates its own state and behavior.
  4. Effects: Effects handle side effects such as asynchronous operations or interacting with external services. They are triggered in response to dispatched actions.

Implementing a Counter Example with Fluxor:

Let's dive into a simple counter example implemented using Fluxor in a Blazor application.

Register the Fluxor services in the Program.cs file,

builder.Services.AddFluxor(o =>
{
    o.ScanAssemblies(typeof(Program).Assembly);
    o.UseRouting();
#if DEBUG
    o.UseReduxDevTools();
#endif
});
Program.cs

Create a code file and add the following classes and records,

namespace BlazorFluxorStateManagement.Store.CounterUseCase
{
    public record IncrementCounterAction();
}

namespace BlazorFluxorStateManagement.Store.CounterUseCase
{
    [FeatureState]
    public record CounterState
    {
        public int Count { get; init; }

        private CounterState() { }

        public CounterState(int count)
        {
            Count = count;
        }
    }
}

namespace BlazorFluxorStateManagement.Store.CounterUseCase
{
    public class Reducers
    {
        [ReducerMethod(typeof(IncrementCounterAction))]
        public static CounterState ReduceIncrementCounterAction(CounterState state) =>
               state with { Count = state.Count + 1 };
    }
}


In this example, we define an action IncrementCounterAction, a feature state CounterState, and a reducer Reducers to handle the increment action.

@page "/counter"
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
@inject IState<CounterState> CounterState
@inject IDispatcher Dispatcher

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @CounterState.Value.Count</p>

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

@code {
    private void IncrementCount()
    {
        Dispatcher.Dispatch(new IncrementCounterAction());
    }
}
Counter.razor

This Blazor component represents the counter functionality. It displays the current count and increments it when the button is clicked by dispatching the IncrementCounterAction. Notice that we are inheriting from Fluxor.Blazor.Web.Components.FluxorComponent which is very important.

<Fluxor.Blazor.Web.StoreInitializer />

<Router AppAssembly="typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
        <FocusOnNavigate RouteData="routeData" Selector="h1" />
    </Found>
</Router>
Routes.razor

Finally, we initialize Fluxor and set up routing in the application.

Implementing Data Fetching with Fluxor:

Let's consider an example where we fetch weather forecast data from a remote service using Fluxor. Here's how the components and Fluxor features are structured:


public record FetchDataAction();
public record FetchDataSuccessAction(IEnumerable<WeatherForecast> Forecasts);
public record FetchDataErrorAction(string Error);

public class Effects
{
    private readonly WeatherForecastService _weatherForecastService;

    public Effects(WeatherForecastService weatherForecastService)
    {
        _weatherForecastService = weatherForecastService;
    }

    [EffectMethod]
    public async Task HandleAsync(FetchDataAction action, IDispatcher dispatcher)
    {
        try
        {
            var forecasts = await _weatherForecastService.GetForecastAsync();
            dispatcher.Dispatch(new FetchDataSuccessAction(forecasts));
        }
        catch (Exception ex)
        {
            dispatcher.Dispatch(new FetchDataErrorAction(ex.Message));
        }
    }
}

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

    [ReducerMethod]
    public static FetchDataState ReduceFetchDataSuccessAction(FetchDataState state, FetchDataSuccessAction action) =>
        new(false, action.Forecasts, null);

    [ReducerMethod]
    public static FetchDataState ReduceFetchDataErrorAction(FetchDataState state, FetchDataErrorAction action) =>
        new(false, null, action.Error);
}

// Weather Forecast Service
public class WeatherForecastService
{
    // Implementation details omitted for brevity
}

In this example, we define actions, effects, reducers, and a service for fetching weather forecast data. The effects handle the asynchronous operation of fetching data and dispatch appropriate success or error actions based on the result.

@page "/weather"
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
@inject IState<FetchDataState> FetchDataState
@inject IDispatcher Dispatcher

<PageTitle>Weather</PageTitle>

<h1>Weather</h1>

<p>This component demonstrates showing data.</p>

@if (FetchDataState.Value.IsLoading)
{
    <p><em>Loading...</em></p>
}
else
{
    if (FetchDataState.Value.Forecasts == null)
    {
        <p><em>@FetchDataState.Value.Error</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 FetchDataState.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());
    }
}
Weather.razor

In the Blazor component, we utilize Fluxor's integration to access the application state (FetchDataState) and dispatch actions (Dispatcher) as needed.

GitHub Repository:

https://github.com/fiyazbinhasan/BlazorFluxorStateManagement