Search code examples
.netblazorrazor-pagesblazor-webassembly.net-7.0

Select dropdown in Blazor in component fires, but only prior selection option is displayed instead of current selection option


I have a Blazor 7 component that is basically a dropdown selection. When a selection is made, an event fires, so the calling page/component can get the selected item. One problem is that, only the previous item is carried over to the parent, not the current selection. I'm at a loss as how to solve this as everything seems to be working other than this quirk. Basically, the first time you make a selection, the object passed to the parent is empty (not null). The second time you make a selection, the first item is passed to the parent. A third selection will show the second item and so forth. Here's the component code:

<h3>DropDownNames</h3>

<select @bind="@mySelector.SelectedId" @bind:after="SelectionChanged">
    @foreach (var opt in mySelector.Options)
    {
        <option value="@opt.Id" class="@opt.Style">@opt.Name @(new String('*', @opt.Stars))</option>
    }
</select>

@if (mySelector.SelectedId > 0)
{
    selectedOption = mySelector.Options.Find(e => e.Id == mySelector.SelectedId);
}

@code {

    [Parameter]
    public EventCallback<Option> GetSelectedOption { get; set; }

    private Selector mySelector = new Selector(myOptions);

    private Option selectedOption { get; set; } = new Option();

    //populate the select options
    private static List<Option> myOptions = new List<Option>() {
        new Option(1, "Fred", "style3", 2),
        new Option(2, "Joe", "style1", 4),
        new Option(3, "Jana", "style2", 4),
        new Option(4, "Mary", "style1", 3),
        new Option(5, "Howard", "style3", 1) };

    async void SelectionChanged()
    {
        await GetSelectedOption.InvokeAsync(selectedOption);
    }

    public class Selector
    {
        public Selector(List<Option> options) => Options = options;

        public int SelectedId { get; set; } = 0;
        public List<Option> Options { get; set; }
    }

    public class Option
    {
        public Option(int id = 0, string? name = "", string? style = "", int stars = 0)
        {
            Id = id;
            Name = name;
            Style = style;
            Stars = stars;
        }

        public int Id { get; set; }
        public string? Name { get; set; }
        public string? Style { get; set; }
        public int Stars { get; set; }
    }


}

and the parent page (just the index page):

@page "/"

<PageTitle>Index</PageTitle>

<DropDownNames GetSelectedOption="getSelectedOption"></DropDownNames>

<br />

<p>You selected: @selectedOption.Id</p>
<p>@selectedOption.Name has @selectedOption.Stars.ToString() stars</p>


@code {

    DropDownNames.Option selectedOption = new DropDownNames.Option();
    public void getSelectedOption(DropDownNames.Option option)
    {
        selectedOption = option;
    }
}

I believe I'm doing something wrong here, but not sure where to start/look. Bing's AI was no help as it generates unusable code, so seeking help from real people.


Solution

  • @if (mySelector.SelectedId > 0)
    {
        selectedOption = mySelector.Options.Find(e => e.Id == mySelector.SelectedId);
    }
    

    The way you're changing the selectedOption field is causing the issue here. It's being executed after the EventCallback is invoked.

    You should move that logic to the SelectionChanged() method, right before the EventCallback is invoked.

    async Task SelectionChanged()
    {
        selectedOption = mySelector.Options.Find(e => e.Id == mySelector.SelectedId);
        await GetSelectedOption.InvokeAsync(selectedOption);
    }