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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DataGrid] Add getFilterState method #13418

Merged
merged 7 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions docs/data/data-grid/filtering-recipes/FilteredRowCount.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import { DataGridPro, useGridApiRef } from '@mui/x-data-grid-pro';
import { useDemoData } from '@mui/x-data-grid-generator';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';

const predefinedFilters = [
{
label: 'All',
filterModel: { items: [] },
},
{
label: 'Filled',
filterModel: { items: [{ field: 'status', operator: 'is', value: 'Filled' }] },
},
{
label: 'Open',
filterModel: { items: [{ field: 'status', operator: 'is', value: 'Open' }] },
},
{
label: 'Rejected',
filterModel: { items: [{ field: 'status', operator: 'is', value: 'Rejected' }] },
},
{
label: 'Partially Filled',
filterModel: {
items: [{ field: 'status', operator: 'is', value: 'PartiallyFilled' }],
},
},
];

export default function FilteredRowCount() {
const { data } = useDemoData({
dataSet: 'Commodity',
rowLength: 1000,
maxColumns: 10,
});

const apiRef = useGridApiRef();

const [predefinedFiltersRowCount, setPredefinedFiltersRowCount] = React.useState(
[],
);

const getFilteredRowsCount = React.useCallback(
(filterModel) => {
const { filteredRowsLookup } = apiRef.current.getFilterState(filterModel);
return Object.keys(filteredRowsLookup).filter(
(rowId) => filteredRowsLookup[rowId] === true,
).length;
},
[apiRef],
);

React.useEffect(() => {
// Calculate the row count for predefined filters
if (data.rows.length === 0) {
return;
}

setPredefinedFiltersRowCount(
predefinedFilters.map(({ filterModel }) => getFilteredRowsCount(filterModel)),
);
}, [apiRef, data.rows, getFilteredRowsCount]);

return (
<div style={{ overflow: 'hidden' }}>
<Stack direction="row" gap={1} mb={1} flexWrap="wrap">
{predefinedFilters.map(({ label, filterModel }, index) => {
const count = predefinedFiltersRowCount[index];
return (
<Button
key={label}
onClick={() => apiRef.current.setFilterModel(filterModel)}
variant="outlined"
>
{label} {count !== undefined ? `(${count})` : ''}
</Button>
);
})}
</Stack>
<Box sx={{ height: 520, width: '100%' }}>
<DataGridPro {...data} loading={data.rows.length === 0} apiRef={apiRef} />
</Box>
</div>
);
}
88 changes: 88 additions & 0 deletions docs/data/data-grid/filtering-recipes/FilteredRowCount.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import { DataGridPro, useGridApiRef, GridFilterModel } from '@mui/x-data-grid-pro';
import { useDemoData } from '@mui/x-data-grid-generator';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';

const predefinedFilters: { label: string; filterModel: GridFilterModel }[] = [
{
label: 'All',
filterModel: { items: [] },
},
{
label: 'Filled',
filterModel: { items: [{ field: 'status', operator: 'is', value: 'Filled' }] },
},
{
label: 'Open',
filterModel: { items: [{ field: 'status', operator: 'is', value: 'Open' }] },
},
{
label: 'Rejected',
filterModel: { items: [{ field: 'status', operator: 'is', value: 'Rejected' }] },
},
{
label: 'Partially Filled',
filterModel: {
items: [{ field: 'status', operator: 'is', value: 'PartiallyFilled' }],
},
},
];

