Skip to content

bug(MultiSelectGeneric): update value when bind-Value changed #6504

@ArgoZhang

Description

@ArgoZhang

Discussed in #6499

Originally posted by rabauss July 28, 2025
I'm working on a project where I've attempted to create a custom reusable multiselect component (MultiSelectProductCategories). My goal is to allow items to be added both through the multiselect UI and programmatically via external buttons. However, I'm encountering an issue where adding items externally does not reflect in the UI as expected. Here is the current setup and problem:

Setup

  1. Razor Page/Component:
    • I use a MultiSelectProductCategories component with two-way binding to a list of selected categories.
    • I've added utility buttons to add or remove a specific item ("Electronics") from the list.
<MultiSelectProductCategories @bind-Value="selectedCategories" />

<p>Selected Categories:</p>
<ul>
    @foreach (var category in selectedCategories ?? [])
    {
        <li>@category.Name</li>
    }
</ul>

<Button OnClick="@AddElectronics" Color="Color.Success">Add Electronics</Button>
<Button OnClick="@RemoveElectronics" Color="Color.Danger">Remove Electronics</Button>

@code {
    private List<ProductCategory>? selectedCategories = null;

    private void AddElectronics()
    {
        var electronics = new ProductCategory { Id = 1, Name = "Electronics" };
        selectedCategories ??= new List<ProductCategory>();
        if (!selectedCategories.Contains(electronics))
        {
            selectedCategories.Add(electronics);
            StateHasChanged();
        }
    }

    private void RemoveElectronics()
    {
        if (selectedCategories != null)
        {
            var electronics = selectedCategories.FirstOrDefault(cat => cat.Name == "Electronics");
            if (electronics != null)
            {
                selectedCategories.Remove(electronics);
                if (!selectedCategories.Any())
                {
                    selectedCategories = null;
                }
                StateHasChanged();
            }
        }
    }
}
  1. MultiSelect Component Logic:
    • I am using a MultiSelectGeneric component inside MultiSelectProductCategories with two-way binding and Items list setup.
    • I’ve implemented the ValueChanged event callback and bind it to the outer component.
<MultiSelectGeneric DisplayText="Types"
                    ShowLabel="true"
                    Items="_items"
                    @bind-Value="_value"
                    ShowSearch="true"
                    IsClearable="true"
                    ShowToolbar="true"
                    OnSearchTextChanged="HandleOnSearchTextChanged"
                    OnSelectedItemsChanged="HandleOnSelectedItemsChanged" />

@code {
    private List<ProductCategory>? _value = null;
    private IEnumerable<SelectedItem<ProductCategory>> _items = Enumerable.Empty<SelectedItem<ProductCategory>>();

    [Parameter] public Func<IEnumerable<SelectedItem<ProductCategory>>?, Task>? OnSelectedItemsChanged { get; set; }
    [Parameter] public EventCallback<List<ProductCategory>?> ValueChanged { get; set; }

    [Parameter]
    public List<ProductCategory>? Value
    {
        get => _value;
        set
        {
            if (!EqualityComparer<List<ProductCategory>?>.Default.Equals(_value, value))
            {
                _value = value;

                if (ValueChanged.HasDelegate)
                    ValueChanged.InvokeAsync(value);
                else
                    StateHasChanged();
            }
        }
    }

    protected override async Task OnInitializedAsync()
    {
        await LoadProductCategoriesAsync();
    }

    private async Task LoadProductCategoriesAsync()
    {
        _items = Enumerable.Empty<SelectedItem<ProductCategory>>();
        await Task.Delay(100);
        _items = new List<SelectedItem<ProductCategory>>
        {
            new SelectedItem<ProductCategory> { Text = "Electronics", Value = new ProductCategory { Id = 1, Name = "Electronics" } },
            new SelectedItem<ProductCategory> { Text = "Books", Value = new ProductCategory { Id = 2, Name = "Books" } },
            new SelectedItem<ProductCategory> { Text = "Home", Value = new ProductCategory { Id = 3, Name = "Home" } }
        };
    }

    private IEnumerable<SelectedItem<ProductCategory>> HandleOnSearchTextChanged(string searchText)
    {
        return _items.Where(i => (i.Value.Name.Contains(searchText, StringComparison.OrdinalIgnoreCase)));
    }

    private async Task HandleOnSelectedItemsChanged(IEnumerable<SelectedItem<ProductCategory>>? selectedItems)
    {
        Value = selectedItems?.Select(x => x.Value).ToList() ?? new List<ProductCategory>();
        if (OnSelectedItemsChanged != null)
        {
            await OnSelectedItemsChanged.Invoke(selectedItems);
        }
    }

    public class ProductCategory
    {
        public int Id { get; set; }
        public string Name { get; set; } = "";
    }
}

The Problem

  • When I attempt to add an item programmatically via the AddElectronics method, the item does not appear in the UI.
  • The two-way binding setup does not seem to detect changes made by external methods like AddElectronics.

What I've Tried

  • Verified that the ValueChanged event is properly invoked when programmatically modifying the list.
  • Added StateHasChanged() calls to manually refresh the state.
  • Ensured that _value within MultiSelectGeneric is updated correctly.

Request for Help

I would appreciate any insights on what might be missing from my implementation to allow external modifications to reflect properly in the UI. Thank you in advance for your help!

This issue is a follow-up to dotnetcore/BootstrapBlazor#6363 , where I'm encountering difficulties implementing a custom multiselect component with external item addition reflecting properly in the UI.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions