In this article, we will review the Flux pattern with its benefits, and drawbacks in general. Then we will take a look into how Fluxor implements this pattern. Finally, we will learn how to use the library using an example Blazor application.

To download the source code for this article, you can visit our GitHub repository.

That said, let’s get started.

What Is the Flux Pattern

When an application grows too big, it becomes quite a challenge to update the state correctly. It is also difficult to debug why a specific state change happened. The Flux pattern is an architectural pattern designed to solve these complex state management problems on the front end. Its main goal is to make state changes transparent and predictable, thus making unit testing and debugging easier.

Let’s take a look at a typical Flux pattern flow and components:

Structure and data flow in Flux architectural pattern

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

Typically we’re starting from an action. Actions are events or user interactions in the application that require a state change. Next, the Dispatcher directs dispatched actions to the appropriate Store functions. The Store holds the application state. And the View is the UI that can generate new actions.

The most important principles of the Flux pattern are the unidirectional data flow and the read-only state.

Unidirectional Data Flow

Unidirectional data flow means, that data only flows in one direction in the application directly, typically from top to bottom, so from parent to children. Passing data directly back to the parent is forbidden, since we would have to have a reference to the parent which can cause serious problems. So to achieve this, the first thing we must enforce is that children cannot have access to their parents’ state. However, parents should also not modify their children’s state directly, because it can lead to unexpected state changes. They should initialize them, and then let them manage their state. So the state should be immutable.

Immutable State

In the Flux pattern, stores store the state. The store is responsible for managing state changes and emitting events when the state changes, allowing the UI to react. So if the state is immutable, children have no chance of modifying their parent’s state, nor the other way around. But then no data exchange can happen between components. That wasn’t our intention. Everyone should be able to modify their state and we need a way to allow the data flow between components.

This is where we employ actions. Using a dispatcher, we dispatch actions that contain any data necessary for the new state. Then, reducers handle these actions. A reducer’s task is to take an action and the current state, creating a new state object based on the two inputs. This ensures that the state is never modified; instead, it is always replaced.

Effects

Reducers are always pure functions, which means they cannot have a side effect, like data fetching. For example, if we need external data, we should use effects to fetch it, then dispatch an action from the effect, so reducers can update the state with the new data. The action dispatching is needed because only reducers are allowed to replace the state, effects can only execute side effects.

What Is Fluxor

Fluxor is a library for .NET that implements the Flux pattern and requires minimal boilerplate code to start using it. It has every infrastructure-related code already written, we just have to set it up.

Service Registrations

So let’s start by creating a Blazor WebAssembly application and installing the Fluxor.Blazor.Web NuGet package. Now we can register Fluxor into the dependency injection container in the Program class:

builder.Services.AddFluxor(options => options.ScanAssemblies(typeof(Program).Assembly));

Here, we use the AddFluxor() method, as well as the ScanAssemblies() method to scan our assembly for reducers and effects, then add them to the dependency injection container. However, Fluxor will not register middleware automatically.

Middlewares

We haven’t talked about middleware yet, because it is not strictly part of the Flux pattern, it’s rather an extension of it. Middleware allows us to hook into various lifecycle events, and execute side effects that shouldn’t be part of an effect. For example, we aim to log every dispatched action. Writing an effect for every action to just log it, is very inefficient. So the Fluxor team introduced middleware. We can execute code before and after every action dispatch, and the middleware itself can decide if it wants to execute something on a given action’s lifecycle event.

Razor Component Setup

Next, let’s add the store initializer component to the top of the App.razor file:

<Fluxor.Blazor.Web.StoreInitializer/>

We add the StoreInitializer component to initialize and keep track of the store, which can contain many state objects. It’s also responsible for collecting unhandled exceptions and throwing them.

To learn more about components in Blazor, check out our Blazor Components with Arbitrary and Cascading Parameters article.

And the last note about integrating Fluxor into our Blazor WebAssembly application is, that every component that has a state should inherit from the Fluxor.Blazor.Web.Components.FluxorComponent class. This ensures that the component rerenders when its state changes, because in the OnInitialized() and OnInitializedAsync() methods the base class registers its event listeners. Note, that if we override the aforementioned methods it’s important to call base.OnInitialized(), otherwise our component won’t rerender and won’t reflect state changes.

That said, let’s introduce the Flux pattern on the Counter page.

Using Reducers in Fluxor

The first thing we should do is to make the Counter page inherit the Fluxor.Blazor.Web.Components.FluxorComponent class:

@inherits Fluxor.Blazor.Web.Components.FluxorComponent

By adding the @inherits attribute to the top of the file, we make the Counter page a Fluxor component.

Creating the Fluxor State

Now let’s create a State folder and a Counter folder inside it, then add a new CounterState record to it:

[FeatureState]
public record CounterState
{
    public int ClickCount { get; init; }
}

