La gestione dello stato dell’applicazione web è sempre un compito complesso. Al crescere del numero dei componenti, e del nesting tra gli stessi, della nostra applicazione Blazor aumenta anche la complessità della gestione del flusso dei dati tra componenti. Ci vuole poco a trasformare qualcosa partito come una semplice applicazione in un mostro di componenti accoppiate se non si presta attenzione a questo tema.
Modalità di gestione dello stato nei nostri componenti
Abbiamo diverse modalità built-in nel framework per la gestione dello stato all’interno dei nostri componenti, indipendentemente dall’hosting model scelto. Le abbiamo già approfondite nell’articolo di Alberto che vi invito a recuperare, ma rivediamole velocemente.
Il modo naïve di rispondere alla necessità di gestione dello stato interno del componente è lasciare che sia il componente stesso a recuperare in autonomia i dati, magari iniettando un servizio che dialoghi con una fonte dati. Questo approccio è sicuramente il più veloce da implementare, ci fa capire istantaneamente da dove vengono i dati semplicemente guardando il codice del componente e rende il componente stesso indipendente dal padre che lo incapsula (ovvero: ovunque andiamo a mettere il componente, questo continuerà a funzionare perché è autonomo nella gestione del proprio stato). D’altro canto ogni volta che viene effettuato il render del componente andiamo ad eseguire le operazioni di lettura dei dati deteriorando sicuramente le performance; inoltre dobbiamo gestire l’aggiornamento dello stato da parte di eventuali altri componenti che lo manipolano ed il componente stesso sarà accoppiato alle logiche ed ai meccanismi di recupero dati.
Un altro modo che abbiamo è lasciare che sia il componente padre a passare direttamente i dati al componente figlio tramite i Parameter. In questo modo rendiamo il componente agnostico rispetto al modo in cui dati vengono recuperati, possiamo testarlo più facilmente con bUnit, ma iniziamo ad accoppiare i componenti e i dati possono fluire attraverso i componenti solo per essere trasmessi ai componenti figli senza che il componente stesso li utilizzi.
Una modalità che risolve, almeno in parte, i problemi dell’approccio sopra è quella di usare i CascadingValue. Questo ci permette di costruire un contenitore in cui tutti i componenti contenuti hanno accesso ai valori iniettati nel contenitore stesso. In questa maniera i componenti ai livelli superiori non dovranno più fare da “passa-carte” per i dati dei componenti ai livelli inferiori perché questi saranno autonomi nel recuperare i dati stessi. All’aumentare dei CascadingValue l’esperienza di sviluppo ne inizia a risentire: ogni volta dobbiamo andare a cercare dove e come questi dati vengono messi nel “contenitore”.
Introduzione al pattern Flux in Blazor con Fluxor
Un modo meno diretto e leggermente più complesso per gestire lo stato e che va sicuramente ponderato, come l’introduzione di qualsiasi altro pattern, è quello di utilizzare lo “State Container” o State Store. In questa modalità lo stato della nostra applicazione è centralizzato in una classe e tutti i componenti possono andare a leggere i dati dallo store. La declinazione più avanzata di questo pattern nel mondo web è sicuramente Flux di Facebook che ha dato poi i natali a librerie di state management molto interessanti come Redux e Mobx. Questo approccio è stato poi portato in Blazor da Peter Morris con Fluxor.
Tramite una serie di accorgimenti, questa libreria ci permette di creare applicazioni Blazor che siano reattive e che abbiano uno stato che sia sempre prevedibile.
Questo pattern si compone dei seguenti elementi chiave:
- State: è un oggetto read-only che contiene lo stato della nostra applicazione o una parte di esso; questo stato viene aggiornato soltanto dai Reducer;
- Action: sono oggetti che vengono inviati al Dispatcher dai componenti in seguito alle interazioni dell’utente e che indicano una determinata azione da effettuare;
- Reducer: sono funzioni, idealmente pure, che ricevono in input la Action e lo State attuale e restituiscono il nuovo valore dello stato;
Proviamo a vedere come questi elementi interagiscono tra di loro con il classico esempio del componente Counter di default.

