Search code examples
c#.netrazordata-bindingblazor-webassembly

Blazor WASM UI updates inconsistent


I have a simple login function in my app. When login starts, a boolean is set to true to activate a loading animation, and upon completion it is set to false to hide it. The problem is the UI does not react to the second part, but does react to first.

This is the function:

private async void AttemptLogin()
{
    //This updates
    LoginInProgress = true;
    try
    {
        var result = await _loginPageViewModel.Login(Username, Password, exception => { Console.WriteLine(exception.Message); }); //TODO add notifier
        if (result)
        {
            Console.WriteLine("success");

            _navigationManager.NavigateTo("./");
        }
        else
        {
            Console.WriteLine("Failure");
        }

    }
    catch (Exception e)
    {
        Console.WriteLine(e);
        throw;
    }
    finally
    {
        //This does not update
        LoginInProgress = false;
    }
    Console.WriteLine("Login attempt finished");
}

I am using blazorise as a component framework, this is how the value is bound (+ an extra testing checkbox, checking and unchecking updates everything as expected):

<Button Clicked="AttemptLogin" @bind-Loading="LoginInProgress">Login</Button>
<Check TValue="bool" @bind-Checked="LoginInProgress">Test</Check>
     

For now, StateHasChanged at the LoginInProgress = false; seems to work, but I do not like that I have to put it at some places and not others. What am I doing wrong?


Solution

  • Blazor will perform an internal "StateHasChanged" re-render of current component upon the completion of any event handlers in the component.

    So, under normal circumstances having caught the "Clicked" event and handled it in "AttemptLogin" method then Blazor engine would re-render after AttemptLogin has completed and take the variable "LoginInProgress = false" into account. No need to manually call "StateHasChanged.

    However, as your method is Async, then the engine will wait for completion. Normally, you'd return a Task that the engine will hold onto and wait until completion of that Task before performing the re-rendering.

    In your case, you're returning a void instead of a Task. The result is that as soon as your AttempLogin method calls it first await, then control is returned to the caller (the Blazor engine) and it will consider the method complete.

    var result = await _loginPageViewModel
        .Login(Username, Password, exception => 
         { Console.WriteLine(exception.Message); });
    

    If you'd returned a Task, it would wait until completion of the method (a result is provided by the Task), but because you return a void, then engine expects no further response from the method and starts re-rendering immediately. This re-rendering will happen before this line is executed:

    LoginInProgress = false;
    

    Hence, the re-rendering will be applied whilst LoginInProgress is still true.

    Change your return type to Task and the re-rendering will be delayed until the event handler is complete.