mui / mui-x

MUI X: Build complex and data-rich applications using a growing list of advanced React components, like the Data Grid, Date and Time Pickers, Charts, and more!
https://mui.com/x/
4.53k stars 1.31k forks source link

[data grid] Filter button outside of the data grid : get number of filtered items before applying filter #5410

Open NicoIxiasoft opened 2 years ago

NicoIxiasoft commented 2 years ago

Order ID 💳

45010

Duplicates

Latest version

The problem in depth 🔍

Hello support,

We are using the datagrid to display a list of items and we want to add a button outside of the datagrid that will filter programmatically the results. We managed to do it using the filterModel prop. Now we would like to show to the user the number of items that will be shown before really applying the filter. This will be useful for the users to know if there are relevant items somewhere in the grid without always applying the filter.

To help you understand imagine you have a big list of items and once in a while one of them will have a status "action required immediately". We want the user to know he has something to do without him having to click everytime the button.

Thank you for your help. This is our first time submitting a support question, we hope we did everything ok.

Regards, Nicolas.

Your environment 🌎

`npx @mui/envinfo` System: OS: Windows 10 10.0.19044 CPU: (8) x64 Intel(R) Core(TM) i7-8559U CPU @ 2.70GHz Memory: 16.75 GB / 31.87 GB Binaries: Node: 16.15.0 - C:\Program Files\nodejs\node.EXE Yarn: 1.16.0 - C:\Program Files (x86)\Yarn\bin\yarn.CMD npm: 6.14.16 - C:\Program Files\nodejs\npm.CMD Managers: Maven: 3.6.1 - C:\Program Files (x86)\apache-maven-3.6.1\bin\mvn.CMD pip2: 20.3.4 - C:\Python27\Scripts\pip2.EXE pip3: 21.1.2 - ~\AppData\Local\Programs\Python\Python39\Scripts\pip3.EXE Utilities: Git: 2.22.0. - /mingw64/bin/git FFmpeg: 4.2.3 - C:\Program Files\ImageMagick-7.1.0-Q16-HDRI\ffmpeg.EXE SDKs: Windows SDK: AllowDevelopmentWithoutDevLicense: Enabled AllowAllTrustedApps: Enabled Versions: 10.0.17763.0 IDEs: VSCode: 1.59.1 - C:\Users\danetn\AppData\Local\Programs\Microsoft VS Code\bin\code.CMD Languages: Bash: 4.4.23 - C:\Program Files\Git\usr\bin\bash.EXE Java: javac 11 - /c/Program Files/Java/jdk-11/bin/javac Perl: 5.26.2 - C:\Program Files\Git\usr\bin\perl.EXE Python: 2.7.16 - /c/Python27/python Browsers: Chrome: 103.0.5060.114 Edge: Spartan (44.19041.1266.0), Chromium (103.0.1264.44) Internet Explorer: 11.0.19041.1566
cherniavskii commented 2 years ago

That is a really interesting use case! Basically the idea is to be able to get grid filtering results without affecting grid's state.

Can you answer few questions?

  1. Do you use server-side pagination or do you have all the data on client side?

  2. imagine you have a big list of items and once in a while one of them will have a status "action required immediately". We want the user to know he has something to do without him having to click everytime the button.

    Did you try to do this using Array.filter on your data? For the use case you mentioned, it sounds like it should be fairly easy to do.

NicoIxiasoft commented 2 years ago

Thank you for your reply and your interest,

  1. We don't use server-side pagination, all the data is available to the client.
  2. We thought about that but we would like this filter to work with all other possible filters already included in the datagrid : for example if the user filters first by a column "Country" we would like the button to show only the number of "action required immediately" only for this country. Moreover the data that is filtered is processed with the datagrid valueGetter, so it's really more convenient to use the datagrid filter because otherwise we would have to transform again the data if we want to filter the raw data.

We tried to find a way to copy the state of the grid and apply the filter on the copy, but we were not able to make it work. We also thought using a second hidden datagrid to perform the filter, but we are worried that having a second huge datagrid may cause performance issues.

Best regards, Nicolas.

m4theushw commented 2 years ago

I don't see how we can support properly this use case. Even if we delay the rerendering of the grid with the filtered results, the internal state will still be updated, potentially causing other issues. I think to achieve something similar you need to filter the data manually then update the rows prop. It will be like https://mui.com/x/react-data-grid/filtering/#server-side-filter but with the filtering being done on client-side.

cherniavskii commented 2 years ago

@m4theushw I was thinking about exposing a method that would return filtering results without adding them to grid state, something like this:

diff --git a/packages/grid/x-data-grid/src/hooks/features/filter/useGridFilter.tsx b/packages/grid/x-data-grid/src/hooks/features/filter/useGridFilter.tsx
index e4d7a84ba..3aa3dc903 100644
--- a/packages/grid/x-data-grid/src/hooks/features/filter/useGridFilter.tsx
+++ b/packages/grid/x-data-grid/src/hooks/features/filter/useGridFilter.tsx
@@ -29,6 +29,7 @@ import {
 } from './gridFilterUtils';
 import { GridStateInitializer } from '../../utils/useGridInitializeState';
 import { isDeepEqual } from '../../../utils/utils';
+import { GridFilterModel } from '../../../models/gridFilterModel';

 export const filterStateInitializer: GridStateInitializer<
   Pick<DataGridProcessedProps, 'filterModel' | 'initialState' | 'disableMultipleColumnsFiltering'>
@@ -73,9 +74,8 @@ export const useGridFilter = (
     changeEvent: 'filterModelChange',
   });

-  const updateFilteredRows = React.useCallback(() => {
-    apiRef.current.setState((state) => {
-      const filterModel = gridFilterModelSelector(state, apiRef.current.instanceId);
+  const getFilterResults = React.useCallback(
+    (filterModel: GridFilterModel) => {
       const isRowMatchingFilters =
         props.filterMode === GridFeatureModeConstant.client
           ? buildAggregatedFilterApplier(filterModel, apiRef)
@@ -85,6 +85,16 @@ export const useGridFilter = (
         isRowMatchingFilters,
       });

+      return filteringResult;
+    },
+    [apiRef, props.filterMode],
+  );
+
+  const updateFilteredRows = React.useCallback(() => {
+    apiRef.current.setState((state) => {
+      const filterModel = gridFilterModelSelector(state, apiRef.current.instanceId);
+      const filteringResult = getFilterResults(filterModel);
+
       return {
         ...state,
         filter: {
@@ -94,7 +104,7 @@ export const useGridFilter = (
       };
     });
     apiRef.current.publishEvent('filteredRowsSet');
-  }, [props.filterMode, apiRef]);
+  }, [apiRef, getFilterResults]);

   /**
    * API METHODS
@@ -251,6 +261,8 @@ export const useGridFilter = (
     hideFilterPanel,
     getVisibleRowModels,
     setQuickFilterValues,
+    getFilterResults,
   };

   useGridApiMethod(apiRef, filterApi, 'GridFilterApi');

Then you can do something like this:

const filteringResults = apiRef.current.getFilterResults(filterModel)

But it's kind of fragile - it uses internal grid state to get rows data, and it's hard to know when to recalculate filtering results.

flaviendelangle commented 2 years ago

For me we would need to first allow to de-sync the filter model update and the filter application. We had users in the past asking for a mode of the filter panel with an "Apply" button at the bottom for instance.

But yes it is still fragile.

Do we have competitors handling this use case

NicoIxiasoft commented 2 years ago

Thank you all for your interest in our use case. Just for your information we settled with a suboptimal yet still good enough solution :

  1. We added a listener on the "onStateChange" listener of the datagrid where we call the hook "gridVisibleSortedRowIdsSelector" : that way we get the rows currently displayed in the datagrid with the currently applied filters by the user.
  2. When necessary we manually apply the filter on these displayed rows in order to guess the number of results, without having to really apply the filter in the datagrid.

Best Regards, Nicolas.