dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.19k stars 9.93k forks source link

Button disabled state not updated during onclick function #15170

Closed JDBarndt closed 4 years ago

JDBarndt commented 4 years ago

Describe the bug

Stumbled into this while following documentation here: https://docs.microsoft.com/en-us/aspnet/core/security/blazor/server?view=aspnetcore-3.0#guard-against-multiple-dispatches

While following the guard against multiple dispatches example in the URL above, the button is not disabled (visibly) to end-user while the forecast data is being retrieved.

To Reproduce

Steps to reproduce the behavior:

  1. Using VS 2019 16.3.5 w/ .NET Core 3.0.
  2. File -> New Project and create a Blazor App (Server) project
  3. Modify Pages/FetchData.razor according to "Guard against multiple dispatches" example Note: May be helpful to add an artificial delay to the GetForecastAsync service to prove that button is not being disabled.
  4. Run page and click button to see it's not disabled at all

Expected behavior

The button should be disabled while isLoading = true.

Additional context

This is my full source of FetchData.razor:

@page "/fetchdata"

@using BlazorApp_MSBugRepro1.Data
@inject WeatherForecastService ForecastService

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from a service.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>

    <button disabled="@isLoading" @onclick="UpdateForecasts">Update</button>
}

@code {
    private bool isLoading;
    WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
    }

    private async Task UpdateForecasts()
    {
        if (!isLoading)
        {
            isLoading = true;
            //await Task.Delay(1);
            forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
            isLoading = false;
        }
    }
}

You'll notice I left a commented line in there, await Task.Delay(1);. That is the workaround I have found in order to get the button to show as disabled while updating the forecast.

pranavkm commented 4 years ago

Does the call to GetForecastAsync largely perform work synchronously? The pattern specifically works if work's being done asynchronously. That might explain why the Task.Delay has a visible outcome.

JDBarndt commented 4 years ago

Ah, looks like you're right! I didn't realize the GetForecastAsync function is in fact not asynchronous:

public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
{
    var rng = new Random();
    return Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        Date = startDate.AddDays(index),
        TemperatureC = rng.Next(-20, 55),
        Summary = Summaries[rng.Next(Summaries.Length)]
    }).ToArray());
}

Refactoring to the below seems to have done the trick:

public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
{
    return Task.Run(() =>
    {
        var rng = new Random();

        return Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = startDate.AddDays(index),
            TemperatureC = rng.Next(-20, 55),
            Summary = Summaries[rng.Next(Summaries.Length)]
        }).ToArray();
    });
}

Looks like the default Blazor project template could use an update so those of us following the documentation don't run into this. 👍

Thanks!