If you are using Blazor WebAssembly with Fluxor, you will likely have come across the situation where refreshing the current page wipes out your state and sets it back to defaults.
This is not unique to Blazor/Fluxor - it happens in React/Redux too - but it’s a behaviour that is guaranteed to frustrate users.
This is one approach you can take to ensure that a browser refresh doesn't wipe out your Fluxor state.
Prerequisites
The following assumes you already have a Blazor WebAssembly project set up with Fluxor. If not, head over to https://github.com/mrpmorris/Fluxor and get started.
Step 1 - Session storage
First off we need to install Blazored.SessionStorage following the instructions at https://github.com/Blazored/SessionStorage. This gives us the core functionality to read and write objects to the browser’s Session Storage.
builder.Services.AddBlazoredSessionStorageAsSingleton();
Step 2 - Add a new action and reducer
At the heart of Fluxor are actions which mutate state via reducers. The beauty of actions is they can pass any object on their properties.
Normally an action would pass properties specific to a state, but this time we want an action that passes the state itself.
public record LoadUsersFromStorageAction(UsersState storedState);
Now that the action is created, we need to mutate the state in the reducer using the state object on the action.
[ReducerMethod]
public static UsersState ReduceLoadUsersFromStorageAction(UsersState state, LoadUsersFromStorageAction action)
{
return state with
{
ErrorMessage = action.storedState.ErrorMessage,
LoadingStatus = action.storedState.LoadingStatus,
Users = action.storedState.Users
};
}
Step 3 - Add a new wrapper component
Create a new Razor component called something like FluxorSessionStorage.razor that is going to handle the reads and writes e.g.
@inject IDispatcher _dispatcher;
@inject ISyncSessionStorageService _sessionStorage;
@inject IState<UsersState> _usersState;
@ChildContent
@code {
protected override void OnInitialized()
{
// read
var savedState = _sessionStorage.GetItem<UsersState>("UsersState");
if (savedState != null)
{
_dispatcher.Dispatch(new LoadUsersFromStorageAction(savedState));
}
// write
_usersState.StateChanged += ((state, e) =>
{
var data = ((IState<UsersState>)state).Value;
_sessionStorage.SetItem<UsersState>("UsersState", data);
});
}
[Parameter] public RenderFragment? ChildContent { get; set; }
}
As you can see, the OnInitialized method is used to wire up the storage methods. These methods are only called when the component is loaded on refresh.
The component will wrap the entire Blazor app thereby capturing all the Fluxor activity.
<Fluxor.Blazor.Web.StoreInitializer />
<CascadingAuthenticationState>
<FluxorSessionStorage>
<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>
</FluxorSessionStorage>
</CascadingAuthenticationState>
Step 4 - Test it
Now when we run the application, we should see our Fluxor state being persisted in the browser's Session Storage.
If we make any change to the state, we trigger a write to Session Storage.
Likewise, if we hit F5, we load the state from Session Storage.
Top comments (2)
Nice.
I'm wondering how well this would work with a large application state.
I have a few applications that end up with a relatively large amount of data in the various Fluxor stores, and I wonder if saving the state to storage on every update would have a noticeable impact on performance. Right now I'm selectively saving a small portion of the state to storage at specific times, but not everything on every update.
I haven’t tried it with a larger state yet so, yes there may be limitations in this approach.