Blazor State Management



What do we mean by “state management” in Blazor?  State management is the persistence of data between pages / components.  State in Blazor in an application is not maintained for us, we have to maintain our on state with in our application.  State management can range from simple to the complex.  As with most implementation decisions when developing an application, it depends on what you need for which solution to use.

There are several ways / patterns to use to implement state in a Blazor application, store data in an external data store, user a third part NuGet package like BlazorFlex or just do it yourself.  My preference is to manage it myself.  This is because on most applications it is fairly easy to implement.

Normal Blazor pages don’t retain state.  If you run the standard Blazor template project you will see that if you click the “Click Me” button the current count increases by 1.  If you click home, then back to Counter will not see the current counter is back to 0.  We did not persist the original value for the current count.  This is the user case we are interested in resolving using state management.

There are 2 main ways to implement state management yourself:
  • Mark up drive
  • Code Driven

For this post I will be following the code driven method.  I find it simple but powerful.

To Implement Code Driven State Management:
1          1. Create a class to hold the state data
        public class CounterState
        {
            public int CurrentCount {get; set;}
        }

2            2. Add the new class to the startup class
a.       For user level of state management use “Scoped” service scope.
b.       For Application level state management, use “Singleton” service scope.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRazorPages();
            services.AddServerSideBlazor();
            services.AddScoped<CounterState>();
        }

3           3. Use the state class on the pages where you need to store and retrieve state data.

           In Header
@inject BlazorStateManagement.SessionState.CounterState CounterState

Code
       void IncrementCount()
       {
        CurrentCount++;
}

With this state management implemented, you can click on the “Click me” button on the counter page and the current count will increment.  Now go to the Home page, then back to the Counter page and you will notice that the current count is the same as before you click on home.

This will solve the state management between pages, but there is a use case where part of the application will see the data change, but the UI will not be updated.  This is caused by the way Blazor uses a virtual DOM.  The data changed but nothing told the UI side that it needs to be updated.  Since Blazor only updates the part of the DOM that has changes with those changes, we must tell Blazor that something changed.

To do this we need to set up an event handler and subscribe to it from the component that needs to know when a piece of state changed.  We will leverage the state change class to do this.
To see this use case will we put another label showing the current count in the Nav Control.

<div>
    <p style="color:white">Counter State: @CounterState.GetCurrentCount()</p>
</div>

Even with this label, you will see that the UI does not update when we click the button but will if we navigate off the Counter Page.

Let’s hook up the event handler

Step 1
First let’s update the state class to have an event and some properties.
    public class CounterState
    {
        private int currentCount = 0;

        public event EventHandler StateChanged;

        public int GetCurrentCount()
        {
            return currentCount;
        }
       
        public void SetCurrentCount(int paramCount)
        {
            currentCount = paramCount;
            StateHasChanged();
        }
       
        public void ResetCurrentCount()
        {
            currentCount = 0;
            StateHasChanged();
        }

        private void StateHasChanged()
        {
            StateChanged?.Invoke(this, EventArgs.Empty);
        }
    }

Step 2
    Update the Counter Page to use the new public methods of the state class
      <p>Current count: @CounterState.GetCurrentCount()</p>

      void IncrementCount()
      {
          int CurrentCount = CounterState.GetCurrentCount();
          CurrentCount++;
          CounterState.SetCurrentCount(CurrentCount);
      }

Step 3

Hookup the event on the Nav Control
*Note* You have to implement the Dispose method to unsubscribe to the event to limit memory leaks.

Header

    @inject CounterState
    @implements IDisposable

Code
    protected override void OnInitialized()
    {
        CounterState.StateChanged +=
        OnCounterStateAdvancedStateChanged;
    }

    void OnCounterStateAdvancedStateChanged(object sender, EventArgs e) => StateHasChanged();
    void IDisposable.Dispose()
    {
        CounterState.StateChanged -= OnCounterStateAdvancedStateChanged;
    }

So why do we have to wire in the event for the Nav Control but not for the actual Counter Page?  Since the counter page is an inline Blazor control, all in a single class, when the “Click me” button is clicked, it updates the virtual DOM because we change the UI.  Since the NAV Control part of the DOM didn’t change, there was nothing to do.  But when the event fires it is telling the virtual DOM that its UI was updated, and it needs to be re-displayed.

There is a lot more you can do with this state management.  You can mix it with component, services etc.


Comments

Popular posts from this blog

Yes, Blazor Server can scale!

Blazor new and improved Search Box

Blazor - Cool Blur Loading Page