Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
3 changes: 1 addition & 2 deletions docs/data/data-grid/pivoting/GridGetPivotDerivedColumns.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ const columns = [
type: 'date',
headerName: 'Transaction Date',
width: 140,
valueGetter: (value) => new Date(value),
groupingValueGetter: (value) => value,
valueGetter: (value) => (value ? new Date(value) : null),
},
{ field: 'ticker', headerName: 'Ticker' },
{
Expand Down
3 changes: 1 addition & 2 deletions docs/data/data-grid/pivoting/GridGetPivotDerivedColumns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ const columns: GridColDef[] = [
type: 'date',
headerName: 'Transaction Date',
width: 140,
valueGetter: (value) => new Date(value),
groupingValueGetter: (value) => value,
valueGetter: (value) => (value ? new Date(value) : null),
},
{ field: 'ticker', headerName: 'Ticker' },
{
Expand Down
3 changes: 1 addition & 2 deletions docs/data/data-grid/pivoting/GridPivotingFinancial.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ const columns = [
type: 'date',
headerName: 'Transaction Date',
width: 140,
valueGetter: (value) => new Date(value),
groupingValueGetter: (value) => value,
valueGetter: (value) => (value ? new Date(value) : null),
},
{ field: 'ticker', headerName: 'Ticker' },
{
Expand Down
3 changes: 1 addition & 2 deletions docs/data/data-grid/pivoting/GridPivotingFinancial.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ const columns: GridColDef[] = [
type: 'date',
headerName: 'Transaction Date',
width: 140,
valueGetter: (value) => new Date(value),
groupingValueGetter: (value) => value,
Copy link
Contributor

@arminmeh arminmeh Aug 28, 2025

Choose a reason for hiding this comment

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

Should we update the docs at https://mui.com/x/react-data-grid/row-grouping/#using-groupingvaluegetter-for-complex-grouping-value to state that Date is also allowed as a grouping value?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.
Also updated the demo for groupingValueGetter, because now groupingValueGetter is not necessary if you have valueGetter, unless you want to use a different value for grouping than the one in the cell.
https://deploy-preview-18967--material-ui-x.netlify.app/x/react-data-grid/row-grouping/#using-groupingvaluegetter-for-complex-grouping-value
WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, that is a nice addition

valueGetter: (value) => (value ? new Date(value) : null),
},
{ field: 'ticker', headerName: 'Ticker' },
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,13 @@ export default function RowGroupingGroupingValueGetter() {
field: 'composer',
headerName: 'Composer',
valueGetter: (value) => value.name,
groupingValueGetter: (value) => value.name,
Copy link
Member Author

Choose a reason for hiding this comment

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

@arminmeh
I think I fixed all use cases, but now the valueGetter is being called first, and the result is passed to groupingValueGetter (if provided). What do you think about this approach?

This might break for someone, but it's kindof a bugfix.

Copy link
Contributor

Choose a reason for hiding this comment

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

It could be confusing, since value can either be a value from the row or the return value from the column's valueGetter.

As mentioned before, the problem is that we are using groupingValueGetter() to generate the group key and the column value.
Since the requirement is that the key is string | number | null | undefined, it can't be a Date, which is a requirement for the date column.

So, we could update the default formatter
https://github.com/mui/mui-x/blob/v8.10.2/packages/x-data-grid/src/colDef/gridDateColDef.ts#L30
not to check for Date if the row is autogenerated.

Copy link
Member Author

Choose a reason for hiding this comment

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

not to check for Date if the row is autogenerated

Hmm, I'll try that

width: 200,
},
{
field: 'decade',
headerName: 'Decade',
valueGetter: (value, row) => Math.floor(row.year / 10) * 10,
groupingValueGetter: (value, row) => Math.floor(row.year / 10) * 10,
renderCell: (params) => {
if (params.value == null) {
return '';
}

return `${params.value.toString().slice(-2)}'s`;
},
valueFormatter: (value) => (value ? `${value.toString().slice(-2)}'s` : ''),
},
],
[data.columns],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,15 @@ export default function RowGroupingGroupingValueGetter() {
field: 'composer',
headerName: 'Composer',
valueGetter: (value: { name: string }) => value.name,
groupingValueGetter: (value: { name: string }) => value.name,
width: 200,
} as GridColDef<Movie, string>,
{
field: 'decade',
headerName: 'Decade',
valueGetter: (value, row) => Math.floor(row.year / 10) * 10,
groupingValueGetter: (value, row) => Math.floor(row.year / 10) * 10,
renderCell: (params) => {
if (params.value == null) {
return '';
}

return `${params.value.toString().slice(-2)}'s`;
},
} as GridColDef<Movie, number>,
valueFormatter: (value: number | null) =>
value ? `${value.toString().slice(-2)}'s` : '',
} as GridColDef<Movie, number, string>,
],
[data.columns],
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import {
GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD,
} from './gridRowGroupingUtils';
import { gridRowGroupingSanitizedModelSelector } from './gridRowGroupingSelector';
import type { GridRowGroupingModel } from './gridRowGroupingInterfaces';
import type { DataGridPremiumProcessedProps } from '../../../models/dataGridPremiumProps';

