Search code examples
blazorfluxor

Blazor Fluxor - Best place to pre-load application state


I'm currently using Blazor Server, but I want this in WASM as well.

I want to load the application state into Fluxor before any components load, including the layout. I want to be able to load initial state into Fluxor and be able to use this state early on in components. Would App.razor be the right place to load the initial state?

I created the following, but I'm also not sure that App.razor executes for every user which this requires. Some app state would be user specific. I imagine that this may not be the best place to pre-load Fluxor with state data. Is there another better way?

<Fluxor.Blazor.Web.StoreInitializer />

@*
    // TODO: I want state to load before everything else so that I can use it anywhere.
    // TODO: Load state into Fluxor in the following component.
*@
<InitializeApplicationState />

<MudThemeProvider Theme="@(new Theme())" /> @* Load theme from Fluxor state *@
<MudDialogProvider />
<MudSnackbarProvider />

<Router AppAssembly="@typeof(App).Assembly" AdditionalAssemblies="new[] { typeof(Home).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>

@code {
    //
}

UDPATE:

I've implemented what I feel is very close to a solution. The following is what I've done using the accepted solution. The _firstRender variable is always false due to Blazor Server using server pre-rendereding. I'm not sure that there is a way around this but the solution is otherwise good. The other new problem I'm having which is not part of the original question here is that the LoadingScreen flashes on the first render which doesn't look nice.

@inherits Fluxor.Blazor.Web.Components.FluxorComponent
@using Fluxor
@using Client.Shared.Models.Authentication;
@using Client.Shared.StateStores.Authentication
@using AuthenticationState = Client.Shared.StateStores.Authentication.AuthenticationState
@using Client.Shared.LoadingIndicators

<Fluxor.Blazor.Web.StoreInitializer UnhandledException="UnhandledException" />
<MudThemeProvider Theme="@(new EamTheme())" />
<MudDialogProvider />
<MudSnackbarProvider />

<LoadingScreen Loaded="authenticationState.Value.Loaded">
    <Router AppAssembly="@typeof(App).Assembly" AdditionalAssemblies="new[] { typeof(Home).Assembly }">
        <Found Context="routeData">
            <RouteView RouteData="@routeData" DefaultLayout="@typeof(MarineEAM.Client.BServer.Shared.MainLayout)" />
            <FocusOnNavigate RouteData="@routeData" Selector="h1" />
        </Found>
        <NotFound>
            <PageTitle>Not found</PageTitle>
            <LayoutView Layout="@typeof(MarineEAM.Client.BServer.Shared.MainLayout)">
                <p role="alert">Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</LoadingScreen>

@code {
    [Inject]
    private IDispatcher dispatcher { get; set; } = default!;

    [Inject]
    private IState<AuthenticationState> authenticationState { get; set; } = default!;

    private bool firstRender = true;

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        parameters.SetParameterProperties(this);

        if (firstRender)
        {
            bool loaded = authenticationState.Value.Loaded;
            // Preload application state
            //GetAuthenticatedUserAsync();
            firstRender = false;
        }

        await base.SetParametersAsync(ParameterView.Empty);
    }

    override protected void OnInitialized()
    {
        bool loaded = authenticationState.Value.Loaded;
        base.OnInitialized();
    }

    protected override Task OnInitializedAsync()
    {
        bool loaded = authenticationState.Value.Loaded;
        return base.OnInitializedAsync();
    }

    private void GetAuthenticatedUserAsync()
    {
        if (!authenticationState.Value.Authenticated)
        {
            dispatcher.Dispatch(new LoadAuthenticationStateAction());
        }
    }

    private void UnhandledException(Fluxor.Exceptions.UnhandledExceptionEventArgs e)
    {
        Console.WriteLine("Fluxor exception: " + e.Exception.ToString());
    }
}

Solution

  • I saw your comment in this question - How to run async method before render in Blazor Server

    I'm not a Fluxor expert: I roll my own state management.

    I want to be able to load initial state into Fluxor and be able to use this state early on in components. Would App.razor be the right place to load the initial state?

    Yes. App is just another component. It's special only because it's the root component in the Render Tree. Unless you trigger a render, App is only rendered once (or twice if you do some async code in OnInitilizedAsync) for every SPA session. There's no UI events to trigger further renders.

    If your code is synchronous then you can run it in OnInitialized and it will complete before any rendering takes place.

    If it's async code then you need to override SetParametersAsync. You have to be careful in how you do this and use the following pattern. I've commented the code to explain what's going on.

    <Router AppAssembly="@typeof(App).Assembly">
      //...
    </Router>
    
    @code {
        private bool _firstRender = true;
    
        // Overridden to provide an async method that is run before any rendering
        public override async Task SetParametersAsync(ParameterView parameters)
        {
            // Apply the supplied Parameters to the Conponent
            // Must do this first
            parameters.SetParameterProperties(this);
    
            // Run the Pre Render code
            @if (_firstRender)
            {
                await this.PreFirstRenderAsync();
                _firstRender = false;
            }
    
            // Run the base SetParametersAsync providing it with an empty ParameterView
            // We have already applied the parameters and then may already be stale
            // This runs the normal lifecycle methods
            await base.SetParametersAsync(ParameterView.Empty);
        }
    
        // Separate out this component's code to make it clear what we're doing
        // We use a ValueTask because it's cheaper if no real async await occurs
        public ValueTask PreFirstRenderAsync()
        {
            // Your async code goes here
            // You can run sync code too if you wish
            return ValueTask.CompletedTask;
        }
    }