microsoft / fluentui-blazor

Microsoft Fluent UI Blazor components library. For use with ASP.NET Core Blazor applications
https://www.fluentui-blazor.net
MIT License
3.78k stars 363 forks source link

Component crashes when choosing option from 2nd FluentSelect using keyboard #1441

Closed ZnowuJa closed 8 months ago

ZnowuJa commented 8 months ago

🐛 Bug Report

In component I have two identical FluentSelect components, both handles lists of very similar objects Categories and Vendors. When I expand both FluentSelects using mouse, everything is working perfect. When I go through Form with keyboard and try to expand the second one striking arrow down key it crashes the form.

I tried some constellations. For instance change order of FluentSelects, add another component after them etc. etc.

Single FluentSelect (either Category or Vendor) in this component works great, so they are rather well written.

💻 Repro or Code Sample

Code of my component:

@* Part New/Edit Component *@
@using MediatR
@inject IMediator _mediator;
@implements IDialogContentComponent<PartVm>

@* Header *@
<FluentDialogHeader ShowDismiss="true">
    <FluentStack VerticalAlignment="VerticalAlignment.Center">
        <FluentIcon Value="@(new Icons.Regular.Size24.WindowApps())" />
        <FluentLabel Typo="Typography.PaneHeader">
            @Dialog.Instance.Parameters.Title
        </FluentLabel>
    </FluentStack>
</FluentDialogHeader>

@* Footer *@
<FluentDialogFooter>
    <FluentButton Appearance="Appearance.Accent" OnClick="@SaveAsync"
                  Disabled="@(!_editContext.Validate())"> Save
    </FluentButton>
    <FluentButton Appearance="Appearance.Neutral" OnClick="@CancelAsync">Cancel</FluentButton>
</FluentDialogFooter>

@* Body *@
<FluentDialogBody Orientation="Orientation.Horizontal">
    <EditForm EditContext="_editContext">
        <FluentValidationValidator @ref="_fluentValidator" />

        <FluentGrid Justify="@Justification">
            <FluentGridItem xs="6" sm="4">
                <div class="card">

                    <FluentTextField Id="Name" @bind-Value="@Content.Name" Placeholder="Enter name"> Name: </FluentTextField>
                    <FluentValidationMessage For="@(() => Content.Name)" />

                </div>
            </FluentGridItem>
            <FluentGridItem xs="6" sm="4">
                <div class="card">
                    <FluentTextField Id="Description" @bind-Value="@Content.Description" Placeholder="Enter description"> Description: </FluentTextField>
                    <FluentValidationMessage For="@(() => Content.Description)" />
                </div>
            </FluentGridItem>

            <FluentGridItem xs="6" sm="4">
                <div class="card">
                    <FluentNumberField Id="WarrantyPeriod" @bind-Value="@Content.WarrantyPeriod" Placeholder="Enter WarrantyPeriod"> Warranty Period: </FluentNumberField>
                    <FluentValidationMessage For="@(() => Content.WarrantyPeriod)" />
                </div>
            </FluentGridItem>
            <FluentGridItem xs="6" sm="4">
                <div class="card">
                    @{
                        // Check if it's an Edit operation

                        if (Content.Id != 0)
                        {
                            <FluentSelect TOption="CategoryVm"
                                          Label="Select a category..."
                                          Items="@itemCategoryList"
                                          Id="Id"
                                          Width="150px"
                                          Height="250px"
                                          OptionValue="@(p => p.Id.ToString())"
                                          OptionText="@(p => p.Name)"
                                          @bind-SelectedOption="@Content.CategoryVm" />
                        }
                        else
                        {
                            // For new records
                            <FluentSelect TOption="CategoryVm"
                                          Label="Select a category..."
                                          Items="@itemCategoryList"
                                          Id="Id"
                                          Width="150px"
                                          Height="250px"
                                          OptionValue="@(p => p.Id.ToString())"
                                          OptionText="@(p => p.Name)"
                                          @bind-SelectedOption="@SelectedCategory" />
                        }
                    }

                    <FluentValidationMessage For="@(() => Content.CategoryVm.Name)" />
                </div>
            </FluentGridItem>
            <FluentGridItem xs="6" sm="4">
                <div class="card">
                    @{
                        // Check if it's an Edit operation

                        if (Content.Id != 0)
                        {
                            <FluentSelect TOption="VendorVm"
                                          Label="Select a vendor..."
                                          Items="@itemVendorList"
                                          Id="Id"
                                          Width="150px"
                                          Height="250px"
                                          OptionValue="@(p => p.Id.ToString())"
                                          OptionText="@(p => p.Name)"
                                          @bind-SelectedOption="@Content.VendorVm" />
                        }
                        else
                        {
                            // For new records
                            <FluentSelect TOption="VendorVm"
                                          Label="Select a vendor..."
                                          Items="@itemVendorList"
                                          Id="Id"
                                          Width="150px"
                                          Height="250px"
                                          OptionValue="@(p => p.Id.ToString())"
                                          OptionText="@(p => p.Name)"
                                          @bind-SelectedOption="@SelectedVendor" />
                        }
                    }

                    <FluentValidationMessage For="@(() => Content.VendorVm.Name)" />
                </div>
            </FluentGridItem>
            <FluentGridItem xs="6" sm="4">
                <div class="card">
                    <FluentTextField Id="Photo" @bind-Value="@Content.Photo" Placeholder="Enter Photo"> Photo: </FluentTextField>
                    <FluentValidationMessage For="@(() => Content.Photo)" />
                </div>
            </FluentGridItem>

        </FluentGrid>

        @if (!string.IsNullOrEmpty(errorMessage))
        {
            <div class="validation-error"><p style="color:red;">@errorMessage</p></div>
        }
    </EditForm>
