I'm building a Blazor component that displays a dynamic form inside a [Syncfusion SfToolbar] and a custom ConfirmDialog.The form is rendered using a foreach loop over a List<TrackedValue<AniFilterType?>>, called _dropActionValues.
I use a DropDownList (from Syncfusion) for each item in the list.
When I select a new value from the dropdown (e.g., changing the type to Transfer, TTS, etc.), the UI does not update.
Similarly, when I click "Add" (weitere hinzufügen), which adds a new TrackedValue to the list and calls StateHasChanged(), the UI also does not render the new row.
The updated value and list count are visible in the debugger, but nothing changes visually until I click the "Add" button again or do a full page reload/navigation.
@if (!HideFilter.UntrackedValue) {<div class="row" style="margin-top: @(IsNameHidden ? "0px" : "15px")"><div class="col-xs-12 col-sm-12 col-md-12 col-lg-12"> @if (!InsideDialog || DialogIsOpen) {<SfToolbar OverflowMode="OverflowMode.Popup"><ToolbarItems><ToolbarItem Type="ItemType.Input" Align="ItemAlign.Left" Width="250px"><Template><TextBox Value="@SearchText" ValueChanged="OnSearchInput" Placeholder="@DefaultTrackedValues.SearchInputPlaceholder" ShowClearButton="@TrackedValue.TrackedTrue" Suffix="@DefaultTrackedValues.SearchInputIcons" SuffixIsIcon="@TrackedValue.TrackedTrue" LabelType="@TrackedLabelType.Never"/></Template></ToolbarItem><ToolbarItem Type="ItemType.Button" Align="ItemAlign.Right" PrefixIcon="@IconSets.InToolbar(IconSets.ButtonAdd)" Text="@UiResource.Button_Add" TooltipText="@UiResource.Button_Add" OnClick="@(OnAddButtonClick)"/><ConfirmDialog @ref="CreateDialog" Width="@DefaultTrackedValues.Percentages.FiftyPercents" Height="@DefaultTrackedValues.Percentages.FiftyPercents" Type="TrackedConfirmDialogType.ContinuousAdd" Visible="@ShowAddDialog" Confirm="PerformAdd" Cancel="CancelAdd" Working="@IsCreating" ProgressTracker="@CreateProgressTracker" AllowConfirm="@IsCreateFormValid" OnOpened="OnCreateDialogOpened" OnClosed="OnCreateDialogClosed" @bind-ContinuousAddEnabled="@IsContinuousCreateMode" FocusFirstElement="false"><Header><p class="dialog-header">@UiResource.Anifilter_View_Dialog_Header_Create</p><span class="dialog-subheader">@UiResource.Anifilter_View_Dialog_SubHeader_Create</span></Header><Content> @foreach (var (dropAction, i) in _dropActionValues.Select((v, i) => (v, i))) {<div class="row mb-2" @key="i"><div class="col-md-6"><DropDownList TValue="AniFilterType?" TSource="KeyValuePair<string, AniFilterType>" ValueSource="@AniFilterTypeOptions" Value="@_dropActionValues[i]" ValueChanged="@(v => OnDropActionChanged(i, v))" SourceKeyName="@SourceKeyName" SourceValueName="@SourceValueName" Placeholder="@DropActionPlaceholders[i]" Disabled="@IsDropActionDisabled" ShowClearButton="@TrackedValue.TrackedFalse"/> @switch (_dropActionValues[i].UntrackedValue) { case AniFilterType.Transfer:<ValidatedTextBox @ref="PatternInputs[i]" TObject="AniFilterFormModel" InputValidator="@InputValidator" Object="@AniFilter" ObjectChanged="@AniFilterChanged" ParameterName="@PatternParameterNames[i]" Placeholder="@PatternPlaceholderTransfer[i]" IsDisabled="@IsPatternDisabled" ShowClearButton="@TrackedValue.TrackedFalse"/> break; case AniFilterType.TTS:<ValidatedTextBox @ref="PatternInputs[i]" TObject="AniFilterFormModel" InputValidator="@InputValidator" Object="@AniFilter" ObjectChanged="@AniFilterChanged" ParameterName="@PatternParameterNames[i]" Placeholder="@PatternPlaceholderTTS[i]" IsDisabled="@IsPatternDisabled" ShowClearButton="@TrackedValue.TrackedFalse"/> break; case AniFilterType.Announcement:<DropDownList TValue="string" TSource="string" ValueSource="@AudioNames" Value="@_announcementValues[i]" ValueChanged="@(v => OnAnnouncementChanged(i, v))" Placeholder="@AnnouncementPlaceholder" ShowClearButton="@TrackedValue.TrackedFalse"/> break; case AniFilterType.ServiceType:<DropDownList TValue="string" TSource="string" ValueSource="@ServiceTypeNames" Value="@_serviceTypeValues[i]" ValueChanged="@(v => OnServiceTypeChanged(i, v))" Placeholder="@ServiceTypePlaceholder" ShowClearButton="@TrackedValue.TrackedFalse"/> break; }</div></div> }<SfButton CssClass="e-primary" IconCss="e-icons e-plus" Disabled="@(AbwurfCount >= 10)" OnClick="AddAbwurf"> Add</SfButton></Content></ConfirmDialog><ToolbarItem Type="ItemType.Button" Align="ItemAlign.Right" TooltipText="@UiResource.Button_Update" PrefixIcon="@IconSets.InToolbar(IconSets.ButtonUpdate)" Text="@UiResource.Button_Update" Disabled="@(!CanClickUpdate)"/><ToolbarItem Type="ItemType.Button" Align="ItemAlign.Right" Text="@UiResource.Button_Delete"><Template><SfTooltip Position="Position.BottomCenter"><ContentTemplate> @((MarkupString)DeleteToolTip.UntrackedValue)</ContentTemplate><ChildContent><SfButton IconCss="@IconSets.ButtonDelete" Content="@($"{UiResource.Button_Delete}: {SelectedEntries.Count}")" CssClass="e-danger" Disabled="@(!CanClickDelete)"/></ChildContent></SfTooltip></Template></ToolbarItem></ToolbarItems></SfToolbar> } else {<PageLoading/> }</div></div> } protected void OnDropActionChanged(int index, TrackedValue<AniFilterType?> value) { if (index < 0 || index >= _dropActionValues.Count) return; _dropActionValues[index].Update(value); AniFilter.Update(u => { var setters = new Action<AniFilterFormModel?>[] { x => x!.DropAction1 = value, x => x!.DropAction2 = value, x => x!.DropAction3 = value, x => x!.DropAction4 = value, x => x!.DropAction5 = value }; setters[index](u); }); AniFilterChanged.InvokeAsync(AniFilter); StateHasChanged(); } private List<TrackedValue<AniFilterType?>> _dropActionValues = [ new(null), new(null), new(null), new(null), new(null) ]; private void InitializeDropActionValues() { const int expectedCount = 5; // par exemple, selon ton UI if (_dropActionValues == null || _dropActionValues.Count != expectedCount) { _dropActionValues = new List<TrackedValue<AniFilterType?>>(expectedCount); for (int i = 0; i < expectedCount; i++) { _dropActionValues.Add(new TrackedValue<AniFilterType?>(null)); } } } private void InitializeAnnouncementValues() { const int expectedCount = 5; if (_announcementValues == null || _announcementValues.Count != expectedCount) { _announcementValues = new List<TrackedValue<string?>>(expectedCount); for (int i = 0; i < expectedCount; i++) { _announcementValues.Add(new TrackedValue<string?>(null)); } } } private void InitializeServiceTypeValues() { const int expectedCount = 5; if (_serviceTypeValues == null || _serviceTypeValues.Count != expectedCount) { _serviceTypeValues = new List<TrackedValue<string?>>(expectedCount); for (int i = 0; i < expectedCount; i++) { _serviceTypeValues.Add(new TrackedValue<string?>(null)); } } } protected override void OnInitialized() { OnNameReadOnlyChanged(); OnDialogOpened(); InitializeDropActionValues(); InitializeAnnouncementValues(); InitializeServiceTypeValues(); AniFilterTypeOptions = new TrackedList<KeyValuePair<string, AniFilterType>>( Enum.GetValues<AniFilterType>() .Cast<AniFilterType>() .Select(e => new KeyValuePair<string, AniFilterType>(e.ToString(), e)) .ToList() ); base.OnInitialized(); } protected override ITrackedValue[] ParametersToTriggerRendering => [IsNameHidden, SearchText, DialogIsOpen, Filters, HideFilter, InsideDialog, ShowReleaseTable, IsNameDisabled, IsImmediatelyValidated, AniFilter, IsDialogOpened, InputValidator, ShowAddDialog, IsCreating, IsContinuousCreateMode, CreateProgressTracker, IsCreateFormValid, AniFilterTypeOptions, SourceKeyName, SourceValueName, IsDropActionDisabled, IsPatternDisabled, AudioNames, ServiceTypeNames, AnnouncementPlaceholder, AbwurfCount, SelectedEntries ];Why does the component fail to re-render when the list item changes or a new item is added?
What is the correct way to force the UI to react to:
TrackedValue<T>updates?Changes in a DropDownList selection?
Adding new rows to a foreach-rendered list?
Is this a limitation of Blazor's rendering mechanism, or possibly due to the TrackedValue<T> structure and how Syncfusion components handle Value binding?