Records are immutable reference types, so they are the perfect candidate to be state. Due to their immutability, we can’t modify them accidentally. We add the FeatureState attribute to the state, because Fluxor must discover and manage them. Also, we keep a parameterless constructor in state records. This is necessary because Fluxor uses reflection to instantiate them. If we were to add a constructor with parameters, it’s a good practice to create the parameterless one as private. Finally, we make the ClickCount property init only, so we will be able to use the with keyword when updating the state.

Let’s inject our state and the Dispatcher into the Counter page:

@page "/counter"
@using FluxorInBlazor.State.Counter
@using Fluxor
@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.ClickCount</p>

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

@code {
    private void IncrementCount()
    {
    }
}

Also, we remove the currentCount field, and use the state’s value in the markup. Now we are reading data from the state, but we are not updating it yet. Let’s fix it by creating the action and the reducer.

Creating the Action and the Reducer

In the Counter folder let’s create an Actions folder and add an IncrementCounterAction record:

public record IncrementCounterAction;

Actions should also be immutable since we do not want them to change after we dispatch them. Therefore using records here is also appropriate. Because we only want to increment the counter by one at a time, we won’t need any properties for our action, but we could have.

Next, let’s create a static Redures class inside the Counter folder:

[ReducerMethod]
public static CounterState ReduceIncrementCounterAction(CounterState state, IncrementCounterAction action)
{
    return new() {ClickCount = state.ClickCount + 1};
}

Here, We add the ReducerMethod attribute to the ReduceIncrementCounterAction() method, so Fluxor can discover it.

We make reducers static because they only take the previous state and the action as inputs and output a new state. If they are static, we cannot use dependency injection in them, thus we won’t trigger accidental side effects. Reducers always take the current state as their first argument and the action they handle as the second one. Fluxor will infer from the method signatures which one it should call for a specific action.

Finally, we return a new state with the incremented ClickCount.

Using the Dispatcher

The last step to make this work is to dispatch the IncrementCounterAction in the Counter page’s IncrementCount() method:

private void IncrementCount()
{
    Dispatcher.Dispatch(new IncrementCounterAction());
}

We use the Dispatch() method, passing in a new instance of our IncrementCounterAction record.

Notice, that action dispatching is not async code, because behind the scenes it just appends our action to a Queue and could process it at a later time. So regardless of synchronous code, Fluxor may not handle actions immediately.

Until now we created a CounterState record, an IncrementCounterAction, and a ReduceIncrementCounterAction() reducer method, and wired them up. Let’s now navigate to the Counter page in the application and click the button. The current count should be incremented by one. Behind the scenes, the dispatcher queued our action, and Fluxor forwarded it to our reducer. Which in turn, created a new state, with ClickCount incremented by one. Then the store replaced the state, and the counter component rerendered, displaying the state change to us. This is the basic Flux workflow, dispatch an action, reduce it into state, and rerender the page that relies on that state.

But how can we fetch data from the server if we can’t use async, or dependency injection in reducers? Let’s take a look at it using effects on the Weather page.

Using Effects in Fluxor

As on the Counter page, let’s start by inheriting the Weather page from Fluxor.Blazor.Web.Components.FluxorComponent:

@inherits Fluxor.Blazor.Web.Components.FluxorComponent

Creating the Fluxor State

Still similar to the Counter page, let’s create a Weather folder inside the State folder and add a WeatherState record to it:

[FeatureState]
public record WeatherState
{
    public bool IsLoading { get; init; }
    public IEnumerable<WeatherForecast> Forecasts { get; init; } = new List<WeatherForecast>();
}

Here, we introduce two properties on the state, to help avoid null pointer exceptions. When IsLoading is true, we will display a loading text, and we won’t access the Forecasts property. However, even if we accidentally access it while loading, it won’t throw a null pointer exception, because it has a default value of an empty list. And when the loading is finished, we set IsLoading to false, to indicate that the page can render the forecast list.

Let’s inject the state into the page, and update the razor markup to read data from the state:

@inject IState<WeatherState> WeatherState
@inject IDispatcher Dispatcher

@if (WeatherState.Value.IsLoading)
{
    <p>
        <em>Loading...</em>
    </p>
}
else
{
    @foreach (var forecast in WeatherState.Value.Forecasts)
    {
        @* code removed for brevity *@
        <tr>
            <td>@forecast.Date.ToShortDateString()</td>
            <td>@forecast.TemperatureC</td>
            <td>@forecast.TemperatureF</td>
            <td>@forecast.Summary</td>
        </tr>
    }
}

Here, we inject the state and the Dispatcher and remove the forecasts field. Instead, we use data from the state and do not send an HTTP request from the OnInitialized() method.

Creating the Actions

Now we will need two actions: one to initialize the data fetching, and another to dispatch when we have the data. So let’s create a FetchDataAction and a DataFetchedAction record in a new Actions folder inside Weather:

public record FetchDataAction;

The FetchDataAction is a simple record without any properties, because we use it as a signal to start fetching the data:

public record DataFetchedAction(IEnumerable<Pages.Weather.WeatherForecast> Forecasts);

On the other hand, we add a Forecasts property to the DataFetchedAction record because it will contain the fetched data.

