My Blazor component is not being re-rendered after receiving a new value for its hashset parameter from a service I have implemented. The parameter is set by an event in the service.
This is the main page where my component in question "GenericGridComponent" is located. When rendering the page, a database query is called by await Task.Run(() => DataExchange.AccessData<fcPersons>());
, which stores a hashset from a database in the DataExchange
service.
@inject DataExchange DataExchange
@rendermode InteractiveServer
<GenericGridComponent
T="fcPersonen">
</GenericGridComponent>
@code {
protected async override OnAfterRender(Boolean firstRender)
{
if (firstRender == true)
{
await Task.Run(() => DataExchange.AccessData<fcPersonen>());
}
}
}
In the GenericGridComponent
, an event is bound in OnAfterRender
, which assigns a new value to the hashset parameter Elements
by the service. The value is successfully assigned to the parameter, but this does not re-render the user interface of my component. In this case, I would like to dispense with the StateHasChanged()
method, because as far as I know, Blazor should automatically register a change to a parameter and re-render the specific content.
@typeparam T where T :class, new()
@inject DataExchange DataExchange
@using System.Reflection
@if (Elements != null)
{
<table class="table table-bordered">
<thead class="thead">
<tr>
@foreach (PropertyInfo propertyInfo in Elements.First().GetType().GetProperties())
{
<th scope="col">
<div style="width:fit-content;">
<p>@propertyInfo.Name</p>
</div>
</th>
}
</tr>
</thead>
<tbody>
<Virtualize TItem="T" Context="obj" Items="Elements">
<tr>
@foreach (PropertyInfo propertyInfo in obj.GetType().GetProperties())
{
<td>@propertyInfo.GetValue(obj)</td>
}
</tr>
</Virtualize>
</tbody>
</table>
}
@code {
protected override void OnAfterRender(Boolean firstRender)
{
if (firstRender == true)
{
DataExchange.DataChanged += DataChanged;
}
}
private void DataChanged()
{
Elements = DataExchange.GetData<T>();
}
private HashSet<T>? Elements { get; set; }
}
It was expected that the component would redraw itself due to the change in the bound property.
It was expected that the component would redraw itself due to the change in the bound property.
Running code in OnAfterRender
does exactly what is says on the tin. It's run after the component has rendered. There's no automatic state mutation detector in a component. Mutate the state after you've rendered, and you need to trigger a render manually [i.e. call StateHasChanged
] to get the component display to reflect the new state, and for any parameter changes to be detected by the Renderer and cascaded to child components.
So how do you prevent this?
Don't do mutation stuff in OnAfterRender
. Write mutation code in OnInitialized{Async}
or OnParametersSet{Async}
. StateHasChanged
will be called at the end of the process: no need for manual rendering.
A good general rule: if you need to call StateHasChanged
then your component logic is probably flawed. The main exception is event handlers where you do need to trigger a manual render if the handler mutates the component state.
The reasons are explained in the following articles:
Or search "[blazor] OnAfterRender" for more.
There's no "observer" watching a component's state and automatically triggering StateHasChanged
.
UI events - such as a button click or invoking an EventCallback
, are invoked by the renderer through the registered IHandleEvent.HandleEventAsync
. In ComponentBase
this calls StateHasChanged
automatically. Link to code
Event handlers called from DI Services and other services such as timers are invoked directly, so there's no automatic call to StateHasChanged
. You need to do it manually, and often through ComponentBase.InvokeAsync
to ensure it gets invoked on the SynchronisationContext
. Link to code
What you are talking about is an implementation of the Notification Pattern. You can see implementation examples here.