I have a dynamic RadzenTabs component that when responding to a list item removal renders everything correctly except for a child component within the tab pane that doesn't rebind.
two tabs
close first tab, tab and WorkItem header behave as expected. Child component doesn't rebind
I've tried many, many things. I'm using OnPropertyChanged
eventing and have tried to capture the RadzenTabsItem in my template and then using tabs.RemoveItem()
. I've tried tabs.Reload()
and removed all of it and just left it to a StateHasChanged
call after setting the underlying collection that drives the tabs. I'm trying to avoid calling my Refresh to reload the pane, but clicking my button to Refresh does set the content correctly.
Got it, had to add @key=...
on my child component
opened 03:13PM - 04 Aug 19 UTC
closed 04:08PM - 04 Aug 19 UTC
### Describe the bug
When you try to remove child components (added via @foreac… h), the view state is lost and can no longer be removed due to reference loss, even so calling StateHasChanged method.
### To Reproduce
Steps to reproduce the behavior:
**1. Create a Blazor client side project and add below code in Startup.cs**
```
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(new ViewModels.IndexViewModel());
}
```
**2. Create the ViewModels folder and add IndexViewModel.cs ad ItemViewModel.cs**
**3. In the Shared folder create a component named **Item** that has a ViewModel property and a Remove Me command to available through an HTML button.**
**Code To: Item.razor**
```
@using ViewModels
<div style="width:320px;height:120px;border: solid 1px black;
margin-left: 10px; margin-top:10px;padding: 5px;
background-color: gainsboro;">
<p>
<label>@ViewModel.Id</label>
</p>
<button type="button" onclick="@removeMeCommand" style="width:60px; height:60px">
<h1>X</h1>
</button>
</div>
@functions
{
[Parameter] ItemViewModel ViewModel { get; set; }
private EventCallback removeMeCommand;
protected override void OnInit()
{
removeMeCommand = EventCallback.Factory.Create(this, ViewModel.RemoveMe);
base.OnInit();
}
}
```
**Code To: ItemViewModel.cs**
```
public class ItemViewModel
{
private readonly IndexViewModel _container;
public string Id { get; set; } = Guid.NewGuid().ToString();
public ItemViewModel(IndexViewModel container)
{
_container = container;
}
public override bool Equals(object obj)
{
if (!(obj is ItemViewModel instance)) return false;
return instance.Id.Equals(Id);
}
public override int GetHashCode()
{
var hashCode = -1392864732;
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Id);
return hashCode;
}
public void RemoveMe()
{
_container.RemoveItem(this);
}
}
```
**4. In the Index.razor file add a list of this new component using @foreach**
**Code To: Index.razor**
```
@page "/"
@using ViewModels
@inject IndexViewModel ViewModel
<div>
<div style="">
<p>
<button onclick="@addCommand">Add Block</button>
</p>
<br />
</div>
@foreach (var item in ViewModel.Items)
{
<Item ViewModel="item"></Item>
}
</div>
@functions {
private EventCallback addCommand;
protected override void OnInit()
{
addCommand = EventCallback.Factory.Create(this, ViewModel.AddNew);
ViewModel.StateChanged += this.StateHasChanged;
if (ViewModel.Items.Count== 0)
{
for (int i = 0; i < 8; i++)
{
ViewModel.AddNew();
}
}
}
}
```
**Code To: IndexViewModel.cs**
```
public class IndexViewModel
{
public event Action StateChanged;
public List<ItemViewModel> Items { get; } = new List<ItemViewModel>();
public void RaiseStateChanged()
{
this.StateChanged?.Invoke();
}
public void AddNewWithouLog()
{
var item = CreateItem();
Items.Add(item);
}
private ItemViewModel CreateItem()
{
var viewModel = new ItemViewModel(this);
return viewModel;
}
public void RemoveItem(ItemViewModel itemViewModel)
{
Console.WriteLine("COUNT before remove: " + Items.Count);
Console.WriteLine();
Items.Remove(itemViewModel);
RaiseStateChanged();
Console.WriteLine("Id should removed: " + itemViewModel.Id);
Console.WriteLine();
Console.WriteLine("COUNT after remove: " + Items.Count);
Console.WriteLine();
Console.WriteLine("List items: ");
foreach (var i in Items)
{
Console.WriteLine(i.Id);
}
Console.WriteLine("================================================");
Console.WriteLine();
}
}
```
5. Press the Remove Me button on one of the items.
### Expected behavior
Child components should be removed. But only a few are. Then the state view gets lost and it ends up using a reference from another object.
If you check your browser console (see screenshot), you'll notice that the component ID we're trying to remove is no longer in the list.
### Screenshots

### Additional
Visual Studio: 2019
Blazor: 3.0.0-preview7.19365.7
Git with project sample: https://github.com/gilbertogwa/blazorbug
This talks about the features in the asp-net-core-and-blazor-updates-in-net-core-3-0-preview-6 which refers to @key
being useful in a list of components. Which isn't entirely germain to my problem. I have the @key
on my list of tab items. But this trick fixed my problem