Creating the Reducer

Let’s create a Reducers class and add a reducer that will initialize the state when the FetchDataAction is dispatched:

public static class Reducers
{
    [ReducerMethod(typeof(FetchDataAction))]
    public static WeatherState ReduceFetchDataAction(WeatherState state) => new() { IsLoading = true };
}

Here, we do not need any data from the action, so instead of adding it to the parameter list, we can pass its type as a parameter to the ReducerMethod attribute. We successfully evaded an unused method parameter warning.

Creating the Effects

Now let’s create an Effects class and add an effect method for this same action because Fluxor will execute both reducers and effects if they match an action, we don’t have to dispatch different actions for reducers and effects:

public class Effects(HttpClient http)
{
    [EffectMethod(typeof(FetchDataAction))]
    public async Task HandleFetchDataAction(IDispatcher dispatcher)
    {
        var forecasts = await http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json") 
                        ?? Array.Empty<WeatherForecast>();
        dispatcher.Dispatch(new DataFetchedAction(forecasts));
    }
}

We utilize C# 12’s primary constructors to inject the HttpClient into the Effects class. Next, we decorate the HandleFetchDataAction() method with the EffectMethod attribute, which can be used very similarly to the ReducerMethod attribute. Effect methods always get the dispatcher as a parameter, so when data fetching is finished, we can dispatch the DataFetchedAction.

Let’s create a reducer for this action too:

[ReducerMethod]
public static WeatherState ReduceDataFetchedAction(WeatherState state, DataFetchedAction action)
{
    return new() {IsLoading = false, Forecasts = action.Forecasts};
}

We take the data from the action and create a new state with it, that has IsLoading set to false, so the page will know that forecasts are available to display.

Finally, let’s dispatch the FetchDataAction in the Weather page’s OnInitialized() method:

protected override void OnInitialized()
{
    base.OnInitialized();
    Dispatcher.Dispatch(new FetchDataAction());
}

Remember to always call the base.OnInitialized() method to make the page rerender on state changes.

Now, when navigating to the weather page, the first reducer briefly displays the loading screen for a split second before the effect, and the second reducer finishes execution and shows the data.

Using Middleware in Fluxor

In the Fluxor introductory section, we mentioned middleware, and how they can execute code before and after all actions, or just specific ones. Let’s look at an example logging middleware, to see how they work.

To learn more about middleware please read our article ASP.NET Core Middleware – Creating Flexible Application Flows.

Let’s start by creating a Middlewares folder inside the State folder and add a LogginMiddleware class inside:

public class LoggingMiddleware : Middleware
{
}

We inherit the LoggingMiddleware from Fluxor’s Middleware base class and we will override the specific lifecycle methods we need.

Fluxor’s Middleware Lifecycles

Let’s look at the lifecycle of Fluxor’s middleware, which has five steps:

Middleware LifecycleDescription
Task InitializeAsync(IStore store)This is the first method that gets called during a middleware's lifecycle. We can use it to get a reference to the store and perform other necessary initialization.
void AfterInitializeAllMiddlewares()This method is called when all other middlewares finish their initialization, and the store is also ready to be used.
bool MayDispatchAction(object action)This method is called after the Dispatcher.Dispatch() method is called, but before the action is passed to the effects and reducers. Here, we can terminate the action dispatch, and completely prevent all effects and reducers from handling the action. If all middlewares return true, Fluxor dispatches the action; however, if even one returns false, it discards it.
void BeforeDispatch(object action)If the action didn't get discarded by a middleware, then before actually dispatching it this method will be called.
void AfterDispatch(object action)Finally, after reducers and effects finish execution, this method is called so we are notified about successful action handling.

Logging Implementation

For our logging middleware, let’s override the BeforeDispatch() method:

public override void BeforeDispatch(object action)
{
    Console.WriteLine($"Before dispatching action {action.GetType().Name}");
}

We simply log the name of the action to the console. As we already mentioned, middleware will not be registered automatically, so let’s add it by calling the AddMiddleware() method inside the AddFluxor() extension:

builder.Services.AddFluxor(options => options.ScanAssemblies(typeof(Program).Assembly)
    .AddMiddleware<LoggingMiddleware>());

Let’s reload the page and inspect the console output:

Before dispatching action StoreInitializedAction
Before dispatching action FetchDataAction
Before dispatching action DataFetchedAction

StoreInitializedAction is a built-in action Fluxor uses to notify its components that the store is ready. Also, we see in the log our two actions from the weather page in the order we dispatch them.

Conclusion

We discussed the Flux pattern itself, its core principles, and why and when should we use it.

Then we looked at how Fluxor implements this pattern, discovered some of its inner workings, and integrated it into a Blazor WebAssembly application.

After, we did a deep dive into reducers, effects, and middleware. We made the whole Blazor template use Fluxor for state management.

All in all, the most important takeaway is, to know when we need Fluxor, and when is the application simple enough to use easier state management mechanics. But if we need Fluxor, then we will now have the confidence to utilize its powerful state management features.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!