Introduction
BitzArt.Blazor.State is a Blazor library that allows persisting your Blazor components' state across rendering environments.
How it works
When using one of Blazor's interactive render modes with prerendering enabled, you will have your page rendered twice:
Step 1: Prerender
Firstly, the server will prerender the page and send the prerendered version to the client. The prerendered page contains the HTML and also all the necessary assets (such as JS, CSS, etc.) to proceed with the following steps.
Step 2: Render
After the prerendered page is loaded, it will be rendered again. This time, the page will be rendered interactively - meaning that the user will be able to interact with the page after it is rendered.
This step happens on the server if you are using the InteractiveServer render mode, or on the client (using webassembly) if you are using the InteractiveWebAssembly render mode.
The problem
The problem with all this is that the state of your components is lost between the prerender and the render.
This means that if your page contains some state, it will be lost when the page is rendered for the second time - which implies initializing the page twice (which may include making WebAPI calls or other expensive operations).
The word "state" here may refer to any kind of data your components posess, including but not limited to:
- Data fetched from a WebAPI
- Data that is expensive to compute
- etc.
The solution
BitzArt.Blazor.State provides a way to persist your components' state across the prerender and render steps. This way, you can avoid initializing your components' state twice.
Getting Started
Refer to the Getting Started section to learn how to use this library in your Blazor applications.
Getting Started
Installation
Add this nuget package to both your Server and Client Blazor application projects:
dotnet add package BitzArt.Blazor.State
Configuration
Call this method in both your Server Program.cs
and Client Program.cs
and reference all assemblies in your project that may contain persistent components:
⚠️ Manually registering assemblies is a temporary step until a better solution is implemented. We expect this step to go away in one of the future releases.
builder.Services.AddBlazorState(state =>
{
state.AddAssembly(someAssembly); // Registers a specified assembly
state.AddAssemblyContaining<Program>(); // Registers an assembly where the referenced class is declared
});
ℹ️ You can find more information on registering assemblies in the source code
Use Blazor.State
To persist your components' state across rendering environments, follow these steps:
1. Inherit from PersistentComponentBase
Make sure your persistent components inherit from the PersistentComponentBase base class.
@inherits PersistentComponentBase // in your .razor file
or
public class YourComponent: PersistentComponentBase // in your .cs file
2. Mark state properties with [ComponentState] attribute
Use ComponentStateAttribute to mark properties that need to be persisted across rendering environments
public class YourComponent: PersistentComponentBase
{
[ComponentState]
public int YourProperty { get; set; } = 0;
}
3. Make sure the page is stateful
Any page containing persistent components, must also be stateful. This is a requirement due to how Blazor.State is implemented under the hood. This can be achieved by making sure your page component inherits from PersistentComponentBase, similarly to how any persistent component would.
4. Make sure your page meets the state hierarchy requirements
To learn more about page state hierarchy, refer to the State Hierarchy section.
5. Enjoy
That's it! Your components' state should now be persisted across rendering environments.
State Hierarchy
When using Blazor.State, it is important to understand how the state hierarchy works.
A page's state is composed of the states of all its stateful components. This means that if a page contains multiple stateful components, the page's state will be composed of the states of all those components.
After the page has been prerendered, it's state is persisted by encoding it into a JSON string. This string is then stored in the page's HTML, not visible to the user. When the page is rendered for the second time, the JSON string is decoded and the page's state is restored.
Component State Hierarchy
Consider the following component:
// MyComponent.razor
@inherits PersistentComponentBase
@Text
@ChildContent
@code
{
[ComponentState]
public string Text { get; set; } = "Some text";
[Parameter]
public RenderFragment ChildContent { get; set; }
}
Let's break down the state hierarchy of this component:
-
This component has a single state property,
Text
- this property is the component's own state. -
The component also allows populating it with nested content. This nested content can also have it's own state, in that case this will be this component's nested state.
Let's consider the following usage of this component:
<MyComponent>
<MyComponent />
</MyComponent>
In this case, this page's state hierarchy will look something like the following:
{
"n__MyComponent": {
"Text": "Some text",
"n__MyComponent": {
"Text": "Some text"
}
}
}
As you can see, the page's state is composed of the states of all its stateful components. In this case, the page contains two instances of MyComponent
, one nested inside the other.
-
The state of the nested component is stored in the parent component's state.
-
When nesting components, the state of the nested component is prefixed with
n__
to avoid conflicts with the parent component's state. -
For the name of the nested component's state property inside of it's parent component's state, the name of the nested component's type is used:
MyComponent
.
Component StateId
When persisting the state of a component inside a state hierarchy, Blazor.State uses the component's type name as an identifier by default. This approach works well in cases where a single state parent does not contain multiple stateful components of the same type.
Consider the following example of how a page containing multiple instances of the same component might look:
// MyPage.razor
<MyComponent />
<MyComponent />
In this case, the page's state hierarchy should look something like the following:
{
"n__MyComponent": {
"Text": "Some text"
},
"n__MyComponent": {
"Text": "Some text"
}
}
But this is not possible, because the state of the parent component is a JSON object, and JSON objects cannot contain multiple properties with the same name. Even if it was possible, it would not be possible to distinguish between the two nested components when restoring such state.
To solve this problem, Blazor.State introduces a component parameter StateId. Component StateIds are unique string identifiers that are used to distinguish between multiple components located under the same state parent.
Consider the following example:
// MyPage.razor
<MyComponent StateId="NestedComponent1" />
<MyComponent StateId="NestedComponent2" />
In this case, the page's state hierarchy should now look something like the following:
{
"n__NestedComponent1": {
"Text": "Some text"
},
"n__NestedComponent2": {
"Text": "Some text"
}
}
As you can see, the state of the nested components is stored in the parent component's state (in this example, the page is the parent component), and the state of each nested component is prefixed with n__
and suffixed with the component's StateId. This way, Blazor.State can distinguish between multiple components of the same type located under the same parent stateful component - and restore their states correctly, with each component receiving it's own state.
You can use this knowledge to build state hierarchies of any complexity, including ones with multiple nested components of the same type, and Blazor.State will take care of persisting and restoring their states for you.