Home > Net >  Blazor List of components, shift values from components when removed from list
Blazor List of components, shift values from components when removed from list

Time:05-20

I'm trying to understand this issue I'm having in blazor 3.1 and appreciate anyone who could explain it to me.

  • I have a list of items.
  • I make child components by iterating through the list.
  • Each component displays a private bool (IsSomething) value that can be toggled with a button.
  • There's a close button (EventCallback) that removes the item from the list in the parent and I can't understand why the bool values of the remaining components shift.
Ex:
[true] [false] [false]
remove 1st item (true in this example) in list gives
[true] [false]
I was expecting
[false] [false]

I would understand if all the bools would be false if the components are re-initialised or something but this behavior has me stumped.

Temporary link to github https://github.com/jeanbisson/ListComponentIssue

Index.razor

@using ListComponentIssue.Shared

<h1>List Component Issue</h1>

@if (listOfItems != null)
{
<div style="display:flex; flex-direction:row;">
    @foreach (var item in listOfItems)
    {
        <MyComponent ThisItem="@item" Close="close"></MyComponent>
    }
</div>
}

Index.razor.cs

     {
         private List<Item> listOfItems = new List<Item>();
 
         protected override void OnInitialized()
         {
             base.OnInitialized();
             listOfItems.Add(
                 new Item()
                 {
                     Id = "1",
                     Title = "First"
                 }
             );
             listOfItems.Add(
                 new Item()
                 {
                     Id = "2",
                     Title = "Second"
                 }
             );
             listOfItems.Add(
              new Item()
              {
                  Id = "3",
                  Title = "Third"
              }
          );
             listOfItems.Add(
              new Item()
              {
                  Id = "4",
                  Title = "Fourth"
              }
          );
             listOfItems.Add(
              new Item()
              {
                  Id = "5",
                  Title = "Fifth"
              }
          );
         }
 
         public void close(Item itemToRemove)
         {
             listOfItems.Remove(itemToRemove);
         }
 
     }

MyComponent.razor

    <div style="margin:5px;">
        @ThisItem.Title is @isSomething
    </div>
    <div>
        <button @onclick="toggleBool">Toggle bool</button>
        <button @onclick="() => CloseToParent(ThisItem)">close</button>
    </div>
</div>


@code {

    private bool isSomething { get; set; }

    [Parameter]
    public Item ThisItem { get; set; }

    [Parameter]
    public EventCallback<Item> Close { get; set; }

    private void toggleBool()
    {
        isSomething = !isSomething;
    }

    public async Task CloseToParent(Item itemToClose)
    {
        await Close.InvokeAsync(itemToClose);
    }

    public class Item
    {
        public string Id { get; set; }
        public string Title { get; set; }

    }
}

In another project, I use Radzen Dropdown multi select components and the selected items from components on the right shift/merge. ex.:
[selected:1] [selected:2,3]
I remove the first component and the result is
[selected:1,2,3]

Removing components on the right does not change the components on the left.
My goal is for the user to be able to add and remove components dynamically. Maybe my whole approach is wrong... any advice is appreciated

CodePudding user response:

I wonder if this is the classic c# lambda capture problem. Try

@foreach (var item in listOfItems)
{  
    var temp = item;
    <MyComponent ThisItem="@temp" Close="close"></MyComponent>
}

CodePudding user response:

use @key on your component inside the loop. This helps the render engine know what elements have been updated.

<MyComponent @key=@item ThisItem="@item" Close="close" />

Index.razor

<h1>List Component Issue</h1>

<div style="display:flex; flex-direction:row;">
    @foreach (var item in listOfItems)
    {
        <MyComponent ThisItem="@item" OnClose="OnCloseClicked" />
    }
</div>

Index.razor.cs

public partial class Index
{
    private List<Item> listOfItems;

    public Index()
    {
        listOfItems = new(5)
        {
           new() {Id = "1", Title = "First"},
           new() {Id = "2", Title = "Second"},
           new() {Id = "3", Title = "Third"},
           new() {Id = "4", Title = "Fourth"},
           new() {Id = "5", Title = "Fifth"}
        };
    }

    public void OnCloseClicked(Item itemToRemove)
        => listOfItems.Remove(itemToRemove);
}

MyComponent.razor

<div>
    <div style="margin:5px;">
        @ThisItem.Title is @isSomething
    </div>
    <div>
        <button @onclick=ToggleBool>Toggle</button>
        <button @onclick=CloseToParent>close</button>
    </div>
</div>

@code {
    private bool isSomething;

    [Parameter]
    public Item ThisItem { get; set; }

    [Parameter]
    public EventCallback<Item> OnClose { get; set; }

    private void ToggleBool() => isSomething = !isSomething;

    public async Task CloseToParent() => await OnClose.InvokeAsync(ThisItem);
}

@key Docs

  • Related