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:
- When the set of parameters of a component is updated by its parent component.
- When a cascading value is updated.
- When an event on the component is raised or an external EventCallback that the component is subscribed to is invoked.
- 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.