skip to Main Content

I am trying to figure out when I need to call StateHasChanged(). I know when changing the value via Javascript, and blazor component may need to be forced to render the page. In the following code, I have a method called HandleClick to handle the button click and I intentionally commentted out StateHasChanged() . The HandleClick method itself calls Javascript to set the value of element in Part 2. Since Blazor component is not aware of the change of the value for fromJs, so the value in Part 1 is not updated after the click. But the question is why the value in Part 1 gets changed when I click the button again.

@page "/ExampleY"

@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JSRuntime

<div class="p-2">
    <p class="alert-info p-3">
       Part 1.  Get value via JS call: <strong>@(fromJs ?? "Empty")</strong>
    </p>

    <p class="alert-success p-3">
        Part 2. Set value via JS call: <strong @ref="element"></strong>
    </p>

    <button class="btn btn-success" @onclick="HandleClick">Click to Call JS</button>
</div>

@code {
    private string fromJs;
    private ElementReference element;

    public async void HandleClick()
    {
        fromJs = await JSRuntime.InvokeAsync<string>("setText", element, "Hello from JS call!");
        // StateHasChanged();
    }

}

2

Answers


  1. This has nothing to do with JavaScript Interop.

    When you make this change:

    //public async void HandleClick()
      public async Task HandleClick()
    

    then both parts will update at the same time.

    Almost always avoid async void. It is a last resort for old-style eventhandlers like Timer.Elapsed. Blazor events can return either void or [async] Task.

    the question is why the value in Part 1 gets changed when I click the button again.

    A ButtonClick will always cause a re-render. But because of the async void this can happen before fromJs is assigned. The await in await JSRuntime.InvokeAsync(...) allows that. On the next click you will see the previous value of fromJs appear in Part 1.
    Use "Hello from JS call! "+DateTime.Now to see this versioning happen.

    Login or Signup to reply.
  2. The Blazor "internal" event handler code looks like this:

    var task = InvokeAsync(EventMethod);
    StateHasChanged();
    if (!task.IsCompleted)
    {
        await task;
        StateHasChanged();
    }
    

    If you pass task a void as in:

    public async void HandleClick()
    

    there’s nothing to wait on. task is completed and the final render event never takes place. That’s why any async handler (i.e. a method with an await), as the one above, must return a Task.

    So to answer your questions. The value in Part 1 is updated, but after the re-render of the component – no Task to wait on. When you click the button again you show the "last" value, i.e. the one from the last button click, not the current click. If you set the text to the click time you would see this.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search