dotnet / dotnet/aspnetcore

Add StateHasChanged(async: true) that guarantees never to run synchronously

Open
#22,159 58 comments 16 reactions 0 assignees View on GitHub
affected-few area-blazor enhancement feature-blazor-component-model Pillar: Complete Blazor Web Priority:1 reevaluate severity-major
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 5h
Merged PRs (30d)
276

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 "/"

Update Component (From Blazor)
@code {

Component1 component1;

private void UpdateComponent()
{
component1.UpdateTheComponent();
}
}
```
and the component code is the following

```

Component1

@{
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

Component1

Update Component (From JS)

@{
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.

[BlazorApp8.zip](https://github.com/dotnet/aspnetcore/files/4670113/BlazorApp8.zip)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.