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. It works by persisting the state of the page after it has been prerendered and restoring it when the page is rendered for the second time.


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. Include the necessary JS code

⚠️ This is a temporary step until a better solution is implemented. We expect this step to go away in one of the future releases.

Include this JS file in your Client project's wwwroot folder.

Then, also include the following line in your App.razor:

<script src="app.js"></script>

6. Enjoy

That's it! Your components' state should now be persisted across rendering environments.

You can learn more about initializing your components' state in the Initializing State section.

State Hierarchy

When using Blazor.State, it is important to understand how the state hierarchy works. This is important because Blazor.State works by persisting the state of the page after it has been prerendered and restoring it when the page is rendered for the second time.

Page State

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.

The page'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:

{
    "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:

<MyComponent>
    <MyComponent />
    <MyComponent />
</MyComponent>

In this case, the page's state hierarchy should look something like the following:

{
    "Text": "Some text",
    "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:

<MyComponent>
    <MyComponent StateId="NestedComponent1" />
    <MyComponent StateId="NestedComponent2" />
</MyComponent>

In this case, the page's state hierarchy should now look something like the following:

{
    "Text": "Some text",
    "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, 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.

Initializing State

Blazor.State adds several new lifecycle methods to your persistent components, aside from the normal Blazor lifecycle methods.

Lifecycle Methods

InitializeState

This method is called when the component's state is first initialized. This is the perfect place to initialize your component's state, as it is called only once during the component's lifecycle between the rendering environments. This method will be called when the component is initialized for the first time, and not when the component is re-rendered.

public class YourComponent : PersistentComponentBase
{
    [ComponentState]
    public int YourProperty { get; set; } = 0;

    protected override void InitializeState()
    {
        // Initialize your component's state here
        YourProperty = 420;
    }
}

InitializeStateAsync

This method works the same as InitializeState, but it is asynchronous. This is useful when you need to perform asynchronous operations to initialize your component's state. This method's invocation will be awaited before proceeding with initializing the component.

public class YourComponent : PersistentComponentBase
{
    [ComponentState]
    public int YourProperty { get; set; } = 0;

    protected override async Task InitializeStateAsync()
    {
        // Perform some asynchronous operation
        await Task.Delay(1000);

        YourProperty = 69;
    }
}