Search code examples
c#bootstrap-modalblazor-webassembly

Parameter In Child Modal Component Not Being Updated Properly


Have two pages: UsersManagement.razor and UserDeletionConfirmation.razor. UserDeletionConfirmation.razor is the modal which is being called from UsersManagement.razor. The problem is that I have a Cascading Parameter of type string in UserDeletionConfirmation.razor called Email and it is not updated properly.

When I confirm in modal my Delete action then User is deleted in database and then my Child Component and Parent are refreshed and I see changes in the table but when I press Delete button again after I deleted some user before then nothing happens.

I started to debug and learned that my Parameter Email is always stays the same (I mean it still holds email of the user I previously deleted) for some cryptic reason and this is the reason of this issue. When I fully reload my page with NavigationManager then everything works fine but I don't won't to do forceload of my parent component every time I approved my CRUD action in child modal component. How can I fix it (without reloading of the entire Parent component page) so that my Parameter Email in my child component (UserDeletionConfirmation.razor) updated properly after I used the Delete button already?

Here is my Parent Component Page code:

@page "/users-management"

<h3>Users Management</h3>

@if (ViewModel.Users != null && ViewModel.Users.Count > 0)
{
ViewModel.SequenceNumber = 0;

<table class="table table-bordered table-striped table-sm">
    <thead>
        <tr>
            <th>Seq. No.</th>
            <th>First Name</th>
            <th>Last Name</th>
            <th>Email Address</th>
            <th>Actions</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var user in ViewModel.Users)
        {
            IncrementSequenceNumber();

            <tr>
                <td>@ViewModel.SequenceNumber</td>
                <td>@user.FirstName</td>
                <td>@user.LastName</td>
                <td>@user.Email</td>
                <td class="text-right">
                    <CascadingValue Value="this">
                        <CascadingValue Value="@user.Email" Name="Email">
                            <UserDeletionConfirmation @ref="UserDeletionModalWindow"></UserDeletionConfirmation>
                        </CascadingValue>
                    </CascadingValue>
                    <button class="btn btn-danger btn-sm" @onclick="@(() => UserDeletionModalWindow.OpenModalWindow())">Delete</button>
                </td>
            </tr>
        }

    </tbody>
</table>
}

@code {
private UserDeletionConfirmation? UserDeletionModalWindow { get; set; } 

private UsersManagementViewModel ViewModel { get; set; } = new();

private void IncrementSequenceNumber()
{
    ViewModel.SequenceNumber++;
}

protected override async Task OnInitializedAsync()
{
    ViewModel.Users = await _identityService.GetAllUsersAsync();
}

internal async void RefreshState()
{
   ViewModel.Users = await _identityService.GetAllUsersAsync();

   this.StateHasChanged();
}
}

And here is my Child Modal Component:

<div class="modal @modalClass" tabindex="-1" role="dialog" style="display:@modalDisplay">
<div class="modal-dialog modal-dialog-centered" role="document">
    <div class="modal-content">
        <div class="modal-header">
            <h5 class="modal-title">User Deletion</h5>
            <button type="button" class="close" data-dismiss="modal" aria-label="Close" @onclick="() => CloseModalWindow()">
                <span aria-hidden="true">&times;</span>
            </button>
        </div>
        <div class="modal-body">
            <p>Are you sure that you want to delete user?</p>
        </div>
        <div class="modal-footer">
            <button type="button" class="btn btn-primary" @onclick="() => ConfirmDeletionAsync(Email)">Confirm</button>
            <button type="button" class="btn btn-secondary" data-dismiss="modal" @onclick="() => CloseModalWindow()">Cancel</button>
        </div>
    </div>
</div>
@if (modalWindowIsOpen == true)
{
    <div class="modal-backdrop show"></div>
}

@code {
[EditorRequired, EmailAddress, CascadingParameter(Name = "Email")]
public string Email { get; set; }

[CascadingParameter]
public UsersManagement ParentPage { get; set; }

public string modalClass = "";
public string modalDisplay = "none;";

private bool modalWindowIsOpen = false;

protected override Task OnInitializedAsync()
{
    return base.OnInitializedAsync();
}

public void OpenModalWindow()
{
    modalDisplay = "block;";
    modalClass = "Show";
    modalWindowIsOpen = true;
    StateHasChanged();
}

public void CloseModalWindow()
{
    modalDisplay = "none";
    modalClass = "";
    modalWindowIsOpen = false;
    StateHasChanged();
}

public async Task ConfirmDeletionAsync(string email)
{
    await _identityService.DeleteUserAsync(email);

    CloseModalWindow();

    ParentPage.RefreshState();
}
}

And here is the ViewModel which I use in my Parent Component (UsersManagement.razor):

  public class UsersManagementViewModel
  {
      public int SequenceNumber { get; set; } = new();

      public List<GetUserResponse> Users { get; set; } = new List<GetUserResponse>();
  }

Solution

  • I understood what I needed to do not to use NavigationManager to refresh the data in a table in razor component parent page. Callbacks and delegates are the answer and Mr. Venkat clearly explained what exactly you need to do if you encounter the same issue as I encountered. Here are the links on his awesome videos:

    1. https://www.youtube.com/watch?v=4DiDjQc7bcI&t=1s - explains how to use callbacks properly,

    2. https://www.youtube.com/watch?v=Caw5hmq4dEY&t=565s - explains how to use modal window and how to pass data to it.

    Hope that will help somebody (I struggled for two days with this).