Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Screen rendering issues when using IJSRuntime Blazor

First of all, I am very sorry for my English skills.

I am currently developing a web project through dotnet core 3.1 blazor.

As in the source below, I use IJSRuntime to call a Javascript function that takes a long time.

[Inject]
IJSRuntime JSRuntime { get; set; }
private async Task BtnDisplay()
    await JSRuntime.InvokeVoidAsync("RefreshWebJS", Data);
}

Javascript function takes a long time, so I added the source below to add a spinner.

private async Task BtnDisplay()
{
    showLoadingImg = true; // add
    await JSRuntime.InvokeVoidAsync("RefreshWebJS", Data);
    showLoadingImg = false;
}

A spinner is defined on the razor page as follows:

@if (showLoadingImg == true)
{
    <div style="position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; text-align: center;">
    <img src="/images/LoadingImg.gif" style="position: absolute; top: 50%; left: 50%;" />
    </div>
}

"StateHasChanged()" or "await InvokeAsync(() => StateHasChanged())" also doesn't work.

It works fine when I use Task instead of JSRuntime.

await Task.Delay(1000); 

Why doesn't it work when I use JSRuntime.InvokeVoidAsync?

Thank you and sorry for reading difficult English .

like image 795
GT Kim Avatar asked Sep 20 '25 09:09

GT Kim


2 Answers

private async Task BtnDisplay()
{
    showLoadingImg = true; // add
    await Task.Delay(1);
    await JSRuntime.InvokeVoidAsync("RefreshWebJS", Data);
    showLoadingImg = false;
}

Blazor automatically re-renders at the completion of the first Task that you await in an async task.

So, if that first Task is your long running process, it won't re-render until that finishes.

Adding await Task.Delay(1) is a simple way to allow the render before your long running process.

Further Reading : https://github.com/dotnet/aspnetcore/issues/22159

This is known feature of the way Blazor works and the creator of Blazor also recommends this approach in that thread (although he prefers await Task.Yield() - which I always forget to use!)

like image 175
Mister Magoo Avatar answered Sep 23 '25 00:09

Mister Magoo


As mentioned in a comment before, there is also an approach with a synchronous event handler, that starts the JS Interop in a new Task and returns immediately after that. You have to make sure to resynchronize this new task when it is finished, by using await InvokeAsync(StateHasChanged):

private bool ShowLoading = false;

private void HandleButtonClick()
{
    ShowLoading = true;
    Task.Run(async () => await CallLongRunningJs())
        .ContinueWith(t => { ShowLoading = false; InvokeAsync(StateHasChanged); });
}

private async Task CallLongRunningJs()
{
    await jsRuntime.InvokeVoidAsync("longRunningJsFunc", "data...");
}

It is more verbose than the approach with Task.Yield() presented by Mister Magoo, but I think it is good to mention this here for completeness.

like image 22
loe Avatar answered Sep 22 '25 22:09

loe