export default function FilteredRowCount() {
const { data } = useDemoData({
dataSet: 'Commodity',
rowLength: 1000,
maxColumns: 10,
});

const apiRef = useGridApiRef();

const [predefinedFiltersRowCount, setPredefinedFiltersRowCount] = React.useState<
michelengelen marked this conversation as resolved.
Show resolved Hide resolved
number[]
>([]);

const getFilteredRowsCount = React.useCallback(
(filterModel: GridFilterModel) => {
const { filteredRowsLookup } = apiRef.current.getFilterState(filterModel);
return Object.keys(filteredRowsLookup).filter(
(rowId) => filteredRowsLookup[rowId] === true,
).length;
},
[apiRef],
);

React.useEffect(() => {
// Calculate the row count for predefined filters
if (data.rows.length === 0) {
return;
}

setPredefinedFiltersRowCount(
predefinedFilters.map(({ filterModel }) => getFilteredRowsCount(filterModel)),
);
}, [apiRef, data.rows, getFilteredRowsCount]);

return (
<div style={{ overflow: 'hidden' }}>
<Stack direction="row" gap={1} mb={1} flexWrap="wrap">
{predefinedFilters.map(({ label, filterModel }, index) => {
const count = predefinedFiltersRowCount[index];
return (
<Button
key={label}
onClick={() => apiRef.current.setFilterModel(filterModel)}
variant="outlined"
>
{label} {count !== undefined ? `(${count})` : ''}
</Button>
);
})}
</Stack>
<Box sx={{ height: 520, width: '100%' }}>
<DataGridPro {...data} loading={data.rows.length === 0} apiRef={apiRef} />
</Box>
</div>
);
}
6 changes: 6 additions & 0 deletions docs/data/data-grid/filtering-recipes/filtering-recipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@ Currently if you want to use the [Quick filter](/x/react-data-grid/filtering/qui
A common use case is to have certain components positioned outside of the grid. Because of the way the grid context works this might not be a straightforward thing to do. The example below illustrates how this use case can be achieved.

{{"demo": "QuickFilterOutsideOfGrid.js", "bg": "inline", "defaultCodeOpen": false}}

## Calculating row count in advance
cherniavskii marked this conversation as resolved.
Show resolved Hide resolved

The [Grid API](/x/react-data-grid/api-object/#how-to-use-the-api-object) provides the [`getFilterState`](/x/api/data-grid/grid-api/#grid-api-prop-getFilterState) method, which allows you to display the row count for predefined filters upfront without applying filters to the Data Grid:

michelengelen marked this conversation as resolved.
Show resolved Hide resolved
{{"demo": "FilteredRowCount.js", "bg": "inline", "defaultCodeOpen": false}}
6 changes: 6 additions & 0 deletions docs/pages/x/api/data-grid/grid-api.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@
"required": true,
"isProPlan": true
},
"getFilterState": {
"type": {
"description": "(filterModel: GridFilterModel) =&gt; GridStateCommunity['filter']"
},
"required": true
},
"getLocaleText": {
"type": {
"description": "&lt;T extends GridTranslationKeys&gt;(key: T) =&gt; GridLocaleText[T]"
Expand Down
5 changes: 5 additions & 0 deletions docs/pages/x/api/data-grid/grid-filter-api.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
"description": "Deletes a <a href=\"/x/api/data-grid/grid-filter-item/\">GridFilterItem</a>.",
"type": "(item: GridFilterItem) => void"
},
{
"name": "getFilterState",
"description": "Returns the filter state for the given filter model without applying it to the data grid.",
"type": "(filterModel: GridFilterModel) => GridStateCommunity['filter']"
},
{ "name": "hideFilterPanel", "description": "Hides the filter panel.", "type": "() => void" },
{
"name": "ignoreDiacritics",
Expand Down
3 changes: 3 additions & 0 deletions docs/translations/api-docs/data-grid/grid-api.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@
"description": "Returns the grid data as an exceljs workbook.<br />This method is used internally by <code>exportDataAsExcel</code>."
},
"getExpandedDetailPanels": { "description": "Returns the rows whose detail panel is open." },
"getFilterState": {
"description": "Returns the filter state for the given filter model without applying it to the data grid."
},
"getLocaleText": { "description": "Returns the translation for the <code>key</code>." },
"getPinnedColumns": { "description": "Returns which columns are pinned." },
"getRootDimensions": { "description": "Returns the dimensions of the grid" },
Expand Down
34 changes: 23 additions & 11 deletions packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,21 +107,13 @@ export const useGridFilter = (
const updateFilteredRows = React.useCallback(() => {
apiRef.current.setState((state) => {
const filterModel = gridFilterModelSelector(state, apiRef.current.instanceId);
const isRowMatchingFilters =
props.filterMode === 'client'
? buildAggregatedFilterApplier(filterModel, apiRef, props.disableEval)
: null;

const filteringResult = apiRef.current.applyStrategyProcessor('filtering', {
isRowMatchingFilters,
filterModel: filterModel ?? getDefaultGridFilterModel(),
});
const filterState = apiRef.current.getFilterState(filterModel);

const newState = {
...state,
filter: {
...state.filter,
...filteringResult,
...filterState,
},
};

Expand All @@ -133,7 +125,7 @@ export const useGridFilter = (
};
});
apiRef.current.publishEvent('filteredRowsSet');
}, [apiRef, props.filterMode, props.disableEval]);
}, [apiRef]);

const addColumnMenuItem = React.useCallback<GridPipeProcessor<'columnMenu'>>(
(columnMenuItems, colDef) => {
Expand Down Expand Up @@ -321,6 +313,25 @@ export const useGridFilter = (
[apiRef, logger, props.disableMultipleColumnsFiltering],
);

const getFilterState = React.useCallback<GridFilterApi['getFilterState']>(
(filterModel) => {
const isRowMatchingFilters =
props.filterMode === 'client'
? buildAggregatedFilterApplier(filterModel, apiRef, props.disableEval)
: null;

const filterResult = apiRef.current.applyStrategyProcessor('filtering', {
isRowMatchingFilters,
filterModel: filterModel ?? getDefaultGridFilterModel(),
});
return {
...filterResult,
filterModel,
};
},
[apiRef, props.filterMode, props.disableEval],
);

const filterApi: GridFilterApi = {
setFilterLogicOperator,
unstable_applyFilters: applyFilters,
Expand All @@ -332,6 +343,7 @@ export const useGridFilter = (
hideFilterPanel,
setQuickFilterValues,
ignoreDiacritics: props.ignoreDiacritics,
getFilterState,
};

useGridApiMethod(apiRef, filterApi, 'public');
Expand Down
7 changes: 7 additions & 0 deletions packages/x-data-grid/src/models/api/gridFilterApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { GridFilterModel } from '../gridFilterModel';
import { GridFilterItem, GridLogicOperator } from '../gridFilterItem';
import { GridControlledStateReasonLookup } from '../events';
import type { DataGridProcessedProps } from '../props/DataGridProps';
import { GridStateCommunity } from '../gridStateCommunity';

/**
* The filter API interface that is available in the grid [[apiRef]].
Expand Down Expand Up @@ -61,4 +62,10 @@ export interface GridFilterApi {
* Returns the value of the `ignoreDiacritics` prop.
*/
ignoreDiacritics: DataGridProcessedProps['ignoreDiacritics'];
/**
* Returns the filter state for the given filter model without applying it to the data grid.
* @param {GridFilterModel} filterModel The filter model to get the state for.
* @returns {GridStateCommunity['filter']} The filter state.
*/
getFilterState: (filterModel: GridFilterModel) => GridStateCommunity['filter'];
Comment on lines +65 to +70
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds like it should be a (pure) selector, but I see how that would be annoying to implement.

}
Loading