Description
The StateHasChanged method is supposed to flag the component to be re-rendered, so if you call this method multiple times from the same call, it should render the component only once.
Actually, this is working ok when the call is performed from a Blazor event callback.
If I have a component names Component1 and the following markup in Index.razor
@page "/"
<Component1 @ref="component1" />
<button class="btn btn-primary" @onclick="UpdateComponent">Update Component (From Blazor)</button>
@code {
Component1 component1;
private void UpdateComponent()
{
component1.UpdateTheComponent();
}
}
and the component code is the following
<h3>Component1</h3>
@{
System.Diagnostics.Debug.WriteLine("ComponentRendered");
}
@code {
public void UpdateTheComponent()
{
for (int i = 0; i < 100; i++)
{
StateHasChanged();
}
}
}
The text written in the output of visual studio is
"ComponentRendered"
Only one time.
If instead of calling the UpdateTheComponent() method from the Blazor button handler, it is called from JavaScript, the component is updated multiple times.
To call the UpdateTheComponent() method from javascript, I will alter the component to pass the component reference to a JavaScript method.
@inject IJSRuntime JS
<h3>Component1</h3>
<button @ref="button" class="btn btn-primary" onclick="window.Component1.updateComponent(this)">Update Component (From JS)</button>
@{
System.Diagnostics.Debug.WriteLine("ComponentRendered");
}
@code {
ElementReference button;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var ComponentReference = new Component1Reference(this);
await JS.InvokeVoidAsync("Component1.init", button, DotNetObjectReference.Create(ComponentReference));
}
}
public void UpdateTheComponent()
{
for (int i = 0; i < 100; i++)
{
StateHasChanged();
}
}
public class Component1Reference
{
private Component1 Component1;
internal Component1Reference(Component1 scrollViewer)
{
Component1 = scrollViewer;
}
[JSInvokable]
public void UpdateTheComponent()
{
Component1.UpdateTheComponent();
}
}
}
and the scripts.js javascript having the following
window.Component1 = {
init: function (elementReference, componentReference) {
elementReference.Component1 = componentReference;
},
updateComponent: function (element) {
element.Component1.invokeMethodAsync('UpdateTheComponent');
}
}
When I press the button, the javascript obtains the component reference and call to the Blazor method one time.
But this time, the component is rendered multiple times
ComponentRendered
ComponentRendered
ComponentRendered
ComponentRendered
ComponentRendered
ComponentRendered
ComponentRendered
ComponentRendered
ComponentRendered
ComponentRendered
ComponentRendered
In many scenarios this causes dramatic performance degradation, as the rendering is executed multiple times unnecessarily.