Blazor is an awesome UI framework which brings the power and ease of .NET to your browser. At certain points Blazor does automatically render your UI, but there are also some "pitfalls" you have to be aware about. As a small disclaimer I assume that your components derive from ComponentBase
which is the default when you create "normal" Blazor components. From a technical point of view IComponent
would satisfy the Blazor renderer, but if you use this, I think you already know what will come next.
Let's begin with the obvious candiate.
StateHasChanged
StateHasChanged
is a protected
method, which basically tells the Blazor renderer: "Hey please redraw my compoennt". This can be for example very handy in conjunction with timers or incomplete asynchronous tasks. The latter one will be addressed later in that article.
Please be aware that StateHasChanged
has to be called from the UI thread otherwise you will get an exception. On Blazor WASM there is currently only one thread available, but still it is good to use InvokeAsync
to guarantee that StateHasChanged
is executed on the right context.
Timer = new System.Threading.Timer(_ =>
{
InvokeAsync(StateHasChanged);
}, null, 500, 500);
OnAfterRender
This method is called after the Blazor renderer has done all the other lifecycle events. As the name suggests this lifeycle method is called after the last render. That means if you update some parameter, which should be reflected in the UI, though luck with this one. Blazor will not render anything here. If you want to render something in OnAfterRender
you can utilize our friend StateHasChanged
.
async
stuff
A lot of the lifecycle events are async
(OnInitializedAsync
), as well as event handlers (MyEvent.InvokeAsync(someProp)
). Asynchronous stuff is a bit special. Everytime on the first await boundary the Blazor pipeline will trigger a re-render and after the Task is complete. On the Microsoft page, which I will link in the resources below, you can see this for OnInitizaliedAsync
and OnParamtersSetAsync
:
As well as for event handlers:
Now what does that mean in code? Just imagine the OnInitializedAsync
method:
protected async Task OnInitializedAsync()
{
var userName = SomeSyncOperation();
// This is the first await
// after the Task started and we give back the control to the caller Blazor will render
var employees = await employeeService.GetByUsernameAsync(userName);
var tranformed = transformer.Transform(employees);
// We are done and Blazor renders again
}
All the time I explicitly stated after the first await bounday and after the task is done. So if you have the following code, this will still only 2 render cycles:
<ProgressBar Progess="@progress"></ProgressBar>
@code {
private int progress = 0;
protected async Task OnInitializedAsync()
{
progress = 10;
// Here the UI will update the progress from 0 to 10
var user = await GetUserByIdAsync(Id);
// This will not be reflected on the UI
progress = 25;
var games = await GetHighScoreFromUserAsync(user);
// Still not
progress = 75;
someObject = await DoAnotherasync(games);
progress = 100;
// After we are dine here the UI will jump from 10 to 100
}
}
The question is now: Why does it change only after the first time and after we are completely done? The first time is pretty easy to answer. Before we hit any await
boundary the UI-Thread is occupied by our OnInitializedAsync
. Only when we hit the await we (the OnInitializedAsync
method) give back control to the caller. So that makes sense to a certain degree, but why not after the second or third await
?. And the reason is that Blazor has no means to detect how "far" your method has run until now. Any Task has a TaskStatus
property which tells the current state of the task. And this state will not change after the first await boundary (except of course you have an exception). It only changes when your task ran into completion. So summarized: Everytime your TaskStatus
changes... which does not happen on every await
encounter.
So how to fix that? And the answer is again: StateHasChanged
. In terms off the example given above:
<ProgressBar Progess="@progress"></ProgressBar>
@code {
private int progress = 0;
protected async Task OnInitializedAsync()
{
progress = 10;
// Here the UI will update the progress from 0 to 10
var user = await GetUserByIdAsync(Id);
progress = 25;
// Force a re-render
StateHasChanged();
var games = await GetHighScoreFromUserAsync(user);
progress = 75;
// Force a re-render
StateHasChanged();
someObject = await DoAnotherasync(games);
progress = 100;
// After we are dine here the UI will jump from 10 to 100
}
}
I created an example on BlazorFiddle where you can see this live in action.
ShouldRender
Last but not least there is also a property called ShouldRender
and it does exactly what it says. If you set it to false
no re-render will be triggered.
That might be very helpful if you have a batch of events and you don't want to re-render every single item.
A small example: Just imagine you get a list of items one by one. You know exactly how many items you expect, so there is no need to render everytime you get one item. You will only render if you received all the items.
Parameters
If you have a typical parent child combination like this:
<ChildComponent Value="@myValue"></ChildComponent>
Updating myValue
will lead to a new render cycle for the parent and the child. If the child component internally changes the passed myValue
the parent will not update. The reason is that those binding are one-way bindings. That said if somehow your child component influences your parent component most probably you need to call StateHasChanged
in your child component. An example would be modal dialogs, depending the way they are implemented.
If you use two-way binding, this looks of course different.
<ChildTwoWayBind @bind-Value="myValue"></ChildTwoWayBind>
Of course they same as for "normal" aka one-way binding applies: The parent and the child get re-rendered. The special case is that even if the child changes the value, the parent re-renders as well. Internally there are event handlers working, basically applying the logic we discussed earlier. More about the bind-directive can be found on the Microsoft site.
Another special form is the CascadingParameter
. Everytime the cascading value changes all child components using that parameter will also be re-rendered. That is why it makes it also quite expensive. First all the events have to be activated and then all the children have to re-render. If you only need the initial value of a cascading value and never interested in updates of such you can provide the IsFixed
value. That will cause no re-renders and lighter components as the event chaining can be omitted:
<CascadingValue Value="@parentCascadeParameter1" Name="CascadeParam1" IsFixed="true">
...
</CascadingValue>
Conclusion
Blazor has a lot of predefined lifecycle events where render cycles are automatically invoked. But also there are some scenario which are not automatically covered.
Resources
- A in-depth documentation can be found on the Microsoft page