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">×</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>();
}
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:
https://www.youtube.com/watch?v=4DiDjQc7bcI&t=1s - explains how to use callbacks properly,
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).