Search code examples
razorblazorsignalrblazor-server-sideblazor-webassembly

StateHasChanged not updating on Blazor server app


I am working on a Blazor Server app over a SignalR connection with an ASP.NET Core API, this code works fine in WebAssembly but for some reason it doesn't works in Blazor Server app.

I suspect the problem is that StateHasChanged() isn't making effect, since Console.WriteLine($"Company Name: {item.CompanyName}, Volumn: {item.Volume}"); is printing in console but MarketData isn't updating UI at StateHasChanged().

For more context; full code is explained here:

https://www.webnethelper.com/2022/01/aspnet-core-6-signalr-creating-real.html

But I guess it's just a common fix; but I can't find the solution.

I've also tried with InvokeAsync(() => StateHasChanged()); as mentioned here in stackoverflow but It didnt work. Can anyone help me fix this issue?

private HubConnection? hubConn;
private string? ConnectionStatusMessage;
public List<Market> MarketData = new List<Market>(); 
public List<Market> MarketReceivedData = new List<Market>(); 


private  List<string> xSource;
private  List<int> ySource;
private List<object> source; 

protected override async Task OnInitializedAsync()
{

    xSource = new List<string>();
    ySource = new List<int>();
    source = new List<object>();  

    await service.GetMarketEndpoint();
    await Start();
     
}


private async Task Start()
{
    hubConn = new HubConnectionBuilder().WithUrl("https://localhost:7193/marketdata").Build();
    await hubConn.StartAsync();
    if(hubConn.State == HubConnectionState.Connected )
        ConnectionStatusMessage = "Connection is established Successfully...";
    else
        ConnectionStatusMessage = "Connection is not established...";
}

private void MarketDataListener(string chartType)
{
    hubConn.On<List<Market>>("SendMarketStatusData", async (data) =>
    {
        MarketData = new List<Market>(); 
        foreach (var item in data)
        {
            Console.WriteLine($"Company Name: {item.CompanyName}, Volumn: {item.Volume}");
            xSource.Add(item.CompanyName);
            ySource.Add(item.Volume);
        }


        source.Add(ySource);
        source.Add(xSource);


        MarketData = data;

        StateHasChanged();

        await js.InvokeAsync<object>(chartType, source.ToArray());
        xSource.Clear();
        ySource.Clear();
    });
}

private void ReceivedMarketDataListener()
{
    hubConn.On<List<Market>>("CommunicateMarketData", (data) =>
    {
        MarketReceivedData = data;
        StateHasChanged();           
    });
}



public async Task Dispose()
{
    await hubConn.DisposeAsync();
}


async Task  generateLineChartTask()
{
    MarketDataListener("marketLineChart");
     ReceivedMarketDataListener();
    await service.GetMarketDataAsync();
}
 async Task   generateBarChartTask()
{
  MarketDataListener("marketBarChart"); 
   ReceivedMarketDataListener();
    await service.GetMarketDataAsync();
}

Solution

  • The main difference here is that Blazor Serverside is multithreaded so the callbacks from the circuit will execute on a different Thread.

    StateHasChanged() has to be executed on the main (UI) thread so call it like InvokeAsync(StateHasChanged), which is short for InvokeAsync(() => StateHasChanged()).

    And be aware of other threading risks. Ie, don't share data like Lists between threads.