const GROUPING_COL_DEF_DEFAULT_PROPERTIES: Omit<GridColDef, 'field'> = {
...GRID_STRING_COL_DEF,
Expand Down Expand Up @@ -110,13 +112,53 @@ const groupedByColValueFormatter: (
return value;
};

const getGroupingCriteriaProperties = (groupedByColDef: GridColDef, applyHeaderName: boolean) => {
function getGroupingCriteriaProperties(
groupedByColDef: GridColDef,
rowGroupingColumnMode: 'single',
rowGroupingModel: GridRowGroupingModel,
columnsLookup: GridColumnRawLookup,
): Partial<GridColDef>;
function getGroupingCriteriaProperties(
groupedByColDef: GridColDef,
rowGroupingColumnMode: 'multiple',
): Partial<GridColDef>;
function getGroupingCriteriaProperties(
groupedByColDef: GridColDef,
rowGroupingColumnMode: DataGridPremiumProcessedProps['rowGroupingColumnMode'],
rowGroupingModel: GridRowGroupingModel = [],
columnsLookup: GridColumnRawLookup = {},
): Partial<GridColDef> {
let valueFormatter: GridColDef['valueFormatter'] | undefined;

if (rowGroupingColumnMode === 'single' && rowGroupingModel.length > 1) {
// In single column grouping mode, the `valueFormatter` of the grouping column uses
// value formatters from original columns for each of the grouping criteria
Comment on lines +134 to +135
Copy link
Member Author

Choose a reason for hiding this comment

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

I think this is the only way valueFormatter could work in rowGroupingColumnMode="single".

valueFormatter = (value, row, column, apiRef) => {
const rowId = gridRowIdSelector(apiRef, row);
const rowNode = gridRowNodeSelector(apiRef, rowId);
if (rowNode.type === 'group') {
const originalColDef = columnsLookup[rowNode.groupingField!]!;
if (originalColDef.type === 'singleSelect') {
// the default valueFormatter of a singleSelect colDef won't work with the grouping column values
return value;
}
const columnValueFormatter = originalColDef.valueFormatter;
if (typeof columnValueFormatter === 'function') {
return columnValueFormatter(value as never, row, column, apiRef);
}
}
return value;
};
} else {
valueFormatter = groupedByColDef.valueFormatter
? groupedByColValueFormatter(groupedByColDef)
: undefined;
}

const properties: Partial<GridColDef> = {
sortable: groupedByColDef.sortable,
filterable: groupedByColDef.filterable,
valueFormatter: groupedByColDef.valueFormatter
? groupedByColValueFormatter(groupedByColDef)
: undefined,
valueFormatter,
valueOptions: isSingleSelectColDef(groupedByColDef) ? groupedByColDef.valueOptions : undefined,
sortComparator: (v1, v2, cellParams1, cellParams2) => {
// We only want to sort the groups of the current grouping criteria
Expand All @@ -134,12 +176,13 @@ const getGroupingCriteriaProperties = (groupedByColDef: GridColDef, applyHeaderN
filterOperators: groupedByColDef.filterOperators,
};

const applyHeaderName = !(rowGroupingColumnMode === 'single' && rowGroupingModel.length > 1);
if (applyHeaderName) {
properties.headerName = groupedByColDef.headerName ?? groupedByColDef.field;
}

return properties;
};
}

interface CreateGroupingColDefMonoCriteriaParams {
columnsLookup: GridColumnRawLookup;
Expand Down Expand Up @@ -253,11 +296,11 @@ export const createGroupingColDefForOneGroupingCriteria = ({
// By default, we apply the sorting / filtering on the groups of this column's grouping criteria based on the properties of `groupedColDef`.
let sourceProperties: Partial<GridColDef>;
if (mainGroupingCriteria && mainGroupingCriteria === groupingCriteria) {
sourceProperties = getGroupingCriteriaProperties(groupedByColDef, true);
sourceProperties = getGroupingCriteriaProperties(groupedByColDef, 'multiple');
} else if (leafColDef) {
sourceProperties = getLeafProperties(leafColDef);
} else {
sourceProperties = getGroupingCriteriaProperties(groupedByColDef, true);
sourceProperties = getGroupingCriteriaProperties(groupedByColDef, 'multiple');
}

// The properties that can't be overridden with `colDefOverride`
Expand All @@ -281,7 +324,7 @@ interface CreateGroupingColDefSeveralCriteriaParams {
/**
* The fields from which we are grouping the rows.
*/
rowGroupingModel: string[];
rowGroupingModel: GridRowGroupingModel;
/**
* The col def properties the user wants to override.
* This value comes `prop.groupingColDef`.
Expand Down Expand Up @@ -379,13 +422,20 @@ export const createGroupingColDefForAllGroupingCriteria = ({
// By default, we apply the sorting / filtering on the groups of the top level grouping criteria based on the properties of `columnsLookup[orderedGroupedByFields[0]]`.
let sourceProperties: Partial<GridColDef>;
if (mainGroupingCriteria && rowGroupingModel.includes(mainGroupingCriteria)) {
sourceProperties = getGroupingCriteriaProperties(columnsLookup[mainGroupingCriteria], true);
sourceProperties = getGroupingCriteriaProperties(
columnsLookup[mainGroupingCriteria],
'single',
rowGroupingModel,
columnsLookup,
);
} else if (leafColDef) {
sourceProperties = getLeafProperties(leafColDef);
} else {
sourceProperties = getGroupingCriteriaProperties(
columnsLookup[rowGroupingModel[0]],
rowGroupingModel.length === 1,
'single',
rowGroupingModel,
columnsLookup,
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,11 @@ export const getCellGroupingCriteria = ({
}) => {
let key: GridKeyValue | null | undefined;
if (groupingRule.groupingValueGetter) {
key = groupingRule.groupingValueGetter(row[groupingRule.field] as never, row, colDef, apiRef);
key = row[groupingRule.field];
if (colDef.valueGetter) {
key = colDef.valueGetter(key as never, row, colDef, apiRef);
}
key = groupingRule.groupingValueGetter(key as never, row, colDef, apiRef);
} else {
key = getRowValue(row, colDef, apiRef) as GridKeyValue | null | undefined;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1529,4 +1529,39 @@ describe('<DataGridPremium /> - Row grouping', () => {

expect(getColumnValues(0)).to.deep.equal(['2025 (1)', 'MUI (1)', '']);
});

it('should use the valueFormatter of the respective original column for each of the grouping criteria in single column grouping mode', () => {
render(
<Test
columns={[
{ field: 'id' },
{
field: 'date',
type: 'date',
valueGetter: (value) => (value ? new Date(value) : null),
},
{
field: 'price',
type: 'number',
valueFormatter: (value: number | undefined) => (value ? `$${value.toFixed(2)}` : null),
},
]}
rows={[
{ id: 1, date: '2025-01-01', price: 100 },
{ id: 2, date: '2025-01-02', price: 200 },
]}
rowGroupingModel={['price', 'date']}
defaultGroupingExpansionDepth={-1}
/>,
);

expect(getColumnValues(0)).to.deep.equal([
'$100.00 (1)',
'1/1/2025 (1)',
'',
'$200.00 (1)',
'1/2/2025 (1)',
'',
]);
});
});
Loading