-
-
Notifications
You must be signed in to change notification settings - Fork 362
Description
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
- Razor Page/Component:
- I use a
MultiSelectProductCategoriescomponent 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.
- I use a
<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();
}
}
}
}- MultiSelect Component Logic:
- I am using a
MultiSelectGenericcomponent insideMultiSelectProductCategorieswith two-way binding andItemslist setup. - I’ve implemented the
ValueChangedevent callback and bind it to the outer component.
- I am using a
<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
AddElectronicsmethod, 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
ValueChangedevent is properly invoked when programmatically modifying the list. - Added
StateHasChanged()calls to manually refresh the state. - Ensured that
_valuewithinMultiSelectGenericis 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.