Skip to content

fix: Row click with SelectColumn and filtered items causes data refresh #3847

@ASchweers

Description

@ASchweers

🐛 Bug Report

In a non-virtualized DataGrid with a SelectColumn and filtered items, every row click is triggering a data refresh.

💻 Repro or Code Sample

Taken from the DataGridTypical.razor sample - with an inserted SelectColumn.

@inject DataSource Data

<p>To test set ResizeType on the DataGrid to either DataGridResizeType.Discrete or DataGridResizeType.Exact</p>
<p>Remove the parameter completely to get the original behavior</p>

<div style="height: 380px; overflow-x:auto; display:flex;">
    <FluentDataGrid @ref="grid"
                    Items="@FilteredItems"
                    ResizableColumns=true
                    ResizeType="DataGridResizeType.Discrete"
                    GridTemplateColumns="0.3fr 1fr 0.2fr 0.2fr 0.2fr 0.2fr"
                    Pagination="@pagination"
                    RowClass="@rowClass"
                    RowStyle="@rowStyle"
                    HeaderCellAsButtonWithMenu="true"
                    ColumnResizeLabels="@resizeLabels">
        <SelectColumn TGridItem="@Country"
                      SelectMode="DataGridSelectMode.Single"
                      SelectFromEntireRow="true"
                      @bind-SelectedItems="@SelectedItems" />
        <TemplateColumn Tooltip="true" TooltipText="@(c => "Flag of " + c.Name)" Title="Rank" SortBy="@rankSort" Align="Align.Center" InitialSortDirection="SortDirection.Ascending" IsDefaultSortColumn=true>
            <img class="flag" src="_content/FluentUI.Demo.Shared/flags/@(context.Code).svg" alt="Flag of @(context.Code)" />
        </TemplateColumn>
        <PropertyColumn Property="@(c => c.Name)" Sortable="true" Filtered="!string.IsNullOrWhiteSpace(nameFilter)" Tooltip="true" Title="Name of the country">
            <ColumnOptions>
                <div class="search-box">
                    <FluentSearch Autofocus=true @bind-Value=nameFilter @oninput="HandleCountryFilter" @onkeydown="HandleCloseFilterAsync" @bind-Value:after="HandleClear" Placeholder="Country name..." Style="width: 100%;" Label="Filter" />
                </div>
            </ColumnOptions>
        </PropertyColumn>
        <PropertyColumn Property="@(c => c.Medals.Gold)" Sortable="true" Align="Align.Start" Tooltip="true" TooltipText="@(c => "That is " + c.Medals.Gold + " x GOLD!!")" />
        <PropertyColumn Property="@(c => c.Medals.Silver)" Sortable="true" Align="Align.Center" Tooltip="true" />
        <PropertyColumn Property="@(c => c.Medals.Bronze)" Sortable="false" Align="Align.End" />
        <PropertyColumn Property="@(c => c.Medals.Total)" Sortable="true" Filtered="@(minMedals != 0 || maxMedals != 130)" Align="Align.End" Tooltip="true">
            <ColumnOptions>
                <div style="width: 100%; height: 150px;">
                    <FluentSlider Label="@($"Min ({minMedals})")" Min="0" Max="150" Step="1" Orientation="Orientation.Horizontal" @bind-Value=minMedals Immediate="true" Style="width: 100%;">
                        <FluentSliderLabel Position="0">0</FluentSliderLabel>
                        <FluentSliderLabel Position="50">50</FluentSliderLabel>
                        <FluentSliderLabel Position="100">100</FluentSliderLabel>
                        <FluentSliderLabel Position="150">150</FluentSliderLabel>
                    </FluentSlider>
                    <br /><br />
                    <FluentSlider Label="@($"Max ({maxMedals})")" Min="0" Max="150" Step="1" Orientation="Orientation.Horizontal" @bind-Value=maxMedals Immediate="true" Style="width: 100%;">
                        <FluentSliderLabel Position="0">0</FluentSliderLabel>
                        <FluentSliderLabel Position="50">50</FluentSliderLabel>
                        <FluentSliderLabel Position="100">100</FluentSliderLabel>
                        <FluentSliderLabel Position="150">150</FluentSliderLabel>
                    </FluentSlider>
                </div>
            </ColumnOptions>
        </PropertyColumn>
    </FluentDataGrid>
</div>

<FluentPaginator State="@pagination" />

<FluentSwitch @bind-Value="@_clearItems"
              @bind-Value:after="ToggleItemsAsync"
              UncheckedMessage="Clear all results"
              CheckedMessage="Restore all results">
</FluentSwitch>

@code {
    FluentDataGrid<Country>? grid;
    bool _clearItems = false;
    IQueryable<Country>? items;
    PaginationState pagination = new PaginationState { ItemsPerPage = 10 };
    string nameFilter = string.Empty;
    int minMedals;
    int maxMedals = 130;
    IEnumerable<Country> SelectedItems = [];

    ColumnResizeLabels resizeLabels = ColumnResizeLabels.Default with
    {
        DiscreteLabel = "Width (+/- 10px)",
        ResetAriaLabel = "Restore"
    };

    GridSort<Country> rankSort = GridSort<Country>
        .ByDescending(x => x.Medals.Gold)
        .ThenDescending(x => x.Medals.Silver)
        .ThenDescending(x => x.Medals.Bronze);

    Func<Country, string?> rowClass = x => x.Name.StartsWith("A") ? "highlighted" : null;
    Func<Country, string?> rowStyle = x => x.Name.StartsWith("Au") ? "background-color: var(--highlight-bg)" : null;

    //IQueryable<Country>? FilteredItems => items?.Where(x => x.Name.Contains(nameFilter, StringComparison.CurrentCultureIgnoreCase));

    IQueryable<Country>? FilteredItems
    {
        get
        {
            var result = items?.Where(c => c.Medals.Total <= maxMedals);

            if (result is not null && !string.IsNullOrEmpty(nameFilter))
            {
                result = result.Where(c => c.Name.Contains(nameFilter, StringComparison.CurrentCultureIgnoreCase));
            }

            if (result is not null && minMedals > 0)
            {
                result = result.Where(c => c.Medals.Total >= minMedals);
            }

            return result;
        }
    }

    protected override async Task OnInitializedAsync()
    {
        items = (await Data.GetCountriesAsync()).AsQueryable();
    }

    private void HandleCountryFilter(ChangeEventArgs args)
    {
        if (args.Value is string value)
        {
            nameFilter = value;
        }
    }

    private void HandleClear()
    {
        if (string.IsNullOrWhiteSpace(nameFilter))
        {
            nameFilter = string.Empty;
        }
    }

    private async Task HandleCloseFilterAsync(KeyboardEventArgs args)
    {
        if (args.Key == "Escape")
        {
            nameFilter = string.Empty;
        }
        if (args.Key == "Enter" && grid is not null)
        {
            await grid.CloseColumnOptionsAsync();
        }
    }

    private async Task ToggleItemsAsync()
    {
        if (_clearItems)
        {
            items = null;
        }
        else
        {
            items = (await Data.GetCountriesAsync()).AsQueryable();
        }
    }
}

🤔 Expected Behavior

I wouldn't expect any data refreshes just for selecting a row.

😯 Current Behavior

Every Select/Deselect is now triggering a data refresh.

If I ignore the filters and change the Items attribute of the FluentDataGrid of the sample to items instead of FilteredItems then the behavior is as expected (with no unnecessary data refreshes in case of a real Queryable items object).

🌍 Your Environment

  • .NET 9.0
  • FluentUI version 4.11.9

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions