1

Trying to shorten things down, one button has this code (takes the first vacant slot and updates the DB as not vacant):

@onclick="@(e => UpdateTimeSlot(@FirstVacantSlot, "Booked", BookerGuid))"

and the other button has this code (user picks a vacant spot):

@onclick="@(e => UpdateTimeSlot(@selectedItem, "RowBooked", BookerGuid))"

Calling after update:

protected override async Task OnParametersSetAsync()
{
    await RefreshDb(); // get list of todays timeslots from DB 
    StateHasChanged(); // well, Blazor magic refresh or whatever
}

FirstVacantSlot is set from a method that picks it from the DB. selectedItem is set when clicking a row in a (MudBlazor) table. Both get filled with the same type of data and send to the method UpdateTimeSlot. Both buttons correctly updates the DB, but the table refreshes ONLY after use of the first button, not after the second button. Why? Also, if I click the second button and then the first button the update no longer works there either.

Things I tried:

  • Debugged to see that the objects have identical values populated. Trying to follow whats happening.
  • StateHasChanged() in every corner of the code.
  • await this.OnParametersSetAsync() (Which makes the first button work)

Add; The UpdateTimeSlot method:

I substituted a call to RefreshDb() with the OnParameterAsync(). Still can't understand why it beahves differently depending on which button I click.

private async Task UpdateTimeSlot(TimeSlotDto vacantSlot, string title, Guid bookerId)
{
    var updateTimeSlot = new TimeSlotDto();
    updateTimeSlot.ID = (Guid)vacantSlot.ID;
    updateTimeSlot.TimeSlotStart = vacantSlot.TimeSlotStart;
    updateTimeSlot.TimeSlotEnd = vacantSlot.TimeSlotEnd;
    updateTimeSlot.IsVacant = false;
    updateTimeSlot.CreatedUTC = vacantSlot.CreatedUTC;
    updateTimeSlot.Title = title;
    updateTimeSlot.UpdatedUTC = DateTime.UtcNow;
    updateTimeSlot.BookerId = bookerId;
    await _http.PutAsJsonAsync($"https://localhost:44315/api/v1/TimeSlot/Update?id={updateTimeSlot.ID}", updateTimeSlot);

    //await this.OnInitializedAsync();

    await this.OnParametersSetAsync();
}

UPDATE The problem has been located to the MudBlazor component Table, which isn't updating correctly after selecting a row in the table itself. Everything else is working as it should; I tried a foreach-loop to iterate through the IENumerable currentTimeSlots, that the MudTable also is dependent on and the foreach-loop updated correctly. So I guess it's a completely different question now.

Update - RefreshDb()

private async Task RefreshDb()
    {
        var roomClient = new RoomClient();

        room = await roomClient.GetRoomByIdAndDateAsync(ThisRoomId, dtToday);

        allTimeSlotsOfToday = room.TimeSlots;
        currentTimeSlots = allTimeSlotsOfToday.GetAllCurrentTimeSlots(dtToday);
        HasItems = currentTimeSlots.Count();
        FirstVacantSlot = allTimeSlotsOfToday.GetFirstVacant(dtToday);
        AnyVacantSlot = allTimeSlotsOfToday.Count(f => f.IsVacant);
    }

Update: For some this might help: MudBlazor MudTable issue on GitHub

Nicolas Lewentorp
  • 99
  • 1
  • 1
  • 10
  • 1
    If you are adding `StateHasChanged()` all over the place then there's some fundamental things wrong with your code you haven't shown us - possibly `UpdateTimeSlot`. There's no need for `StateHasChanged `in `OnParametersSetAsync`. It's all built in. You need to show us more code – MrC aka Shaun Curtis Nov 02 '21 at 22:25
  • I took out all the StateHasChanged() again and as you said it doesn't make much difference. There must be something with the object taken from the MudBlazor Table because that's the only difference between the two buttons. – Nicolas Lewentorp Nov 04 '21 at 07:43
  • The problem is still not in focus. Add (an outline of) RefreshDb(). – H H Nov 04 '21 at 11:43

3 Answers3

1

A bit of a trick to get the thread released is to add a Task.Delay. You shouldn't need to call OnParametersSetAsync (seems a bit kludgy).

Change your onclicks to something like:

@onclick="@(async () => await UpdateTimeSlotAsync(@FirstVacantSlot, "Booked", BookerGuid))"

Then an async handler:

private async Task UpdateTimeSlotAsync(...)
{
    // update slot code...
    await RefreshDb(); // get list of todays timeslots from DB 
    StateHasChanged(); // well, Blazor magic refresh or whatever
    await Task.Delay(1); // let UI refresh
}
Steve Greene
  • 12,029
  • 1
  • 33
  • 54
  • [Polite] If `await RefreshDb` yields then the Blazor event handler will run a `StateHasChanged` on the first yield and a second `StateHasChanged` once `UpdateTimeSlotAsync` completes. If so, your code achieves nothing. If `await RereshDb` doesn't yield, then it runs to completion before your delay, again achieving nothing. Also you don;t need the `(async () => await...`, your just wrapping a Task inside a Task. I suspect an issue with `UpdateTimeSlotAsync` such as it uses an `async void `pattern. – MrC aka Shaun Curtis Nov 02 '21 at 22:41
  • See this answer - https://stackoverflow.com/questions/69784542/how-to-ensure-the-blazor-renderer-recognises-a-collection-change/69786057#69786057 – MrC aka Shaun Curtis Nov 02 '21 at 22:47
  • @MrCakaShaunCurtis Thanks for the info. My trick derived from Henk's 2.2 answer here: https://stackoverflow.com/questions/56604886/blazor-display-wait-or-spinner-on-api-call – Steve Greene Nov 03 '21 at 14:34
  • 1
    Henk is correct for the situation where you want to update the display with a waiting/spinner message quickly. Calling either `Task.Delay(1)` or `Task.Yield()` prior to the main Task will yield control back to the UI, so it can actually service the Renderer Queue and do the render of the waiting/spinner. Calling `StateHasChanged` only queues a re-render, it doesn't actually run it! You don't need to call `StateHasChanged` as it gets called by the Blazor event handler on the yield. – MrC aka Shaun Curtis Nov 03 '21 at 18:54
  • 1
    The only time you need to call StateHasChanged is if you are stepping through a process with several awaits and you want to display a different message on each step. – MrC aka Shaun Curtis Nov 03 '21 at 18:55
1

Firstly, never call OnInitialized{Async} or OnParametersSet{Async} manually. They are part of the ComponentBase infrastructure.

Try this:

You're calling await RefreshDb(); which I assume contains the code to do the refresh.

Update OnParametersSetAsync to:

protected override async Task OnParametersSetAsync()
{
    await RefreshDb(); // get list of todays timeslots from DB 
    // StateHasChanged(); not required if RefreshDb is coded correctly.
}

UpdateTimeSlot should look like this:

private async Task UpdateTimeSlot(TimeSlotDto vacantSlot, string title, Guid bookerId)
{
    ....
    await _http.PutAsJsonAsync($"https://localhost:44315/api/v1/TimeSlot/Update?id={updateTimeSlot.ID}", updateTimeSlot);
   await RefreshDb(); 
    // StateHasChanged(); if this fixes the problem then RefreshDb is not coded correctly.
}

You need to ensure RefreshDB is a proper async method that returns a Task that can be awaited, and any sub method calls are also awaited correctly. If a call to StateHasChanged at the end of UpdateTimeSlot fixes the problem then RefreshDb has an await problem.

Update your question with the RefreshDb code if necessary.

MrC aka Shaun Curtis
  • 19,075
  • 3
  • 13
  • 31
1

Mudtable is not updating it's content when you are in EditingMode. I could solve my StateHasChanged Issues with calling

mudTable.SetEditingItem(null);