Blazor Flash Cards - State Management with Fluxor
source link: https://fiyazhasan.me/state-management-with-fluxor/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Blazor Flash Cards - State Management with Fluxor
I'm a massive fan of the Redux pattern. I would say one thing, though, it's not everyone's cup of tea. It adds extra boilerplate code and may look overwhelming at first. But you get used to it and start to appreciate its power once your application grows to be a giant where state management becomes a nightmare.
Talking of sidekicks, React has Redux; Angular has NGRX; what does Blazor have? I'm really into this library called Fluxor recently. You can carry over your existing knowledge of any Redux
based state management library over here, and you are good to go.
You start by adding the following packages in your Blazor Server/WASM application,
dotnet add package Fluxor.Blazor.Web
dotnet add package Fluxor.Blazor.Web.ReduxDevTools
Register Fluxor
services with the default IoC
container. You would do that in Program.cs
file,
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
/* Code removed for brevity */
builder.Services.AddFluxor(opt =>
{
opt.ScanAssemblies(typeof(Program).Assembly);
opt.UseRouting();
opt.UseReduxDevTools();
});
await builder.Build().RunAsync();
}
Program.csInside index.html
, add the script reference for Fluxor.Blazor.Web
,
<!DOCTYPE html>
<html>
<head>
<!-- Code removed for brevity -->
</head>
<body>
<!-- Code removed for brevity -->
<script src="_framework/blazor.webassembly.js"></script>
<script src="_content/Fluxor.Blazor.Web/scripts/index.js"></script>
</body>
</html>
index.htmlStore:
As the name suggests, a store persists an application state tree, i.e., build-out of different feature states. The Store
has to be initialized when the application first kicks off. Hence, it is best to put out the Store
initialization logic in the App
component,
<Fluxor.Blazor.Web.StoreInitializer />
<Router AppAssembly="@typeof(Program).Assembly">
<!-- Code remve for brevity -->
</Router>
App.razorFeature:
Feature is an area that provides a new piece of state for the Store
. To declare a feature, you would want to extend the abstract Feature<T>
class available in Fluxor
. Feature<T>
takes a state type. The following feature class can be used to name a feature and an initial state for the CounterState
type,
public class Feature : Feature<CounterState>
{
public override string GetName()
{
throw new NotImplementedException();
}
protected override CounterState GetInitialState()
{
throw new NotImplementedException();
}
}
Feature.csThe state contains nothing but some properties. A state should be immutable in nature. You should never mutate a state directly, rather return a new state by changing its different properties. Hence, we can go for a record
instead of a class
,
public record CounterState(int Count);
public class Feature : Feature<CounterState>
{
public override string GetName() => "Counter";
protected override CounterState GetInitialState() => new(0);
}
Feature.csCounterState
contains a init
only property Count
and it is initialized with the value 0
.
Action:
A state should change based on different actions dispatched on it. An action may or may not have arguments. For example, an IncrementCounterAction
does nothing but increment the Count
property by 1
; but if you want to change the increment value to something more dynamic, use arguments.
public record IncrementCounterAction();
public record IncrementCounterByAction(int IncrementBy);
Actions.csReducer:
A reducer is a pure function that takes the current state and an action been dispatched upon it. Depending on the action type, it produces a new state and returns it.
public class Reducers
{
[ReducerMethod]
public static CounterState ReduceIncrementCounterAction(CounterState state, IncrementCounterAction action) =>
state with { Count = state.Count + 1 };
[ReducerMethod]
public static CounterState ReduceIncrementCounterByAction(CounterState state, IncrementCounterByAction action) =>
state with { Count = state.Count + action.IncrementBy };
}
Reducers.csEffect:
Effects are used to handle side effects in your application. A side effect can be anything from a long-running task to an HTTP call to a service. In these cases, state changes are not instantaneous. Effects perform tasks, which are synchronous/asynchronous, and dispatch different actions based on the outcome. The following uses the HttpClient
to asynchronously get the content of the sample-data/weather.json
file.
public class Effects
{
private readonly HttpClient _httpClient;
public Effects(HttpClient httpClient)
{
_httpClient = httpClient;
}
[EffectMethod]
public async Task HandleAsync(FetchDataAction action, IDispatcher dispatcher)
{
try
{
var forecasts = await _httpClient.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json");
dispatcher.Dispatch(new FetchDataSuccessAction(forecasts));
}
catch (Exception ex)
{
dispatcher.Dispatch(new FetchDataErrorAction(ex.Message));
}
}
}
Effects.csCheck the FetchDataUseCase to get a better understanding of the related state, actions, reducers and effects.
Accessing State:
Feature states can be injected in a razor component using the IState<T>
interface. Following shows how to inject the Counter
state in the Counter.razor
component,
@page "/counter"
@using Fluxord
@inject IState<CounterState> _counterState
<h1>Counter</h1>
<p>Current count: @_counterState.Value.Count</p>
Counter.razorYou use the
Value
property to get access to different properties of the injected state
Dispatching Action:
To dispatch an action on the store depending on a UI event, inject the IDispatcher
service and use the Dispatch
method passing the type of the action.
@page "/counter"
@using Fluxor
@using FlashCardsWasm.Store.CounterUseCase
@inject IState<CounterState> _counterState
@inject IDispatcher _dispatcher
<h1>Counter</h1>
<p>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.razorA similar approach is taken FetchData
component. Although one thing to notice here is the inheritance of the component from FluxorComponent
. Dispatching of the FetchDataSuccessAction
and FetchDataErrorAction
is happening from within the Effects
. The UI has to be notified i.e. call StateHasChanged
when these actions are dispatched. Using the FluxorComponent
takes care of that.
@page "/fetchdata"
@using Fluxor
@using FlashCardsWasm.Store.FetchDataUseCase
@using static FlashCardsWasm.Store.FetchDataUseCase.Actions
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
@inject IState<FetchDataState> _fetchDataState
@if (_fetchDataState.Value.Error != null)
{
<MudAlert Severity="Severity.Error">@_fetchDataState.Value.Error</MudAlert>
}
else
{
<MudGrid Class="mt-4">
<MudItem xs="12" sm="12" md="12">
<MudText Typo="Typo.h3">Weather forecast</MudText>
<MudText Typo="Typo.subtitle1">This component demonstrates fetching data from a service.</MudText>
</MudItem>
<MudItem xs="12" sm="12" md="12">
<MudTable Items="@_fetchDataState.Value.Forecasts" Hover="true" Breakpoint="Breakpoint.Md" Loading="@_fetchDataState.Value.IsLoading"
LoadingProgressColor="Color.Info">
<HeaderContent>
<MudTh>Date</MudTh>
<MudTh>Temp. (C)</MudTh>
<MudTh>Temp. (F)</MudTh>
<MudTh>Summary</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Date">@context.Date</MudTd>
<MudTd DataLabel="TempC">@context.TemperatureC</MudTd>
<MudTd DataLabel="TempF">@context.TemperatureF</MudTd>
<MudTd DataLabel="Summary">@context.Summary</MudTd>
</RowTemplate>
</MudTable>
</MudItem>
</MudGrid>
}
@code {
[Inject]
public IDispatcher Dispatcher { get; set; }
protected override void OnInitialized()
{
Dispatcher.Dispatch(new FetchDataAction());
base.OnInitialized();
}
}
FetchData.razorRedux DevTools
To tinker around with application state, you should have the Redux DevTools extension installed in your preferred browser. Open up the Developer Console
, go to the Redux
tab. You will see what actions are dispatched and how your application state is modified.
Repository Link:
Part II - https://github.com/fiyazbinhasan/FlashCardsWasm/tree/Part-II-State-Management-With-Fluxor
Important Links:
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK