Merged
Conversation
…SelectedValues Co-authored-by: ScarletKuro <19953225+ScarletKuro@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Rewrite MudSelect SelectedValues to use ParameterState
Rewrite MudSelect to use ParameterState for SelectedValues
Dec 11, 2025
Co-authored-by: ScarletKuro <19953225+ScarletKuro@users.noreply.github.com>
…arer Co-authored-by: ScarletKuro <19953225+ScarletKuro@users.noreply.github.com>
…Async Create new HashSet instances when calling SetValueAsync to ensure ParameterState's comparer detects value changes. Previously passing the same _selectedValues reference caused the comparer to see no change and skip firing SelectedValuesChanged events. Progress: 65/70 SelectTests passing (was 57/70) Co-authored-by: ScarletKuro <19953225+ScarletKuro@users.noreply.github.com>
The test modifies the bound collection directly but needs to call StateHasChanged to trigger parent re-render so ParameterState receives the new value. Co-authored-by: ScarletKuro <19953225+ScarletKuro@users.noreply.github.com>
Changed from mutating readonly HashSet to creating new instance so ParameterState detects the reference change. Also removed readonly modifier to allow reassignment. Progress: 66/70 SelectTests passing (was 65/70) Co-authored-by: ScarletKuro <19953225+ScarletKuro@users.noreply.github.com>
…nces Create new HashSet from incoming SelectedValues to prevent parent and child from sharing the same collection reference. This ensures ParameterState properly detects changes when parent modifies and re-passes the collection. Progress: 67/70 SelectTests passing (was 66/70) Co-authored-by: ScarletKuro <19953225+ScarletKuro@users.noreply.github.com>
Only call FieldChanged in OnSelectedValuesChangedAsync after first render to avoid triggering field change events during component initialization with non-default values. Progress: 68/70 SelectTests passing (was 67/70) Co-authored-by: ScarletKuro <19953225+ScarletKuro@users.noreply.github.com>
Initialize SelectedValues to empty HashSet in constructor to ensure GetState never returns null. Update SelectAndHighlightItem to publish to ParameterState. Use Comparer for value equality check in single-select mode. Ensure SelectedValues is always synchronized even when value doesn't change. All 70 SelectTests now passing! ✅ Co-authored-by: ScarletKuro <19953225+ScarletKuro@users.noreply.github.com>
versile2
requested changes
Dec 11, 2025
Contributor
versile2
left a comment
There was a problem hiding this comment.
Overall looked real strong. Pretty close.
src/MudBlazor.UnitTests.Viewer/TestComponents/Select/MultiSelectWithCustomComparerTest.razor
Show resolved
Hide resolved
Member
|
@copilot I found problem with your implementation. I used the same example inside the |
Added internal ConvertValueToString method to MudSelect that delegates to ConvertSet, allowing MudSelectItem to properly display values using the parent's ToStringFunc. This fixes the issue where complex types were showing their type name instead of the custom string representation. Also fixed test bug where all assertions were checking items[0] instead of items[0-3]. All 71 SelectTests passing ✅ Co-authored-by: ScarletKuro <19953225+ScarletKuro@users.noreply.github.com>
3 tasks
This was referenced Dec 12, 2025
3 tasks
3 tasks
10 tasks
7 tasks
This was referenced Feb 20, 2026
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refactored
MudSelect<T>andMudAutocomplete<T>to use the ParameterState framework for managing parameters, eliminating logic in parameter getters/setters and preventing unobserved async discards.Changes
MudSelect ParameterState Refactoring
Core Refactoring:
SelectedValuesto auto-property with[Parameter, ParameterState]ParameterState<IEnumerable<T?>?>backing field with event callback and change handlerOnSelectedValuesChangedAsynchandlerSelectedValuesChanged.InvokeAsync()calls with_selectedValuesState.SetValueAsync()_selectedValuesHashSet now acts as the single source of truth for UI operationsOnSelectedValuesChangedAsynccreates a new HashSet from incoming values to prevent shared references between parent and child componentsSetValueAsynccalls now create new HashSet instances to ensure ParameterState's comparer detects changes and fires events correctlySelectedValues = new HashSet<T?>()in constructor to ensure ParameterState never holds nullFieldChangedafter first render usingHasRenderedcheck to prevent firing during initialization with non-default valuesComparer Handling:
Comparerto auto-property with[Parameter, ParameterState(ParameterUsage = None)](read-only from parent)OnComparerChangedAsynchandler to refresh internal HashSet when comparer changesComparerfor value equality checks in single-select mode instead of always usingEqualityComparer<T>.DefaultParameterState Synchronization:
SelectAndHighlightItem(used by keyboard navigation) to publish to ParameterState_selectedValuesinternal HashSet to also update_selectedValuesStateMudSelectItem ToStringFunc Fix:
MudSelectItem.DisplayStringto properly use parent'sToStringFuncfor complex typesConvertValueToStringmethod toMudSelectthat wrapsConvertSetMudBlazor.UnitTests.TestComponents.Select.SelectCustomToStringTest+Pizza) instead of the custom string representationToStringFuncis providedNew Utility:
SequenceComparer<T>for element-wise IEnumerable comparison.WithComparer(() => new SequenceComparer<T?>(Comparer))External Access:
MudSelectItem<T>to useGetState(x => x.SelectedValues)for external property accessusing MudBlazor.Extensionsfor GetState extension methodTest Updates:
SelectTests.csto useGetState(x => x.SelectedValues)instead of direct property accessMultiSelectWithCustomComparerTest.razor) to create new collection instances instead of mutating existing ones when updating bound parametersSelectTestCustomToStringtest bug where all assertions were checkingitems[0]instead ofitems[0-3]MudAutocomplete ParameterState Refactoring
Core Refactoring:
Opento auto-property with[Parameter, ParameterState]ParameterState<bool>backing field with event callback registrationOpenproperty reads with_openState.ValueOpenassignments withawait _openState.SetValueAsync()MudAutocomplete.razorto use_openState.Valuefor bindings_openfield workarounds that preventedOpenChangedeventsTest Updates:
AutocompleteTests.csto useGetState(x => x.Open)patternusing MudBlazor.Extensionsfor GetState extension methodPattern
MudSelect Pattern
Before (forbidden):
After (required):
MudAutocomplete Pattern
Before (forbidden):
After (required):
Key Learnings
ParameterState Change Detection:
SetValueAsynconly fires events if the comparer detects a changeSetValueAsyncDefensive Copying:
Parent Component Pattern:
StateHasChanged()won't trigger ParameterState change detection_selected = new HashSet<T?> { item1, item2 };instead of_selected.Clear(); _selected.Add(item1);ParameterState Synchronization:
_parameterState.SetValueAsyncto keep ParameterState synchronized with parentInitialization:
GetStatefrom returning null (for collection types)HasRenderedflag to prevent side effects from firing during component initializationToStringFunc Display:
Test Results
✅ MudSelect: All 71 SelectTests passing (100%)
🔄 MudAutocomplete: 123/131 AutocompleteTests passing (93.9%)
MudAutocomplete Failing Tests (8 tests):
Autocomplete_ChangeBoundValueAutocomplete_OpenChanged_SelectOptionAsyncAutocomplete_OpenOnFocusShouldWork(True)Autocomplete_Should_Close_OnTabAutocomplete_Should_SelectValue_On_Tab_With_SelectValueOnTabAutocompleteCoercionTestOnTextChanged_CoerceValueAndNotCoerceTextAndImmediateAndDebounce_SetValueImmediatelyButDelaysMenuOpeningOnTextChanged_WithCoerceValueAndNotCoerceTextAndImmediateNotDebounce_SetValueAndOpenMenuImmediatelyThe remaining AutocompleteTests failures are under investigation and appear to be timing/async-related issues that need further debugging.
Checklist:
Original prompt
This section details on the original issue you should resolve
<issue_title>Rewrite MudSelect SelectedValues to use ParameterState</issue_title>
<issue_description>
✅ Rewrite
MudSelect(MudSelect.razor/MudSelect.razor.cs) to useParameterStateforSelectedValues1. Convert
SelectedValuesinto an auto-property with[ParameterState]Replace the existing implementation with:
Add the backing field:
In the end of the constructor, add:
2. Move old
SelectedValues.setlogic into a handlerImplement:
3. Make
Compareran auto-propertyReplace the old implementation with:
If old code used
Comparer = value;side effects, migrate them into:4. Replace usage of
SelectedValueswithParameterStateWhere the component previously read values:
replace with:
Where the code previously wrote to the parameter:
If it was:
→ replace with:
If it was:
→ usually replace with:
BUT do not blindly replace all assignments —
In Blazor it is forbidden for a component to directly assign to a
[Parameter]property to simulate an incoming parameter change — updates must go through the event callback (e.g.SelectedValuesChanged) so the parent stays authoritative. WithParameterStatewe use a shim that automates this pattern: never write directly toSelectedValues. Instead, use the internalHashSet<T?>for all local mutations and call_selectedValuesState.SetValueAsync(...)to publish the final value back to the parent. ThatSetValueAsynccall is the equivalent of raising the event callback and will invoke the change pipeline you registered withWithEventCallback/WithChangeHandler.await _selectedValuesState.SetValueAsync(finalEnumerable)(or push the internalHashSetcontents).HashSet<T?>, then run validation/normalization/side-effects, and only when those succeed callawait _selectedValuesState.SetValueAsync(_selectedInternal)to publish. If validation fails, do not callSetValueAsync(that preserves the previous behavior where the parameter wasn't updated).HashSet<T?>and do not callSetValueAsync. In short:SetValueAsync== "publish to parent (raise change)"; internalHashSetmanipulations == "local state only".5. Introduce an internal
HashSet<T?>Add an internal list to manage values without re-enumeration:
You must also introduce an internal
HashSet<T?>that represents the current selected values inside the component. All add/remove/clear operations should be done on this internal set, not directly onSelectedValuesor_selectedValuesState.Value. The internal set must be created using the sameCompareras the component. After the internal list is modified, you must push its contents to the parent by calling_selectedValuesState.SetValueAsync(_internalHashSet). In addition, you must keep the internal set synchronized with updates coming from the parent: when_selectedValuesStatechanges due to parent re-render, read `_selectedValues...💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.