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:
- Actions: Actions represent events or commands that occur within the application. They trigger state changes by dispatching to reducers.
- 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.
- Features: Features are logical groupings of related state, actions, and reducers. Each feature encapsulates its own state and behavior.
- 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
});
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());
}
}
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>
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());
}
}
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
Comments