</FluentDialogBody>

@code {
    [Parameter]
    public PartVm Content { get; set; } = default!;

    [CascadingParameter]
    public FluentDialog Dialog { get; set; } = default!;

    JustifyContent Justification = JustifyContent.FlexStart;

    private EditContext _editContext;

    private FluentValidationValidator? _fluentValidator;
    private string errorMessage;

    private IQueryable<CategoryVm> itemCategoryList { get; set; }
    private IQueryable<VendorVm> itemVendorList { get; set; }
    private CategoryVm SelectedCategory { get; set; } = new CategoryVm { };
    private VendorVm SelectedVendor { get; set; } = new VendorVm { };

    protected override async Task OnInitializedAsync()
    {
        _editContext = new EditContext(Content);
        itemCategoryList = await _mediator.Send(new GetAllCategoriesForSelectQuery());
        itemVendorList = await _mediator.Send(new GetAllVendorsForSelectQuery());
        base.OnInitialized();
    }

    private async Task SaveAsync()
    {
        Console.WriteLine("SaveAsync starts...");
        if (_editContext.Validate())
        {
            Console.WriteLine("EditContext Validation OK!...");
        }
        else
        {
            Console.WriteLine("EditContext Validation NOT OK!...");
            return;
        }
        if (Content.Id != 0)
        {
            await Dialog.CloseAsync(Content);
        }
        else
        {
            if (SelectedCategory.Id != 0 || SelectedVendor.Id != 0)
            {
                Content.CategoryVm = SelectedCategory;
                Content.VendorVm = SelectedVendor;
                await Dialog.CloseAsync(Content);
            }
            else
            {
                Console.WriteLine();
                errorMessage = "Plese select Category or Vendor!";
            }
        }
    }

    private async Task CancelAsync()
    {
        await Dialog.CancelAsync();
    }
}

🤔 Expected Behavior

All dropdowns should expand the same way no matter which input device we use.

😯 Current Behavior

Application throws this:

System.InvalidOperationException: Sequence contains no matching element at System.Linq.ThrowHelper.ThrowNoMatchException() at System.Linq.Enumerable.First[TSource](IEnumerable1 source, Func2 predicate) at Microsoft.FluentUI.AspNetCore.Components.ListComponentBase`1.OnKeydownHandlerAsync(KeyboardEventArgs e) in /_/src/Core/Components/List/ListComponentBase.razor.cs:line 536 at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task) at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)

From logger:

warn: Microsoft.AspNetCore.Components.Server.Circuits.RemoteRenderer[100] Unhandled exception rendering component: Sequence contains no matching element System.InvalidOperationException: Sequence contains no matching element at System.Linq.ThrowHelper.ThrowNoMatchException() at System.Linq.Enumerable.First[TSource](IEnumerable1 source, Func2 predicate) at Microsoft.FluentUI.AspNetCore.Components.ListComponentBase1.OnKeydownHandlerAsync(KeyboardEventArgs e) in /_/src/Core/Components/List/ListComponentBase.razor.cs:line 536 at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task) at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState) fail: Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost[111] Unhandled exception in circuit '4EVbV4XHMBWAw2Lb9Z51QQCJIbr8JsKhmFaU35taH3M'. System.InvalidOperationException: Sequence contains no matching element at System.Linq.ThrowHelper.ThrowNoMatchException() at System.Linq.Enumerable.First[TSource](IEnumerable1 source, Func2 predicate) at Microsoft.FluentUI.AspNetCore.Components.ListComponentBase1.OnKeydownHandlerAsync(KeyboardEventArgs e) in /_/src/Core/Components/List/ListComponentBase.razor.cs:line 536 at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task) at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)

🌍 Your Environment

VS 2022 Pro, Project is Blazor Server Side in .Net 8.

vnbaaij commented 8 months ago

Please supply us with a ready to run minimal reproduction. We can't run the code you provided anddo not have capacity to craft code ourselves for every issue.

ZnowuJa commented 8 months ago

Here is fresh repo: https://github.com/ZnowuJa/Second-FluentSelect-Crashesh.git

On Home Page please click Add Part button, and on component try to go through it with tab keys and expand Dropdowns. Second one will crash the application. Or I'm doing something wrong... If you use mouse to expand and select - everything is ok. Even you can expand second dropdown with mouse but you can not use keyboard then...

vnbaaij commented 8 months ago

Ok, it was you who was doing it wrong...😉

You have in Part_Edit_Component.razor:

:
<FluentSelect TOption="CategoryVm"
              Label="Select a category..."
              Items="@itemCategoryList"
              Id="Id"
:

and

:
 <FluentSelect TOption="VendorVm"
              Label="Select a vendor..."
              Items="@itemVendorList"
              Id="Id"
: 

See what is the issue there? Both FluentSelects are assigned to the same Id. Just change the second one to something else abd it all starts woring as it should