Skip to content

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

@arivoir

Description

@arivoir

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.

BlazorApp8.zip

Metadata

Metadata

Assignees

No one assigned

    Labels

    Pillar: Complete Blazor WebPriority:1Work that is critical for the release, but we could probably ship withoutaffected-fewThis issue impacts only small number of customersarea-blazorIncludes: Blazor, Razor ComponentsenhancementThis issue represents an ask for new feature or an enhancement to an existing onefeature-blazor-component-modelAny feature that affects the component model for Blazor (Parameters, Rendering, Lifecycle, etc)reevaluateWe need to reevaluate the issue and make a decision about itseverity-majorThis label is used by an internal tool

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions