Skip to content

Latest commit

 

History

History
227 lines (168 loc) · 5.83 KB

File metadata and controls

227 lines (168 loc) · 5.83 KB

Fluxor - Blazor Web Tutorials

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.

Goal

This tutorial will recreate the Fetch data page in a standard Blazor app.

Steps

  • Under the Store folder, create a new folder named WeatherUseCase.
  • 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);
}

Displaying state in the component

  • Find the Pages folder and add a new file named FetchData.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 from FluxorComponent.
@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

Using an Action and a Reducer to alter state

  • Create an empty class FetchDataAction.
  • Create a static Reducers class, which will set IsLoading to true when our FetchDataAction 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 inject IDispatcher and dispatch our action from the OnInitialized 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());
	}
}

Requesting data from the server via an Effect

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.

  1. 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));
	}
}
  1. 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).

Reducing the Effect result into state

  • 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.