- L’utente arriva sulla nostra applicazione Blazor, naviga alla pagina del componente counter e clicca sul bottone “Click me” per far aumentare il valore del current count di uno;
- Al click del bottone andiamo ad inviare una azione (Action) allo Store tramite il Dispatcher indicando che l’utente ha l’intenzione di far aumentare il counter;
- Questo evento viene intercettato da un Reducer che sta in ascolto di quella specifica azione e che, a partire dallo stato attuale, andrà a calcolare il nuovo stato; il calcolo che farà il reducer nel nostro caso è molto semplice, infatti dovrà soltanto creare un nuovo stato che abbia come valore il precedente “current count” a cui sommerà uno;
- Lo stato così calcolato verrà impostato come nuovo stato all’interno del nostro store; Fluxor invierà quindi una notifica a tutti quei componenti interessati ai cambiamenti del valore del Counter; la notifica andrà a generare l’aggiornamento della UI.
Questa libreria ci permette di avere un flusso dei dati che sia veramente unidirezionale, rende ripetibili tutte le azioni che gli utenti eseguono sulla nostra applicazione e, soprattutto, ogni transizione di stato all’interno della nostra applicazione sarà prevedibile.

Un esempio di Fluxor
Supponiamo di voler creare una piccola applicazione Blazor per la gestione di un carrello di prodotti. Avremo quindi una pagina che ci permetterà di aggiungere un nuovo prodotto e che mostrerà lo stato attuale del carrello.
Inizializziamo la libreria andando ad inserire nel file Program.cs
le istruzioni di wiring e di discovery automatica delle varie classi ed interfacce della libreria. Per farlo basta solo aggiungere Fluxor ai servizi della nostra applicazione così:
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddFluxor(options => options.ScanAssemblies(typeof(Program).Assembly));
await builder.Build().RunAsync();
L’inizializzazione della libreria non è finita, dobbiamo inserire un componente StoreIntializer
nella nostra applicazione che si occuperà di fare il bootstrap delle librerie JavaScript di Fluxor. Andiamo nel file App.razor ed aggiungiamo il componente in testa a tutto.
<Fluxor.Blazor.Web.StoreInitializer />
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
Per prima cosa andiamo a creare una classe che conterrà lo stato della nostra applicazione; da best practice creiamo una cartella Store nella nostra applicazione e al suo interno una cartella CartUseCase, qui dentro creiamo la nostra classe CartState.
[FeatureState]
public record CartState
{
public ImmutableArray<Item> ItemsInCart { get; } = ImmutableArray.Create<Item>();
private CartState() { } // Serve a costruire lo stato iniziale
public CartState(ImmutableArray<Item> itemsInCart)
{
ItemsInCart = itemsInCart;
}
}
Passiamo quindi alla creazione della action che useremo per aggiungere un nuovo elemento al nostro carrello. Visto che le action possono essere riutilizzate andiamo a creare un oggetto AddItemToCartAction
nella cartella Store.
public class AddItemToCartAction
{
public Item ItemToAdd { get; }
public AddItemToCartAction(Item itemToAdd)
{
this.ItemToAdd = itemToAdd;
}
}
Questa classe conterrà soltanto una proprietà di tipo Item
che rappresenta l’oggetto che vogliamo aggiungere nel carrello.
Fatto ciò passiamo alla scrittura del reducer che si occuperà della gestione della azione appena creata. Dato che i reducer sono funzioni pure, andiamo a creare nella cartella Store/CartUseCase una nuova classe Reducers
che conterrà un solo metodo statico ReduceAddItemToCartAction
.
public static class Reducers
{
[ReducerMethod]
public static CartState ReduceAddItemToCartAction(CartState state, AddItemToCartAction action) =>
new CartState(itemsInCart: state.ItemsInCart.Add(action.ItemToAdd));
}
Ora abbiamo tutti i componenti per poter scrivere il nostro componente. Andiamo quindi a creare una nuova pagina Items.razor
che conterrà il markup del nostro componente.
@page "/items"
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
<h3>Items</h3>
<div class="row">
<div class="form-group col-4">
<label class="form-label" for="name">Nome:</label>
<input class="form-control" id="name" @bind-value="Name" />
</div>
<div class="form-group col-4">
<label class="form-label" for="price">Prezzo:</label>
<input class="form-control" id="price" type="number" @bind-value="Price" />
</div>
<div class="col-4 align-self-end">
<button type="button" class="btn btn-primary" @onclick="AddItem">
Aggiungi
</button>
</div>
</div>
<div class="mt-2 row">
@if (CartState.Value.ItemsInCart.Any())
{
@foreach (var item in CartState.Value.ItemsInCart)
{
<div class="col-4 mt-2">
@item.Name - @item.Price €
</div>
}
}
else
{
<span>
Non ci sono oggetti nel carrello :(
</span>
}
</div>
È importante che il componente vada ad estendere il componente base FluxorComponent
, in questa maniera il motore di Fluxor potrà informare il componente dei cambiamenti che avvengono allo State e la UI si aggiornerà di conseguenza. Altro punto interessante è il fatto che i dati mostrati a video saranno presi dall’oggetto CartState che verrà iniettato nel componente poco più avanti. Andiamo quindi a scrivere la logica del componente in un nuovo file Items.razor.cs
. In questa classe parziale andremo ad iniettare il CartState
ed il Dispatcher
per effettuare il “dispatch” delle azioni a Fluxor.
public partial class Items
{
[Inject]
private IState<CartState> CartState { get; set; }
[Inject]
public IDispatcher Dispatcher { get; set; }
private string Name { get; set; }
private decimal Price { get; set; }
private void AddItem()
{
var itemToAdd = new Item(Name, Price);
var addItemAction = new AddItemToCartAction(itemToAdd);
Dispatcher.Dispatch(addItemAction);
}
}
Il metodo AddItem
verrà invocato al click del bottone “Aggiungi”, genererà un nuovo Item
con i valori inseriti dall’utente nei due input e effettuerà il “dispatch” dell’azione AddItemToCartAction
.
Come abbiamo visto in precedenza il Reducer si prenderà carico della gestione di questa azione, andrà a ricalcolare lo stato e in automatico la nostra UI

Qualora un altro componente vorrà avere accesso a questi dati sarà sufficiente iniettare l’oggetto IState<CartState>
come abbiamo appena fatto nel componente Items.
Ad esempio possiamo andare nel component NavMenu, iniettare lo stato ed utilizzare i dati di questo stato per mostrare informazioni aggregate.
@using Fluxor
@using FluxorSampleApp.Store.CartUseCase
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">FluxorSampleApp</a>
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
</div>
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<nav class="flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="items">
<span class="oi oi-plus" aria-hidden="true"></span> Items (@CartState.Value.ItemsInCart.Length) - Tot: @CartState.Value.ItemsInCart.Sum(i => i.Price) €
</NavLink>
</div>
</nav>
</div>
@code {
[Inject]
private IState<CartState> CartState { get; set; }
private bool collapseNavMenu = true;
private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
}
Visto che ora i dati non vivono più all’interno dei nostri componenti, ma sono in uno store centralizzato, anche qualora l’utente dovesse cambiare pagina, al suo ritorno si ritroverebbe comunque i dati disponibili.

Conclusione
Ma quindi questo approccio è l’unico da utilizzare per la gestione dello stato? La risposta è chiaramente no. Questo paradigma funziona molto bene per applicazioni con casi d’uso particolari che hanno molteplici componenti che aggiornano lo stesso “pezzo di stato” e altrettanti che lo devono visualizzare, rende sicuramente molto comoda l’esperienza di debug e testing vista la natura pura delle funzioni di reduce e aiuta a scrivere codice più pulito forzando la separazione della UI dalla logica dell’applicazione.

Tuttavia queste facilitazioni vengono con dei costi ben specifici in termini di “boilerplate code” (e non abbiamo ancora visto la parte di effects; ricordate che i reducer sono funzioni pure ovvero niente chiamate API per aggiornare o creare gli oggetti in esse), è una sovra-ingegnerizzazione per quelle applicazioni che hanno UI molto semplici o applicazioni in cui lo stato viene passato soltanto da un componente padre ad uno figlio senza scendere troppo nell’albero.
Il codice per la piccola demo mostrata nell’articolo è disponibile nel seguente repository su Github.