Search code examples
.netblazorblazor-server-side

Blazor Server button refresh while waiting


I have a button like the one below on a razor server component page. The aim is that when the button is clicked, the button goes in disabled mode until the task is finished. This works on one of the pages, but I cannot manage to get it to work in other pages in the same application. The button does trigger the DoSearch() function and refreshes the content when it has finished, but it does not update itself half way through. Any ideas why please?


<button type="button" class="btn btn-primary" @onclick="DoSearch" disabled="@SearchDisabled">@SearchBtnName</button>

        protected bool SearchDisabled { get; set; } = false;
        protected string SearchBtnName { get; set; } = "Search";

        protected async Task DoSearch()
        {
            SearchDisabled = true;
            SearchBtnName = "Searching ...";
            List<string> log = new();

            try
            {
                //Do some work which takes a few seconds ....
            }
            finally
            {
                SearchDisabled = false;
                SearchBtnName = "Search";
            }

        }

I tried changing the way I code the onclick such as: @onclick="@(async() => await DoSearch())"

I tried calling StateHasChanged() and await InvokeAsync(StateHasChanged); I also tried to see what else could be different in that page, but could not figure it out.


Solution

  • Your code will only work IF do some work yields. If it's a block of synchronous code wrapped in a Task then the renderer gets no thread time to update the UI until do some work completes and you've set the status fields back to completed.

    To quote from the answer on a similar question today:

    Calling StateHasChanged doesn't render a component. It queues the component's RenderFragment on the Renderer's queue. That queue only get's serviced [by the Synchronisation Context and the component rendered] when the Renderer get's thread time to run.

    In the code below I've introduced a yield once the status fields have been updated to make sure the UI gets updated at that point.

    @page "/"
    
    <PageTitle>Index</PageTitle>
    
    <h1>Hello, world!</h1>
    
    <div class="m-2 p-2">
        <button type="button" class="btn btn-primary" @onclick="DoSearch" disabled="@SearchDisabled">@this.SearchBtnName</button>
    </div>
    
    @code {
        protected bool SearchDisabled = false;
        protected string SearchBtnName = "Search";
    
        private async Task DoSearch()
        {
            SearchDisabled = true;
            SearchBtnName = "Searching ...";
    
            // make sure we yield to update the UI
            // Do work may be a block of synchronous wrapped in a Task!
            // Some people will suggest you do await Task.Delay(1) here Take your choice
            await Task.Yield();
            //await Task.Delay(1);
    
            await this.DoWork();
    
            SearchDisabled = false;
            SearchBtnName = "Search";
        }
    
        private async Task DoWork()
        {
            // emulate real async work
            await Task.Delay(2000);
        }
    }