Quantcast
Channel: Active questions tagged blazor - Stack Overflow
Viewing all articles
Browse latest Browse all 4480

Blazor Child Component UI Not Updating on Property Change

$
0
0

I am new to Blazor, but I have a strong background in React. I suspect I might be trying to make Blazor work like React, and that could be causing my issue.

I have a BaseStep component that provides a structure for child steps in a multi-step form. One such step is StepOne, which contains a ToggleGroup component that allows users to select an engagement type.

However, the UI does not update/re-render when @AccessRequest.NatureOfEngagementId changes. Strangely enough:

The <p> tag correctly prints the updated ID, meaning the data itself is changing.The selection in ToggleGroup only updates when another part of the page causes a re-render.This leaves me thinking the issue lies in my ToggleGroup component. I need help figuring out why my UI is not updating immediately when @AccessRequest.NatureOfEngagementId changes.

I have a BaseStep component that acts as an abstract step:

 public abstract class StepBase : ComponentBase { [Parameter] public Request NewAccessRequest { get; set; } = default!; [Parameter] public EventCallback<Request> NewAccessRequestChanged { get; set; } = default!; [Parameter] public EventCallback<bool> OnStepValidChanged { get; set; } = default!; public required MudForm StepForm; protected bool StepSuccess; // Local copy of the request for binding private Request _localRequest = default!; // Property that binds to the local request and updates parent when changed protected Request AccessRequest {     get => NewAccessRequest;     set     {         if (!EqualityComparer<Request>.Default.Equals(NewAccessRequest, value))         {             NewAccessRequestChanged.InvokeAsync(value);         }     } } // Ensure local copy is in sync when parent updates the parameter protected override void OnParametersSet() {     if (!EqualityComparer<Request>.Default.Equals(NewAccessRequest, _localRequest))     {         _localRequest = NewAccessRequest;     } } // Method to validate the step public async Task<bool> ValidateStep() {     if (StepForm != null)     {         await StepForm.Validate();         await OnStepValidChanged.InvokeAsync(StepSuccess);     }     return StepSuccess; }}

This child component inherits from StepBase and updates AccessRequest when an engagement is changed.

 public partial class StepOne : StepBase {   [Parameter] public List<NatureOfEngagement> Engagements { get; set; } = []; private void OnEngagementChanged(int newEngagementId) {         AccessRequest = RequestBuilder.From(AccessRequest)             .WithNatureOfEngagementId(newEngagementId)             .Build(); }}

Child Component's UI:

@inherits StepBase<MudStep Title="What relationship does the person requesting access have with the company"><MudForm Model="@NewAccessRequest" @ref="StepForm" @bind-IsValid="StepSuccess"><MudGrid><MudItem xs="12"><ToggleGroup T="int"                             Required                             RequiredError="Please select an option"                             Value="@AccessRequest.NatureOfEngagementId"                             ValueChanged="OnEngagementChanged"                             SelectionMode="SelectionMode.SingleSelection"                             Options="Engagements.Select(e =>                              new Option<int> { Value = e.Id, Label = e.Engagement                              }).ToList()" /></MudItem><p>Selected ID @AccessRequest.NatureOfEngagementId</p></MudGrid></MudForm></MudStep>

This custom ToggleGroup<T> component is used for selection:

      public partial class ToggleGroup<T> : MudFormComponent<T, T>  {      // The text to display above the toggle group      [Parameter] public string? Label { get; set; }      [Parameter] public Dictionary<string, object>? AdditionalAttributes { get; set; }      // The list of toggle options      [Parameter] public IEnumerable<Option<T>>? Options { get; set; }      [Parameter] public SelectionMode SelectionMode { get; set; } = SelectionMode.SingleSelection;      [Parameter]      [Category(CategoryTypes.List.Behavior)]      public EventCallback<T?> ValueChanged { get; set; }      [Parameter]      [Category(CategoryTypes.List.Behavior)]      public EventCallback<IEnumerable<T?>?> ValuesChanged { get; set; }      [Parameter]      public T? Value { get; set; }      [Parameter]      public IEnumerable<T>? Values { get; set; }      // Single selection binding      private T? SingleSelectedValue      {          get => Value;           set          {              if (!EqualityComparer<T?>.Default.Equals(_value, value))              {                  _value = value;                  Value = value;                  Touched = true;                  BeginValidateAsync();                  ValueChanged.InvokeAsync(value);              }          }      }      // Multiple selection binding      private IEnumerable<T>? MultiSelectedValues      {          get => Values;          set          {              if (!EqualityComparer<IEnumerable<T>?>.Default.Equals(_value as IEnumerable<T>, value))              {                  _value = value != null ? (T?)(object?)value : default;                  Touched = true;                  Values = value;                  ValuesChanged.InvokeAsync(value);                  BeginValidateAsync();              }          }      }      public ToggleGroup() : base(converter: new MudBlazor.Converter<T, T>())      {      }      protected override Task ValidateValue()      {          var errors = new List<string>();          ValidationErrors.Clear();          if (Options == null || !Options.Any())          {              // If there are no available options, mark as error              Error = Required;          }          else          {              if (SelectionMode == SelectionMode.SingleSelection)              {                  // Ensure SingleSelectedValue is present in Options                  Error = Required && (SingleSelectedValue == null || !Options.Any(o => EqualityComparer<T>.Default.Equals(o.Value, SingleSelectedValue)));              }              else              {                  // Ensure all MultiSelectedValues exist in Options                  Error = Required && (MultiSelectedValues == null || !MultiSelectedValues.All(val => Options.Any(o => EqualityComparer<T>.Default.Equals(o.Value, val))));              }          }          if (Error)          {              ValidationErrors.Add(RequiredError);          }          ValidationErrors = errors;          return Task.CompletedTask;      }      }

Toggle Component UI:

    @typeparam T    @inherits MudFormComponent<T, T><MudStack>    @if (Label != null)    {<MudText Class="m-2" Typo="Typo.body2" Color="@(Error ? Color.Error : Color.Default)">            @(Label + (Required ? "*" : ""))</MudText>    }    @if (SelectionMode == SelectionMode.SingleSelection)    {<!-- Single Selection --><MudToggleGroup T="T"                        CheckMark                        SelectionMode="SelectionMode.SingleSelection"                        Color="@(Error ? Color.Error: Color.Primary)"                        @bind-Value="SingleSelectedValue"                        @attributes="@AdditionalAttributes">            @foreach (var option in Options ?? Enumerable.Empty<Option<T>>())            {<MudToggleItem Value="@option.Value"                               UnselectedIcon="@Icons.Material.Filled.CheckBoxOutlineBlank"                               SelectedIcon="@Icons.Material.Filled.CheckBox">                    @option.Label</MudToggleItem>            }</MudToggleGroup>    }    else    {<!-- Multiple Selection --><MudToggleGroup T="T"                        CheckMark                        SelectionMode="SelectionMode.MultiSelection"                        Color="@(Error ? Color.Error: Color.Primary)"                        @bind-Values="MultiSelectedValues"                        @attributes="@AdditionalAttributes">            @foreach (var option in Options ?? Enumerable.Empty<Option<T>>())            {<MudToggleItem Value="@option.Value"                               UnselectedIcon="@Icons.Material.Filled.CheckBoxOutlineBlank"                               SelectedIcon="@Icons.Material.Filled.CheckBox">                    @option.Label</MudToggleItem>            }</MudToggleGroup>    }    @if (Error)    {<MudText Class="text-danger" Style="margin-top: -10px; margin-left: 8px;" Typo="Typo.caption">            @(string.IsNullOrWhiteSpace(ErrorText) ? RequiredError : ErrorText)</MudText>    }</MudStack>

The issue is that the child component's UI does not re-render when the @AccessRequest.NatureOfEngagementId property changes. The correct ID is printed inside the <p> tag, but the ToggleGroup does not visually update unless another re-render is triggered elsewhere.

I have been on this for sometime now and would love if another eye was to look at this maybe they can spot the issue.


Viewing all articles
Browse latest Browse all 4480

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>