Skip to content

Commit e65cbb6

Browse files
[DataGrid] Keep pipe pre-processors execution order when callback reference changes (@arminmeh) (#18217)
Co-authored-by: Armin Mehinovic <4390250+arminmeh@users.noreply.github.com> Co-authored-by: Armin Mehinovic <armin@mui.com>
1 parent 34419c2 commit e65cbb6

File tree

14 files changed

+124
-89
lines changed

14 files changed

+124
-89
lines changed

docs/data/data-grid/column-pinning/ColumnPinningWithCheckboxSelection.js renamed to docs/data/data-grid/column-pinning/ColumnPinningAutogeneratedColumns.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
DataGridPro,
66
GridActionsCellItem,
77
GRID_CHECKBOX_SELECTION_COL_DEF,
8+
GRID_REORDER_COL_DEF,
89
} from '@mui/x-data-grid-pro';
910
import {
1011
randomCreatedDate,
@@ -13,16 +14,20 @@ import {
1314
randomUpdatedDate,
1415
} from '@mui/x-data-grid-generator';
1516

16-
export default function ColumnPinningWithCheckboxSelection() {
17+
export default function ColumnPinningAutogeneratedColumns() {
1718
return (
1819
<div style={{ height: 400, width: '100%' }}>
1920
<DataGridPro
2021
rows={rows}
2122
columns={columns}
2223
checkboxSelection
24+
rowReordering
2325
initialState={{
2426
pinnedColumns: {
25-
left: [GRID_CHECKBOX_SELECTION_COL_DEF.field],
27+
left: [
28+
GRID_REORDER_COL_DEF.field,
29+
GRID_CHECKBOX_SELECTION_COL_DEF.field,
30+
],
2631
right: ['actions'],
2732
},
2833
}}

docs/data/data-grid/column-pinning/ColumnPinningWithCheckboxSelection.tsx renamed to docs/data/data-grid/column-pinning/ColumnPinningAutogeneratedColumns.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
GridRowsProp,
88
GridActionsCellItem,
99
GRID_CHECKBOX_SELECTION_COL_DEF,
10+
GRID_REORDER_COL_DEF,
1011
} from '@mui/x-data-grid-pro';
1112
import {
1213
randomCreatedDate,
@@ -15,16 +16,20 @@ import {
1516
randomUpdatedDate,
1617
} from '@mui/x-data-grid-generator';
1718

18-
export default function ColumnPinningWithCheckboxSelection() {
19+
export default function ColumnPinningAutogeneratedColumns() {
1920
return (
2021
<div style={{ height: 400, width: '100%' }}>
2122
<DataGridPro
2223
rows={rows}
2324
columns={columns}
2425
checkboxSelection
26+
rowReordering
2527
initialState={{
2628
pinnedColumns: {
27-
left: [GRID_CHECKBOX_SELECTION_COL_DEF.field],
29+
left: [
30+
GRID_REORDER_COL_DEF.field,
31+
GRID_CHECKBOX_SELECTION_COL_DEF.field,
32+
],
2833
right: ['actions'],
2934
},
3035
}}

docs/data/data-grid/column-pinning/ColumnPinningWithCheckboxSelection.tsx.preview renamed to docs/data/data-grid/column-pinning/ColumnPinningAutogeneratedColumns.tsx.preview

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22
rows={rows}
33
columns={columns}
44
checkboxSelection
5+
rowReordering
56
initialState={{
67
pinnedColumns: {
7-
left: [GRID_CHECKBOX_SELECTION_COL_DEF.field],
8+
left: [
9+
GRID_REORDER_COL_DEF.field,
10+
GRID_CHECKBOX_SELECTION_COL_DEF.field,
11+
],
812
right: ['actions'],
913
},
1014
}}

docs/data/data-grid/column-pinning/column-pinning.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,14 @@ Another alternate option to disable pinning actions on the UI is to disable the
114114

115115
:::
116116

117-
## Pinning the checkbox selection column
117+
## Pinning autogenerated columns
118118

119-
To pin the checkbox column added when using `checkboxSelection`, add `GRID_CHECKBOX_SELECTION_COL_DEF.field` to the list of pinned columns.
119+
Some features add columns to the grid.
120+
Pin those columns by providing their field names to the list of pinned columns.
120121

121-
{{"demo": "ColumnPinningWithCheckboxSelection.js", "bg": "inline"}}
122+
To pin the row reordering column added when using `rowReordering` and checkbox column added when using `checkboxSelection`, add `GRID_REORDER_COL_DEF.field` and `GRID_CHECKBOX_SELECTION_COL_DEF.field` to the list of pinned columns.
123+
124+
{{"demo": "ColumnPinningAutogeneratedColumns.js", "bg": "inline"}}
122125

123126
## Usage with dynamic row height
124127

docs/data/data-grid/row-grouping/row-grouping.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,18 @@ To display a column for each grouping criterion, set the `rowGroupingColumnMode`
6363
To customize the rendering of the grouping column, use the `groupingColDef` prop.
6464
You can override the **headerName** or any property of the `GridColDef` interface, except the `field`, the `type`, and the properties related to inline edition.
6565

66+
:::success
67+
For better performance, keep `groupingColDef` prop reference stable between two renders.
68+
:::
69+
6670
{{"demo": "RowGroupingCustomGroupingColDefObject.js", "bg": "inline", "defaultCodeOpen": false}}
6771

6872
By default, when using the object format, the properties will be applied to all Grouping columns. This means that if you have `rowGroupingColumnMode` set to `multiple`, all the columns will share the same `groupingColDef` properties.
6973

70-
If you wish to override properties of specific grouping columns or to apply different overrides based on the current grouping criteria, you can pass a callback function to `groupingColDef`, instead of an object with its config.
71-
The callback is called for each grouping column, and it receives the respective column's "fields" as parameter.
74+
To override properties for specific grouping columns, or to apply different overrides based on the current grouping criteria, you can pass a callback function to `groupingColDef` instead of an object with its config.
75+
The callback is called for each grouping column, and it receives the respective column's fields as parameters.
76+
77+
The demo below illustrates this approach to provide buttons for toggling between different grouping criteria:
7278

7379
{{"demo": "RowGroupingCustomGroupingColDefCallback.js", "bg": "inline", "defaultCodeOpen": false}}
7480

packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,19 +103,23 @@ export const useDataGridPremiumComponent = (
103103

104104
/**
105105
* Register all pre-processors called during state initialization here.
106+
* Some pre-processors are changing the same part of the state (like the order of the columns).
107+
* Order them in descending order of priority.
108+
* For example, left pinned columns should always render first from the left, so the `hydrateColumns` pre-processor from `useGridColumnPinningPreProcessors` should be called last (after all other `hydrateColumns` pre-processors).
109+
* Similarly, the `hydrateColumns` pre-processor from `useGridRowSelectionPreProcessors` should be called after `useGridRowGroupingPreProcessors` because the selection checkboxes should appear before the grouping columns.
110+
* Desired autogenerated columns order is:
111+
* left pinned columns -> row reordering column -> checkbox column -> tree data / row grouping column -> master detail column -> rest of the columns
106112
*/
107-
useGridRowSelectionPreProcessors(apiRef, props);
108-
useGridRowReorderPreProcessors(apiRef, props);
113+
useGridDetailPanelPreProcessors(apiRef, props);
109114
useGridRowGroupingPreProcessors(apiRef, props);
110115
useGridDataSourceRowGroupingPreProcessors(apiRef, props);
111116
useGridTreeDataPreProcessors(apiRef, props);
112117
useGridDataSourceTreeDataPreProcessors(apiRef, props);
118+
useGridRowSelectionPreProcessors(apiRef, props);
113119
useGridLazyLoaderPreProcessors(apiRef, props);
114120
useGridRowPinningPreProcessors(apiRef);
115121
useGridAggregationPreProcessors(apiRef, props);
116-
useGridDetailPanelPreProcessors(apiRef, props);
117-
// The column pinning `hydrateColumns` pre-processor must be after every other `hydrateColumns` pre-processors
118-
// Because it changes the order of the columns.
122+
useGridRowReorderPreProcessors(apiRef, props);
119123
useGridColumnPinningPreProcessors(apiRef, props);
120124
useGridRowsPreProcessors(apiRef);
121125

packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGroupingPreProcessors.ts

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
GridRowId,
66
gridRowTreeSelector,
77
useFirstRender,
8-
GRID_CHECKBOX_SELECTION_FIELD,
98
} from '@mui/x-data-grid-pro';
109
import {
1110
useGridRegisterPipeProcessor,
@@ -137,17 +136,7 @@ export const useGridRowGroupingPreProcessors = (
137136
newColumnsLookup[groupingColDef.field] = groupingColDef;
138137
});
139138

140-
const checkBoxFieldIndex = newColumnFields.findIndex(
141-
(field) => field === GRID_CHECKBOX_SELECTION_FIELD,
142-
);
143-
const checkBoxColumn =
144-
checkBoxFieldIndex !== -1 ? newColumnFields.splice(checkBoxFieldIndex, 1) : [];
145-
146-
newColumnFields = [
147-
...checkBoxColumn,
148-
...groupingColDefs.map((colDef) => colDef.field),
149-
...newColumnFields,
150-
];
139+
newColumnFields = [...groupingColDefs.map((colDef) => colDef.field), ...newColumnFields];
151140

152141
columnsState.orderedFields = newColumnFields;
153142
columnsState.lookup = newColumnsLookup;

packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,16 +96,20 @@ export const useDataGridProComponent = (
9696

9797
/**
9898
* Register all pre-processors called during state initialization here.
99+
* Some pre-processors are changing the same part of the state (like the order of the columns).
100+
* Order them in descending order of priority.
101+
* For example, left pinned columns should always render first from the left, so the `hydrateColumns` pre-processor from `useGridColumnPinningPreProcessors` should be called last (after all other `hydrateColumns` pre-processors).
102+
* Similarly, the `hydrateColumns` pre-processor from `useGridRowSelectionPreProcessors` should be called after `useGridTreeDataPreProcessors` because the selection checkboxes should appear before the tree data.
103+
* Desired autogenerated columns order is:
104+
* left pinned columns -> row reordering column -> checkbox column -> tree data column -> master detail column -> rest of the columns
99105
*/
100-
useGridRowSelectionPreProcessors(apiRef, props);
101-
useGridRowReorderPreProcessors(apiRef, props);
106+
useGridDetailPanelPreProcessors(apiRef, props);
102107
useGridTreeDataPreProcessors(apiRef, props);
103108
useGridDataSourceTreeDataPreProcessors(apiRef, props);
109+
useGridRowSelectionPreProcessors(apiRef, props);
104110
useGridLazyLoaderPreProcessors(apiRef, props);
105111
useGridRowPinningPreProcessors(apiRef);
106-
useGridDetailPanelPreProcessors(apiRef, props);
107-
// The column pinning `hydrateColumns` pre-processor must be after every other `hydrateColumns` pre-processors
108-
// Because it changes the order of the columns.
112+
useGridRowReorderPreProcessors(apiRef, props);
109113
useGridColumnPinningPreProcessors(apiRef, props);
110114
useGridRowsPreProcessors(apiRef);
111115

packages/x-data-grid-pro/src/hooks/features/detailPanel/useGridDetailPanelPreProcessors.ts

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as React from 'react';
22
import { RefObject } from '@mui/x-internals/types';
3-
import { gridClasses } from '@mui/x-data-grid';
3+
import { gridClasses, GridColDef } from '@mui/x-data-grid';
44
import { useGridRegisterPipeProcessor, GridPipeProcessor } from '@mui/x-data-grid/internals';
55
import { DataGridProProcessedProps } from '../../../models/dataGridProProps';
66
import {
@@ -16,32 +16,44 @@ export const useGridDetailPanelPreProcessors = (
1616
) => {
1717
const addToggleColumn = React.useCallback<GridPipeProcessor<'hydrateColumns'>>(
1818
(columnsState) => {
19-
if (props.getDetailPanelContent == null) {
20-
// Remove the toggle column, when it exists
21-
if (columnsState.lookup[GRID_DETAIL_PANEL_TOGGLE_FIELD]) {
22-
delete columnsState.lookup[GRID_DETAIL_PANEL_TOGGLE_FIELD];
23-
columnsState.orderedFields = columnsState.orderedFields.filter(
24-
(field) => field !== GRID_DETAIL_PANEL_TOGGLE_FIELD,
25-
);
26-
}
27-
return columnsState;
28-
}
29-
30-
// Don't add the toggle column if there's already one
31-
// The user might have manually added it to have it in a custom position
32-
if (columnsState.lookup[GRID_DETAIL_PANEL_TOGGLE_FIELD]) {
33-
return columnsState;
34-
}
35-
36-
// Otherwise, add the toggle column at the beginning
37-
columnsState.orderedFields = [GRID_DETAIL_PANEL_TOGGLE_FIELD, ...columnsState.orderedFields];
38-
columnsState.lookup[GRID_DETAIL_PANEL_TOGGLE_FIELD] = {
19+
const detailPanelToggleColumn: GridColDef = {
3920
...GRID_DETAIL_PANEL_TOGGLE_COL_DEF,
4021
headerName: privateApiRef.current.getLocaleText('detailPanelToggle'),
4122
};
23+
24+
const shouldHaveToggleColumn = !!props.getDetailPanelContent;
25+
const hasToggleColumn = columnsState.lookup[GRID_DETAIL_PANEL_TOGGLE_FIELD] != null;
26+
27+
if (shouldHaveToggleColumn && !hasToggleColumn) {
28+
columnsState.lookup[GRID_DETAIL_PANEL_TOGGLE_FIELD] = detailPanelToggleColumn;
29+
columnsState.orderedFields = [
30+
GRID_DETAIL_PANEL_TOGGLE_FIELD,
31+
...columnsState.orderedFields,
32+
];
33+
} else if (!shouldHaveToggleColumn && hasToggleColumn) {
34+
delete columnsState.lookup[GRID_DETAIL_PANEL_TOGGLE_FIELD];
35+
columnsState.orderedFields = columnsState.orderedFields.filter(
36+
(field) => field !== GRID_DETAIL_PANEL_TOGGLE_FIELD,
37+
);
38+
} else if (shouldHaveToggleColumn && hasToggleColumn) {
39+
columnsState.lookup[GRID_DETAIL_PANEL_TOGGLE_FIELD] = {
40+
...detailPanelToggleColumn,
41+
...columnsState.lookup[GRID_DETAIL_PANEL_TOGGLE_FIELD],
42+
};
43+
// If the column is not in the columns array (not a custom detail panel toggle column), move it to the beginning of the column order
44+
if (!props.columns.some((col) => col.field === GRID_DETAIL_PANEL_TOGGLE_FIELD)) {
45+
columnsState.orderedFields = [
46+
GRID_DETAIL_PANEL_TOGGLE_FIELD,
47+
...columnsState.orderedFields.filter(
48+
(field) => field !== GRID_DETAIL_PANEL_TOGGLE_FIELD,
49+
),
50+
];
51+
}
52+
}
53+
4254
return columnsState;
4355
},
44-
[privateApiRef, props.getDetailPanelContent],
56+
[privateApiRef, props.columns, props.getDetailPanelContent],
4557
);
4658

4759
const addExpandedClassToRow = React.useCallback<GridPipeProcessor<'rowClassName'>>(

packages/x-data-grid-pro/src/hooks/features/rowReorder/useGridRowReorderPreProcessors.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,29 +39,33 @@ export const useGridRowReorderPreProcessors = (
3939
};
4040

4141
const shouldHaveReorderColumn = props.rowReordering;
42-
const haveReorderColumn = columnsState.lookup[reorderColumn.field] != null;
42+
const hasReorderColumn = columnsState.lookup[reorderColumn.field] != null;
4343

44-
if (shouldHaveReorderColumn && haveReorderColumn) {
45-
columnsState.lookup[reorderColumn.field] = {
46-
...reorderColumn,
47-
...columnsState.lookup[reorderColumn.field],
48-
};
49-
return columnsState;
50-
}
51-
52-
if (shouldHaveReorderColumn && !haveReorderColumn) {
44+
if (shouldHaveReorderColumn && !hasReorderColumn) {
5345
columnsState.lookup[reorderColumn.field] = reorderColumn;
5446
columnsState.orderedFields = [reorderColumn.field, ...columnsState.orderedFields];
55-
} else if (!shouldHaveReorderColumn && haveReorderColumn) {
47+
} else if (!shouldHaveReorderColumn && hasReorderColumn) {
5648
delete columnsState.lookup[reorderColumn.field];
5749
columnsState.orderedFields = columnsState.orderedFields.filter(
5850
(field) => field !== reorderColumn.field,
5951
);
52+
} else if (shouldHaveReorderColumn && hasReorderColumn) {
53+
columnsState.lookup[reorderColumn.field] = {
54+
...reorderColumn,
55+
...columnsState.lookup[reorderColumn.field],
56+
};
57+
// If the column is not in the columns array (not a custom reorder column), move it to the beginning of the column order
58+
if (!props.columns.some((col) => col.field === GRID_REORDER_COL_DEF.field)) {
59+
columnsState.orderedFields = [
60+
reorderColumn.field,
61+
...columnsState.orderedFields.filter((field) => field !== reorderColumn.field),
62+
];
63+
}
6064
}
6165

6266
return columnsState;
6367
},
64-
[privateApiRef, classes, props.rowReordering],
68+
[privateApiRef, classes, props.columns, props.rowReordering],
6569
);
6670

6771
useGridRegisterPipeProcessor(privateApiRef, 'hydrateColumns', updateReorderColumn);

packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridDataSourceTreeDataPreProcessors.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
GridRenderCellParams,
88
GridDataSourceGroupNode,
99
GridRowId,
10-
GRID_CHECKBOX_SELECTION_FIELD,
1110
} from '@mui/x-data-grid';
1211
import {
1312
GridPipeProcessor,
@@ -111,12 +110,7 @@ export const useGridDataSourceTreeDataPreProcessors = (
111110
}
112111
columnsState.lookup[groupingColDefField] = newGroupingColumn;
113112
if (prevGroupingColumn == null) {
114-
const index = columnsState.orderedFields[0] === GRID_CHECKBOX_SELECTION_FIELD ? 1 : 0;
115-
columnsState.orderedFields = [
116-
...columnsState.orderedFields.slice(0, index),
117-
groupingColDefField,
118-
...columnsState.orderedFields.slice(index),
119-
];
113+
columnsState.orderedFields = [groupingColDefField, ...columnsState.orderedFields];
120114
}
121115
} else if (!shouldHaveGroupingColumn && prevGroupingColumn) {
122116
delete columnsState.lookup[groupingColDefField];

packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeDataPreProcessors.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
GridRenderCellParams,
88
GridGroupNode,
99
GridRowId,
10-
GRID_CHECKBOX_SELECTION_FIELD,
1110
} from '@mui/x-data-grid';
1211
import {
1312
GridPipeProcessor,
@@ -111,12 +110,7 @@ export const useGridTreeDataPreProcessors = (
111110
}
112111
columnsState.lookup[groupingColDefField] = newGroupingColumn;
113112
if (prevGroupingColumn == null) {
114-
const index = columnsState.orderedFields[0] === GRID_CHECKBOX_SELECTION_FIELD ? 1 : 0;
115-
columnsState.orderedFields = [
116-
...columnsState.orderedFields.slice(0, index),
117-
groupingColDefField,
118-
...columnsState.orderedFields.slice(index),
119-
];
113+
columnsState.orderedFields = [groupingColDefField, ...columnsState.orderedFields];
120114
}
121115
} else if (!shouldHaveGroupingColumn && prevGroupingColumn) {
122116
delete columnsState.lookup[groupingColDefField];

packages/x-data-grid/src/hooks/core/pipeProcessing/useGridPipeProcessing.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ type Cache = {
1414
};
1515

1616
type GroupCache = {
17-
processors: Map<string, GridPipeProcessor<any>>;
17+
processors: Map<string, GridPipeProcessor<any> | null>;
1818
processorsAsArray: GridPipeProcessor<any>[];
1919
appliers: {
2020
[applierId: string]: () => void;
@@ -81,15 +81,17 @@ export const useGridPipeProcessing = (apiRef: RefObject<GridPrivateApiCommon>) =
8181
const oldProcessor = groupCache.processors.get(id);
8282
if (oldProcessor !== processor) {
8383
groupCache.processors.set(id, processor);
84-
groupCache.processorsAsArray = Array.from(cache.current[group]!.processors.values());
84+
groupCache.processorsAsArray = Array.from(cache.current[group]!.processors.values()).filter(
85+
(processorValue) => processorValue !== null,
86+
);
8587
runAppliers(groupCache);
8688
}
8789

8890
return () => {
89-
cache.current[group]!.processors.delete(id);
91+
cache.current[group]!.processors.set(id, null);
9092
cache.current[group]!.processorsAsArray = Array.from(
9193
cache.current[group]!.processors.values(),
92-
);
94+
).filter((processorValue) => processorValue !== null);
9395
};
9496
},
9597
[runAppliers],

0 commit comments

Comments
 (0)