Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Data is re-fetched from remote API after any interaction with components in FluentDataGrid #1514

Closed
NickHirras opened this issue Feb 13, 2024 · 7 comments

Comments

@NickHirras
Copy link
Contributor

NickHirras commented Feb 13, 2024

馃悰 Bug Report

When using FluentDataGrid, I've added a Checkbox in a column. There's an onclick in my checkbox to add the row items 'Id' from the context to a list of selected Ids.

When I click this checkbox, the FluentDataGrid triggers a data request on the ItemsProvider, re-fetching the current page of data from remote. The expected behavior of the onclick is running and works, the list of selected Ids is updated to include this one.

Not sure why data is refreshing and what I should be doing to prevent this.

This seems easily reproducible, I think I'm seeing the same behavior in the currently deployed demo site. https://www.fluentui-blazor.net/DataGrid

Repro steps below.

馃捇 Repro or Code Sample

Point your browser to:
https://www.fluentui-blazor.net/DataGrid

Click on "Examples: Remote data" in the right-hand navigation menu.

image

When you get the Remote data example, click one of the Actions buttons, either the Pencil or Trash Can.

image

The FluentDataGrid will transition into a "Loading..." state, instead of just logging the expected message to the console.

image

馃 Expected Behavior

From the documentation and code sample, I expected a message to be logged reflecting which button was pressed.

馃槸 Current Behavior

The FluentDataGrid content clears and is replaced with a o Loading... spinner.

When using the demo site with Chrome Dev Tools, I can see that it's not actually loading anything from the API, it's simply displaying the "Loading..." state and appears stuck there. However, on my project I can see that it actually performs the API call.

I have tested in other browsers on my machine (Firefox and Safari) and get the same behavior.

馃拋 Possible Solution

Maybe this is the intended behavior? If so, is there something I can do to prevent the reloading data on my project?

I'm wondering if the checkbox being bound to a List, is causing the FluentDataGrid to have to re-render when the values change? In this case, should I be looking for some condition in my ItemsProvider method to perform data retrieval conditionally?

馃敠 Context

Building a Blazor Wasm application. I'm presenting the user with a FluentDataGrid of items fetched from a remote API.

I would like to add a checkbox on each row, as well as a "Check All / Clear All" checkbox in the column header.

When the user selects an item it is added to a list of Selected Ids.

When the user hits SelectAll/ClearAll I either add or remove all Ids from that list.

I don't want the data grid to refresh with each interaction. I would like the user to browse and select all items they are interested in through the checkboxes, and then be able to press a button to perform an action on their selected items.

馃實 Your Environment

  • OS & Device: Mac OS Sonoma (14.2.1) on MacBook Pro (Apple M1 Pro Chip)
  • Browser Google Chrome Version 121.0.6167.160 (Official Build) (arm64)
  • .NET 8.0.101 (Apple Arm 64)
@vnbaaij
Copy link
Collaborator

vnbaaij commented Feb 13, 2024

Hi,

The FluentDataGrid is using ChildContent as a way of collecting the list of columns, the assumption is it's free to render that as often as it wants. In order to discover when columns are added/removed/sorted, it has to be able to invoke ChildContent on each render. Same happens in this case with clicking on the pencil/thrash can. There is no way to prevent the reloading of the data. You should indeed be looking for some condition in your ItemsProvider method to perform data retrieval conditionally.

The message is however logged as you expected. It is just shown too low on the screen because of the ToC length. Collapse that ToC and you will see the messages.

This example goes into showing the Loading animation because of the <FluentDataGrid Loading="true" part in this example. That works well in initial loading but goes horribly wrong when reloading the data as in this case. Removing the `Loading="true" part will make this example work as expected.

@NickHirras
Copy link
Contributor Author

That makes sense. My page is currently built pretty closely to this example in how it handles data retrieval. I added a check in the ItemsProvider, it now only performs the API fetch if the Dictionary of query params is different from the previous request (or if this is the first time). This seems to be working great.

Thanks!

@NickHirras NickHirras closed this as not planned Won't fix, can't repro, duplicate, stale Feb 14, 2024
@markarnolditpro
Copy link

... I added a check in the ItemsProvider, it now only performs the API fetch if the Dictionary of query params is different from the previous request (or if this is the first time). This seems to be working great.

Hey @NickHirras, @vnbaaij - thanks for the update. I'm struggling with the same. What are you returning from the ItemsProvider if you do not perform the API fetch? If I simply skip the fetch, I end up returning an empty GridItemsProviderResult which of course clears the grid. I'm wondering if I need to pull the existing dataset into a new GridItemsProviderResult to return, or look higher upstream to block the GridRowsDataProviderRequest from happening.

Many thanks for any pointers!

@NickHirras
Copy link
Contributor Author

NickHirras commented Mar 7, 2024

@markarnolditpro

This is one of our ItemsProvider implementations and this is the pattern we've settled on. (Some code was removed for clarity but hopefully this is clear). Happy to help if you have more questions just let me know.

  private async ValueTask<GridItemsProviderResult<DisputeCase>> DisputeCasesProvider(GridItemsProviderRequest<DisputeCase> req)
  {
    // Show the "Loading..." indicator on the DataGrid
    _dataGrid?.SetLoadingState(true);

    // Build up a dictionary of all the filters or pagination values that will be sent to our
    // back-end API, and affect the set of results that are returned.
    var queryParameters = new Dictionary<string, object?>();

    if (!string.IsNullOrEmpty(RequestModel.DisputeSource))
    {
      queryParameters.Add("disputeSource", RequestModel.DisputeSource);
    }

    if (RequestModel.CaseCreatedStartDate != null && RequestModel.CaseCreatedStartDate.HasValue)
    {
      queryParameters.Add("caseCreatedStartDate",
        RequestModel.CaseCreatedStartDate.Value.ToString("MM/dd/yyyy 00:00:00"));
    }

    if (RequestModel.CaseCreatedEndDate != null && RequestModel.CaseCreatedEndDate.HasValue)
    {
      queryParameters.Add("caseCreatedEndDate", RequestModel.CaseCreatedEndDate.Value.ToString("MM/dd/yyyy 23:59:59"));
    }
    
    if (!string.IsNullOrEmpty(RequestModel.CompanyNumber))
    {
      queryParameters.Add("companyNumber", RequestModel.CompanyNumber);
    }
    
    queryParameters.Add("sortField", req.GetSortByProperties().First().PropertyName);
    queryParameters.Add("sortDirection",
      req.GetSortByProperties().First().Direction == SortDirection.Ascending ? 0 : 1);
    queryParameters.Add("pageSize", req.Count);
    queryParameters.Add("page", _pagination.CurrentPageIndex + 1);

    // if _cachedQueryParameters dictionary is equal to queryParameters, we will assume this is a
    // duplicate request for the same data, so just keep the existing cached results. If you want to
    // force a re-retrieval, we have a RefreshData boolean you can set to true.
    if (!RefreshData && _cachedQueryParameters.Count == queryParameters.Count &&
        !_cachedQueryParameters.Except(queryParameters).Any())
    {
      // returning the previously cached results.
      return GridItemsProviderResult.From(_cachedItems, NumResults);
    }

    // If you made it this far, we're going to cache this set of query parameters to compare against
    // next time.
    _cachedQueryParameters = queryParameters;

    // And call the API to fetch results.
    response = await Http.GetFromJsonAsync<DisputeCaseResponse>(url, req.CancellationToken);
    NumResults = response!.Count;

    // Cache the results, to be returned again in subsequent requests where the
    // query parameters were unchanged.
    _cachedItems = new List<DisputeCase>();
    foreach (var item in response!.Items)
    {
      _cachedItems.Add(item);
    }
    
    // Reset our 'force refresh' flag
    RefreshData = false;

    StateHasChanged();
    
    return GridItemsProviderResult.From(_cachedItems, NumResults);
  }

@markarnolditpro
Copy link

Thanks @NickHirras! This is great.

The call to StateHasChanged at the bottom is unfortunate though. Did you find that's required for this to work?

@NickHirras
Copy link
Contributor Author

NickHirras commented Mar 7, 2024

@markarnolditpro If I remember correctly we needed that to work around a bug in pagination not updating correctly in all cases. But I believe that's recently been fixed in FluentUI. I just tested removing the StateHasChanged() call on this page and everything still seems to function correctly, so I think it's fine to remove that.

@markarnolditpro
Copy link

Excellent. Many thanks @NickHirras!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants