Ted
Ted

Reputation: 29

Blazor StateHasChanged method exception

Currently migrating blazor webassembly project to blazor server for performance improvements

In Razor page exisiting code where StateHasChanged() method calls is throwing below runtime exception in browser console: "Error: System.InvalidOperationException: The current thread is not associated with the Dispatcher. Use InvokeAsync() to switch execution to the Dispatcher when triggering rendering or component state."

Tried below options but UI page is not getting updated accordingly

  1. await InvokeAsync(StateHasChanged)
  2. await InvokeAsync(() => StateHasChanged())

StateHasChanged() has been invoked in UI grid filters, button clicks, cascading dropdowns, etc

Please help in resolving this issue. In below code when user selects first dropdown (country) value, second dropdown (cities) data is fetched from backend service based on countryId

    <InputSelect id="country" ValueExpression="@(() => country.Id)" Value="@country.Id" 
        ValueChanged="@((int? args) => { country.Id = args; GetCities(args); })" 
        class="form-control" style="appearance:auto;">
      <option value="">-- Select Country --</option>
      ...
    </InputSelect>

    <InputSelect id="city" ValueExpression="@(() => city.Id)" Value="@city.Id" 
        ValueChanged="@((int? args) => { city.Id = args; FetchDistricts(args); })" 
        class="form-control" style="appearance:auto;">
      <option value="">-- Select City --</option>
      @foreach (var city in cities) 
      { 
      ... 
      }
    </InputSelect>

@code
{
public List<city> cities = new List<city>(); 

private async void GetCities(int? selectedvalue) 
 {    
 cities = await AppService.GetCitiesById(selectedvalue);
 this.StateHasChanged(); 
 }

}

Upvotes: 0

Views: 1355

Answers (1)

MrC aka Shaun Curtis
MrC aka Shaun Curtis

Reputation: 30310

There are four common circumstances in which the error you have reported occurs:

  1. You are invoking a block of code [containing StateHasChanged] to run on a threadpool thread using Task.Run().
  2. You have assigned a block of UI code to an event handler, and the owner of that event is running in a different thread context.
  3. You have provided a block of UI code as a callback or delegate to an object that is running in a different thread context.
  4. You've called an async method that is running on a different thread and using ConfigureAwait(false), where the continuation contains a block of UI code.

I don't see any of those here, so the problem is elsewhere. There are several candidates in the code you've shown us, but not provided details on: FetchDistricts for instance.

Are you using ConfigureAwait(false) anywhere in your code?

1, 2, and 3 are fixed by calling this.InvokeAsync(StateHasChanged).

4 is fixed by not doing it. Only veer from the straight Async and Await if you're an async expert and know what you're doing.

I suggest you refactor you code to move everything into GetCities. You can then step through the code and see where the error occurs

// Must be task based to avoid UI problems
private async Task GetCities(int? selectedvalue) 
 {
    // Not sure how you are dealing with nulls?
    city.Id = selectedValue;     
 cities = await AppService.GetCitiesById(selectedvalue);
 // Not needed
 //this.StateHasChanged(); 
 }
   <InputSelect id="country" ValueExpression="@(() => country.Id)" Value="@country.Id" 
       ValueChanged=this.GetCities 
       class="form-control" style="appearance:auto;">
       @if(country.Id is null)
       {
             <option value="" selected disabled>-- Select Country --</option>
       }
       // do foreach loop
     ...
   </InputSelect>

Upvotes: 1

Related Questions