Special Offer: My C#/.NET Bootcamp Course is out now. Get 10% OFF using the code FRIENDS10.

State handling is a common problem. It doesn’t matter what kind of application; sooner or later, you want the different parts of your application to talk to each other.

In this video, I want to show you a simple way for parent-child communication, an option for component hierarchies, and a more complex solution for advanced state handling in Blazor web applications.


In this video, you will learn about component communication and state handling in Blazor web applications.

Event Callbacks 

If you watched my FREE Blazor Crash Course, you already know about EventCallbacks

EventCallbacks are glorified Action and Func implementations that handle calling the StateHasChanged method. Using EventCallbacks makes a lot of sense when we have a nested component structure and want to call a method on the parent component from within the child component.

Let’s look at the following Blazor WebAssembly application, where we have a page component with a list of cars and a component representing a row in the table.

@page "/event-callbacks"

<h2>Cars</h2>
<table>
    <thead>
        <tr>
            <td style="width: 100px;"><b>Make</b></td>
            <td style="width: 140px;"><b>Model</b></td>
            <td style="width: 80px;">&nbsp;</td>
        </tr>
    </thead>
    <tbody>
        @foreach (var car in Cars)
        {
            <CarItem Car="@car" DeleteItem="@DeleteCar" />
        }
    </tbody>
</table>

@code {
    private IList<CarModel> Cars = new List<CarModel> {
        new CarModel { Make = "Ford", Model = "Mustang" },
        new CarModel { Make = "BMW", Model = "M5" },
        new CarModel { Make = "Audi", Model = "R6" },
    };

    public void DeleteCar(CarModel car)
    {
        Cars.Remove(car);
    }
}

As you can see in the code section, we have a list of the CarModel type and a DeleteCar method. In the template section, we have a table definition and use a foreach loop to render a CarItem component for every CarModel in the Cars list. We provide the CarModel to the Car property and the DeleteCar method to the DeleteItem property.

Now let’s open the CarItem component.

<tr>
    <td>@Car.Make</td>
    <td>@Car.Model</td>
    <td><a href="/event-callbacks" @onclick="() => DeleteItem.InvokeAsync(Car)">Delete</a></td>
</tr>

@code {
    [Parameter]
    public CarModel Car { get; set; }

    [Parameter]
    public EventCallback<CarModel> DeleteItem { get; set; }
}

In the child component, we have two properties in the code section. We have the Car property that holds the CarModel, and we have the DeleteItem property, which is of type EventCallback of CarModel.

In the template section of the child component, we call the InvokeAsync method of the DeleteItem property when a user clicks on the Delete link. We provide the Car property as an argument to the DeleteItem EventCallback.

As you can see, EventCallbacks allow us to focus on component development, and they let us conveniently call a method on the parent component.

Cascading Values

Sometimes we don’t have a simple parent-child situation, but we want to build a component hierarchy

An example could be that we have a page that holds user information. We want to make that user information accessible to every child component without explicitly passing it down the component tree.

In the CascadingValues page component, we define a User property of type UserModel and assign values to its fields. In the template section of the component, we use the CascadingValue component and set the value property to the User property we created in the code section.

@page "/cascading-values"

<h2>User</h2>

<CascadingValue Value="User">
    <UserSettings />
</CascadingValue>

@code {
    public UserModel User { get; }

    public CascadingValues()
    {
        User = new UserModel
        {
            Name = "Claudio Bernasconi",
            Username = "claudiobernasconi"
        };
    }
}

Every component wrapped in the CascadingValue component has access to the value in the Value property even though we do not populate it as an argument when using the component.

Let’s open the UserSettings component. We can see that we also have a User property of type UserModel. But this time, we use the Cascading Parameter attribute. We can use the User’s properties in the template section like a regular property or a regular component parameter.

<div style="padding: 20px; width: 300px; display: flex; flex-direction: column; border: 1px solid #808080;">
    <div>Name: @User.Name</div>
    <div>Username: @User.Username</div>
</div>

@code {
    [CascadingParameter]
    public UserModel User { get; set; }
}

Depending on your use case, you can either use regular component parameters or cascading values.

State Container

State containers are the most complex communication pattern explained in this video. You can implement it yourself or use a library. We’re going to take a look at a basic implementation.

The idea is to create a context class registered as a dependency in the application and inject it into the components that need access to the state.

Let’s open the AppState class.

using System;

namespace ComponentCommunication.Client.Shared
{
    public class AppState
    {
        public string FavoriteAnimal { get; set; }
        
        public event Action OnFavoriteAnimalChange;

        public void SetFavoriteAnimal(string favoriteAnimal)
        {
            FavoriteAnimal = favoriteAnimal;
            OnFavoriteAnimalChange?.Invoke();
        }
    }
}

We can see a FavoriteAnimal string property. Next, we have an OnFavoriteAnimalChange Action and a SetFavoriteAnimal method.

In the SetFavoriteAnimal method, we assign the method argument to the property of the AppState class, and if there is an OnFavoriteAnimalChange handler registered, we call that handler too.

using ComponentCommunication.Client.Shared;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace ComponentCommunication.Client
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            builder.RootComponents.Add<App>("#app");

            builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
            builder.Services.AddSingleton<AppState>();

            await builder.Build().RunAsync();
        }
    }
}

In the Program.cs file, we register the AppState class as a Singleton service. Sharing the same instance across all components makes sure we see the same value across the entire application.

Let’s open the StateContainer page.

@page "/state-container"
@inject AppState AppState

<h3>Animals</h3>
Favorite animal: @AppState.FavoriteAnimal

<div style="display: flex; flex-direction: column; width: 200px;">
    <Lion />
    <Snake />
    <button @onclick='() => AppState.SetFavoriteAnimal("Elephant")'>
        Reset
    </button>
</div>

@code
{
    protected override void OnInitialized()
    {
        AppState.OnFavoriteAnimalChange += StateHasChanged;
        AppState.SetFavoriteAnimal("Elephant");
    }

    public void Dispose()
    {
        AppState.OnFavoriteAnimalChange -= StateHasChanged;
    }
}

We inject the AppState instance on the second line and display the favorite animal in the template section. We also have two child components in the template. First, we have the Lion component, and below, we have the Snake component.

In the code section, we override the OnInitialized lifecycle method to register the StateHasChanged method as a handler for the OnFavoriteAnimalChang event. We also set Elephant as the default value.

To avoid a memory leak, we also implement the Dispose method and unregister the StateHasChanged method from the OnFavoriteAnimalChange event.

Let’s open the Lion component.

@inject AppState AppState

<button @onclick='() => AppState.SetFavoriteAnimal("Lion")'>
    Lion
</button>

In the Lion component, we also inject the AppState instance. We have a button in the template section of the component. In the button’s onclick handler, we call the SetFavoriteAnimal method on the AppState component to change the value to Lion.

Summary

We learned about Event Callbacks, Cascading Values, and how to implement a shared service as a state container. All three options allow Blazor components to communicate with each other differently.

As always, use the option that fits your problem best, and do not try to use the same thing for every situation.

This video was inspired by the Microsoft Documentation and an article by Chris Sainty. The source code of the example project is available on GitHub.

 

Claudio Bernasconi

I'm an enthusiastic Software Engineer with a passion for teaching .NET development on YouTube, writing articles about my journey on my blog, and making people smile.