blazorblazor-server-sideblazor-webassemblymudblazor

How to use ValueChanged in mudselect


I am trying to write to write general component for MudSelect dropdown. The options for the component is passed from the Parent , When the selected options change I wanted to get the value defined in the options which is a class and navigate to the page defined.

Th following is the code

HomePage.razor

 <HomeContainerComponent>
     <div>
         <HomeDropdownComponent Options="@dropdownOptions" TValue="string" SelectedOption="@selectedOption" OnValueChanged="OnValueChanged" SelectedOptionChanged="SelectedOptionChanged"></HomeDropdownComponent>
     </div>
</<HomeContainerComponent>

HomePage.razor.cs
using Microsoft.AspNetCore.Components;
 

namespace Test.Shared.Components.Pages
{
    public partial class HomePage :ComponentBase
    {
        
        [Inject] private NavigationManager? NavigationManager { get; set; }
        protected List<DropdownOptions<string>> dropdownOptions { get; set; } = new();      
        public DropdownOptions<string> selectedOption { get; set; } = new();       
        protected  override void OnInitialized()
        {
            dropdownOptions = FetchDropdownOptions();
            selectedOption = dropdownOptions?.FirstOrDefault() ?? new();
        }
        private List<DropdownOptions<string>> FetchDropdownOptions()
        {
          return  dropdownOptions = new List<DropdownOptions<string>>
            {
                new DropdownOptions<string> { Value = "Option1", Text = "Option1" ,ImageUrl ="_content/Test.Shared.Components/option1.png"},
                new DropdownOptions<string> { Value = "Option2", Text = "Option2" ,ImageUrl ="_content/Test.Shared.Components/option2.png"},
                new DropdownOptions<string> { Value = "Option3", Text = "Option3" ,ImageUrl ="_content/Test.Shared.Components/option3.png" , PageName = "/mud-tab"},
                new DropdownOptions<string> { Value = "Option4", Text = "Option4" ,ImageUrl ="_content/Test.Shared.Components/option3.png"},
            };
        }
        private void OnValueChanged(string selected)
        {
            selectedOption = dropdownOptions?.Where( x => x.Text == selected).FirstOrDefault() ?? new();
            NavigationManager?.NavigateTo(selectedOption.PageName);
            // Do other stuff
        }
        
        
    }
 public class DropdownOptions<T>
 {
     public T Value { get; set; } = default!;
     public string Text { get; set; } = string.Empty;
     public string Icon { get; set; } = string.Empty;
     public string ImageUrl { get; set; } = string.Empty;
     public string PageName { get; set; } = string.Empty;
 }
}
HomeDropdownComponent.razor
@typeparam TValue

    
<MudSelect T="TValue" Label="@Label"  Variant="@Variant" AnchorOrigin="@AnchorOrigin" ValueChanged="OnSelectedValueChanged">
 @foreach (var option in Options)
    {
        <MudSelectItem T="TValue" Value="@(option.Value)">

            @if (!string.IsNullOrEmpty(option.Icon))
            {
                <MudIcon Icon="@option.Icon" Class="mr-2" />
            }
            @if (!string.IsNullOrEmpty(option.ImageUrl))
            {
                <MudImage Src="@option.ImageUrl" Class="ml-2" />
            }
            @option.Text
        <div style="flex-grow: 1"></div>


    </MudSelectItem>
    }
</MudSelect>

HomeDropdownComponent.razor.cs

using Microsoft.AspNetCore.Components;
using MudBlazor;
 

namespace Test.Shared.Components
{
    public partial class HomeDropdownComponent<TValue>
    {
        [Parameter]
        public string Label { get; set; } = string.Empty;

        [Parameter]
        public string Placeholder { get; set; } = string.Empty;

        [Parameter]
        public List<DropdownOptions<TValue>> Options { get; set; } = new();
        [Parameter]
        public DropdownOptions<TValue> SelectedOption { get; set; } = default!;

        [Parameter]

        public EventCallback<TValue> OnValueChanged { get; set; } = default!;

        [Parameter]
        public Variant Variant { get; set; } = Variant.Outlined;

        public Origin AnchorOrigin = Origin.BottomCenter;

        
        private TValue Value =  default!;
        private async Task OnSelectedValueChanged()
        {
            if (OnValueChanged.HasDelegate)
                await OnValueChanged.InvokeAsync();

        }
       
    }
   
}



When I run the code no value is selected by default , also The valuechanged function returns null. My requirement is when the dropdown value changes get the value of the page and navigate to that page, if i use the <MudSelect T="TValue" Label="@Label" @bind-Value="SelectedOption.Value" Variant="@Variant" AnchorOrigin="@AnchorOrigin" > the first drop down value is slected.

if if use <MudSelect T="TValue" Label="@Label" @bind-Value="SelectedOption.Value" Variant="@Variant" AnchorOrigin="@AnchorOrigin" ValueChanged = "OnSelectedValueChanged">

It shows error ValueChanged is used more than one time.


Solution

  • Value property needs to be assigned to the MudSelect

    And you need to pass the changed value to the EventCallback<TValue> when you invoke it.

    <MudSelect T="TValue" Label="@Label"  Variant="@Variant"
     AnchorOrigin="@AnchorOrigin" Value="SelectedOption.Value" ValueChanged="OnSelectedValueChanged">
    
    private async Task OnSelectedValueChanged(TValue newValue)
    {
        if (OnValueChanged.HasDelegate)
            await OnValueChanged.InvokeAsync(newValue);
    
    }
    

    If you weren't wrapping the MudSelect in another component then you would need to ensure that the field assigned to Value needs to be changed in the handler method. E.g.

    private async Task OnSelectedValueChanged(TValue newValue)
    {
        SelectedOption.Value = newValue;
    }
    

    However, since it's a child component the parent component (HomeDropdownComponent.razor) should handle changing the SelectedOption.Value.


    Here's a simplified example.

    //main.razor
    <HomeDropdownComponent Options="@options"
        TValue="string"
        HomeValue="@parentValue"
        HomeValueChanged="OnHomeValueChanged"/>
    
    <h1>parent chosen value:@parentValue</h1>
    @code{
        string parentValue;
        List<string> options;
        protected  override void OnInitialized()
        {
            options = FetchOptions();
            parentValue = options.First();
        }
        private List<string> FetchOptions()
        {
            return  new List<string>
            {
                "Option 1",
                "Option 2",
                "Option 3",
                "Option 4",
                "Option 5",
            };
        }
        void OnHomeValueChanged(string newVal){
            parentValue=newVal;
        }
    }
    
    //HomeDropdownComponent.razor
    @typeparam TValue
    
    <MudSelect Value="HomeValue" ValueChanged="HandleChange" T="TValue" Variant="Variant.Text" >
        @foreach (var option in Options)
        {
            <MudSelectItem Value="@(option)">
        </MudSelectItem>
        }
    </MudSelect>
    
    <h1>child chosen value:@HomeValue</h1>
    @code{
        [Parameter] public List<string> Options { get; set; } 
        [Parameter] public TValue HomeValue { get; set; } 
        [Parameter] public EventCallback<TValue> HomeValueChanged { get; set; } 
        
    
        async Task HandleChange(TValue newVal){
            await HomeValueChanged.InvokeAsync(newVal);
        }
    }
    

    Demo 👉MudBlazor Snippet