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
Post a Comment