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.
This tutorial will recreate the Fetch data
page in a standard Blazor app.
- Under the
Store
folder, create a new folder namedWeatherUseCase
. - Create a new state class to hold the state for this use case.
public class WeatherState
{
public bool IsLoading { get; }
public IEnumerable<WeatherForecast> Forecasts { get; }
public WeatherState(bool isLoading, IEnumerable<WeatherForecast> forecasts)
{
IsLoading = isLoading;
Forecasts = forecasts ?? Array.Empty<WeatherForecast>();
}
}
This state holds a property indicating whether or not the data is currently being retrieved from
the server, and an enumerable holding zero to many WeatherForecast
objects.
Note: Again, the state is immutable
- Create a new class named
Feature
. This class describes the state to the store.
public class Feature : Feature<WeatherState>
{
public override string GetName() => "Weather";
protected override WeatherState GetInitialState() =>
new WeatherState(
isLoading: false,
forecasts: null);
}
- Find the
Pages
folder and add a new file namedFetchData.razor.cs
- Mark the class
partial
. - Add the following
using
declarations
using Fluxor;
using Microsoft.AspNetCore.Components;
using YourAppName.Store.WeatherUseCase;
- Next we need to inject the
WeatherState
into our component
public partial class FetchData
{
[Inject]
private IState<WeatherState> WeatherState { get; set; }
}
- Edit
FetchData.razor
and make the page descend fromFluxorComponent
.
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
- Change the mark-up so it uses our
IsLoading
state to determine if data is being retrieved from the server or not.
Change
@if (forecasts == null)
to
@if (WeatherState.Value.IsLoading)
- Change the mark-up so it uses our
Forecasts
state.
Change
@foreach (var forecast in forecasts)
to
@foreach (var forecast in WeatherState.Value.Forecasts)
- Remove
@inject WeatherForecastService ForecastService
- Create an empty class
FetchDataAction
. - Create a static
Reducers
class, which will setIsLoading
to true when ourFetchDataAction
action is dispatched.
public static class Reducers
{
[ReducerMethod]
public static WeatherState ReduceFetchDataAction(WeatherState state, FetchDataAction action) =>
new WeatherState(
isLoading: true,
forecasts: null);
}
- In
Fetchdata.razor.cs
injectIDispatcher
and dispatch our action from theOnInitialized
lifecycle method. The code-behind class should now look like this
public partial class FetchData
{
[Inject]
private IState<WeatherState> WeatherState { get; set; }
[Inject]
private IDispatcher Dispatcher { get; set; }
protected override void OnInitialized()
{
base.OnInitialized();
Dispatcher.Dispatch(new FetchDataAction());
}
}
Effect handlers do not (and cannot) affect state directly. They are triggered when the action they are waiting for is dispatched through the store, and as a response they can dispatch new actions.
Effect handlers can be written in one of two ways.
- By descending from the
Effect<TAction>
class. The name of the class is unimportant.
public class FetchDataActionEffect : Effect<FetchDataAction>
{
private readonly HttpClient Http;
public FetchDataActionEffect(HttpClient http)
{
Http = http;
}
protected override async Task HandleAsync(FetchDataAction action, IDispatcher dispatcher)
{
var forecasts = await Http.GetJsonAsync<WeatherForecast[]>("WeatherForecast");
dispatcher.Dispatch(new FetchDataResultAction(forecasts));
}
}
- By decorating instance or static methods with
[EffectMethod]
. The name of the class and the method are unimportant.
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.GetJsonAsync<WeatherForecast[]>("WeatherForecast");
dispatcher.Dispatch(new FetchDataResultAction(forecasts));
}
}
Both techniques work equally well, which you choose is an organisational choice. However, if your effect requires lots of (or unique) dependencies then you should consider having the handling method in its own class for simplicity (still either approach #1 or #2 may be used).
- Create a new class
FetchDataResultAction
, which will hold the results of the call to the server so they can be "reduced" into our application state.
public class FetchDataResultAction
{
public IEnumerable<WeatherForecast> Forecasts { get; }
public FetchDataResultAction(IEnumerable<WeatherForecast> forecasts)
{
Forecasts = forecasts;
}
}
This is the action that is dispatched by our Effect
earlier, after it has retrieved the data from
the server via an HTTP request.
- Edit the
Reducers.cs
class and add a new[ReducerMethod]
to reduce the contents of this result action into state.
[ReducerMethod]
public static WeatherState ReduceFetchDataResultAction(WeatherState state, FetchDataResultAction action) =>
new WeatherState(
isLoading: false,
forecasts: action.Forecasts);
This reducer simply sets the IsLoading
state back to false, and sets the Forecasts
state to the
values in the action that was dispatched by our effect.