Automatic Component State Management in Blazor using INotifyPropertyChanged and INotifyCollectionChanged

In Blazor, changes to the state of a component become visible to the user when the component re-renders. Blazor has some conventions to make a component automatically re-render when it thinks there is a chance for the component’s state to be changed. However these conventions might sometimes fail to detect changes that actually require a re-render.

According to Microsoft docs, Blazor considers the following situations as the sources of state-changes and will re-render the component accordingly:

  1. When the set of parameters of a component is updated by its parent component.
  2. When a cascading value is updated.
  3. When an event on the component is raised or an external EventCallback that the component is subscribed to is invoked.
  4. When `StateHasChanged` method of the component is explicitly called.

In simple scenarios, the first 3 conventions will be enough to keep your components reactive. But let your models and the relationship between your components get complicated, and you will most often than not find yourself explicitly calling StateHasChanged to make your components re-render.

A common source of a state-change is a modification to a variable that is used (through binding) to render the component in the most recent render cycle. A variable can be a member (field or property) of the component (possibly a parameter), a nested member of direct members of the component or an item of a collection member of the component. Some UI frameworks already have the mechanisms to detect such modifications and will automatically make the component re-render (e.g. the reactivity system of Vue.js does so by replacing data of a component with proxies that are able to notify the component when they are changed, bindings in WPF/Xamarin.Forms are able to track changes by subscribing to INotifyPropertyChanged and INotifyCollectionChanged instances present in the binding path). But Blazor does not react to such changes if the source of the change does not fall into one of the conventions explained above.

Consider the following sample component:

Name: @(Person.Name)

<ul>
  @foreach (var skill in Person.Skills)
  {
    <li @key="skill">@(skill.Title)</li>
  }
</ul>

<ChildComponent Person="@Person" />

@code {
  [Parameter] public Person Person { get; set; }
}

Here if the child component changes Person.Name, adds a skill to Person.Skills, or changes the title of an existing skill, our component has no way to realize that it should re-render to reflect the changes, unless we modify the child component or our model class to make them notify our component when Person.Name is changed, Person.Skills collection is modified, or skill.Title is changed for all of the existing skills in Person.Skills and we modify our component to let Blazor know that the state has changed (by calling StateHasChanged) as the result of these notifications. Just imagine the amount of boilerplate code we need to write for every variable that the rendering of our component relies on!

Fortunately, the infrastructure to let an object notify its changes is already available in .NET. The key is to make Person and Skill classes implement INotifyPropertyChanged and raise PropertyChanged event when Person.Name and Skill.Title get changed and to use a collection that implements INotifyCollectionChanged (like ObservableCollection<T>) as the value of Person.Skills.

In Xaml-based frameworks you can use these two interfaces in your models and the framework will take care of handling the change events to update the UI. Unfortunately, Blazor is ignorant about these interfaces. Phork.Blazor.Reactivity is a library -which I’m the author of- that brings the support of these two interfaces to Blazor!

Using Phork.Blazor.Reactivity, we can apply the following simple changes to the above component to make it reactive! (Assuming our models are modified to implement INotifyPropertyChanged and use ObservableCollection<T> correctly and Phork.Blazor.Reactivity is installed and configured in our project)

@inherits ReactiveComponentBase

Name: @Observed(() => Person.Name)

<ul>
  @foreach (var skill in ObservedCollection(() => Person.Skills))
  {
    <li @key="skill">@Observed(() => skill.Title)</li>
  }
</ul>

<ChildComponent Person="@Person" />

@code {
  [Parameter] public Person Person { get; set; }
}

With Phork.Blazor.Reactivity, you can take advantage of INotifyPropertyChanged and INotifyCollectionChanged to create reactive components. The library will take care of watching changes for you. It doesn’t end here, there are more features available in the library that you can learn about in its documentation.

Find out more:

🎯 Bonus Tip: If you plan to use MVVM in your Blazor application, using Phork.Blazor.Reactivty will give you the luxury of reusing your existing cross-platform view models from your WPF/Xamarin.Forms applications with less effort.