[data grid] Fix getColumn return type not reflecting that it can return undefined#23165
Conversation
Deploy previewBundle size
Check out the code infra dashboard for more information about this PR. |
| if (!column) { | ||
| return null; | ||
| } |
There was a problem hiding this comment.
Keep in mind this is "breaking change" as it changes the typescript. It corrects it though. In a few places internally we are already checking for presence, possibly our users are too.
IDK what is the pattern we follow in these cases though. On charts we generally used to accept it as a bug, and this fixes the bug.
| * @returns {{GridStateColDef | undefined}} The [[GridStateColDef]], or `undefined` if the field is not in the current column set. | ||
| */ | ||
| getColumn: (field: string) => GridStateColDef; | ||
| getColumn: (field: string) => GridStateColDef | undefined; |
There was a problem hiding this comment.
This is fine, because field is a string, which means that you can always ask for something you didn't define.
We should not update other API and helpers that need GridColDef (getRowValue, getRowFormattedValue, ...) to accept undefined as well, just because this type changed.
Those don't make sense if there is no column, and the guards should be placed in the code before those are called to make sure that we have a column definition.
There was a problem hiding this comment.
If I understand you correctly, having GridColDef | undefined doesn't make sense, but getColumn is ok to return undefined?
There was a problem hiding this comment.
I meant as an input in other places.
If I need a formatted value of the column, then the parameter should be GridColDef, not GridColDef | undefined
This particular line is fine. My comment was for the changes related to this change
There was a problem hiding this comment.
I've updated the code, let me know if there any missing pieces 😆
Revert widening of getRowValue/getRowFormattedValue/getCellParamsForRow and getAvailableAggregationFunctions to accept an undefined colDef. Those APIs are meaningless without a column definition, so callers guard instead. getCellParams/getColumnHeaderParams keep their previous runtime behavior (see mui#22831) via a cast at the params boundary.
There was a problem hiding this comment.
Pull request overview
This PR updates the Data Grid column API typing so gridColumnApi.getColumn accurately reflects runtime behavior by allowing undefined when a field is not present during column-set transitions, and then hardens internal call sites and docs accordingly.
Changes:
- Update
GridColumnApi.getColumnreturn type and JSDoc toGridStateColDef | undefined. - Add guards / optional chaining across sorting, filtering, editing, column resize/reorder, and premium features to avoid
TypeErrorduring transitions. - Update generated API docs JSON and docs demo code paths that call
getColumn.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/x-data-grid/src/models/api/gridColumnApi.ts | Widen getColumn return type and update documentation. |
| packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts | Early-return when getColumn returns undefined. |
| packages/x-data-grid/src/hooks/features/rows/useGridParamsApi.ts | Adjust params construction to compile with getColumn possibly returning undefined. |
| packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx | Avoid dereferencing filterOperators when the column is missing. |
| packages/x-data-grid/src/hooks/features/editing/useGridRowEditing.ts | Add null-guards for column-dependent editing logic. |
| packages/x-data-grid/src/hooks/features/editing/useGridCellEditing.ts | Add null-guards for column-dependent editing logic. |
| packages/x-data-grid/src/hooks/features/columns/useGridColumns.tsx | Make setColumnIndex a no-op when the field is unknown. |
| packages/x-data-grid/src/hooks/features/columnResize/useGridColumnResize.tsx | Bail out of resize start when column definition is missing. |
| packages/x-data-grid/src/components/cell/GridEditLongTextCell.tsx | Guard valueParser access when getColumn returns undefined. |
| packages/x-data-grid/src/components/cell/GridEditInputCell.tsx | Guard valueParser access when getColumn returns undefined. |
| packages/x-data-grid-pro/src/hooks/features/columnReorder/useGridColumnReorder.tsx | Guard reorder event/logic when dragged/target column is missing. |
| packages/x-data-grid-premium/src/hooks/features/chartsIntegration/useGridChartsIntegration.tsx | Avoid spreading a potentially missing grouped column definition. |
| packages/x-data-grid-premium/src/hooks/features/cellSelection/useGridCellSelection.ts | Guard actions-column checks when column is missing. |
| packages/x-data-grid-premium/src/components/GridGroupingCriteriaCell.tsx | Guard renderCell access when grouped column definition is missing. |
| packages/x-data-grid-premium/src/components/chartsPanel/data/GridChartsPanelDataField.tsx | Avoid calling aggregation discovery with an undefined column. |
| docs/pages/x/api/data-grid/grid-api.json | Update API docs to reflect getColumn returning undefined. |
| docs/data/data-grid/components/filter-panel/FilterPanelTriggerDescription.tsx | Guard docs demo logic when getColumn returns undefined. |
| docs/data/data-grid/components/filter-panel/FilterPanelTriggerDescription.js | Generated JS counterpart updated to match the TSX demo behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| * Returns the [[GridColDef]] for the given `field`. | ||
| * @param {string} field The column field. | ||
| * @returns {{GridStateColDef}} The [[GridStateColDef]]. | ||
| * @returns {{GridStateColDef | undefined}} The [[GridStateColDef]], or `undefined` if the field is not in the current column set. |
| const getColumnHeaderParams = React.useCallback<GridParamsApi['getColumnHeaderParams']>( | ||
| (field) => ({ | ||
| field, | ||
| colDef: apiRef.current.getColumn(field), | ||
| colDef: apiRef.current.getColumn(field) as GridStateColDef, | ||
| }), |
There was a problem hiding this comment.
@arminmeh I think we have two approaches we can do here:
- Make
GridCellParams['colDef']allowundefined - Make the functions
getCellParams/getColumnHeaderParamsthrowMissingColumnErrorlike thegetRowParams. But this changes behaviour, and instead of users checking for undefined they would need to use a try/catch 😬
Technically throwing has precedence in the getRowParams, so technically it makes sense, but the side effects for the user might be too big for a "bugfix"
Do you have any preference?
There was a problem hiding this comment.
Well, they allowed undefined before, just not explicitly, so we should keep it that way. Throwing an error would have a much larger impact. We can work on the alignment in the next major.
There was a problem hiding this comment.
Should be good, also updated some demos that needed it with the change
There was a problem hiding this comment.
I didn't realize that the impact was so big.
Now we have a situation where renderEditCell params send colDef as undefined, which also doesn't make sense, because it is an edit renderer for this column. I can't be in the renderer if the column doesn't exist 😀
There was a problem hiding this comment.
I have looked at it as well. Seems that, for now, the best option is to case (like it was before the big change)
| return apiRef.current.getCellParamsForRow<any, any, any, any>(id, field, row, { | ||
| colDef: | ||
| props.listView && props.listViewColumn?.field === field | ||
| ? gridListColumnSelector(apiRef)! | ||
| : apiRef.current.getColumn(field), | ||
| // Params keep a non-nullable `colDef`, but it can be `undefined` at runtime for an unknown field. | ||
| colDef: (props.listView && props.listViewColumn?.field === field | ||
| ? gridListColumnSelector(apiRef)! | ||
| : apiRef.current.getColumn(field)) as GridStateColDef, | ||
| rowNode, |
arminmeh
left a comment
There was a problem hiding this comment.
LGTM, appart from the grid params API changes. I have the same questions as Copilot.
Do we allow undefined for those params? If so, we should align their return type.
| dataFieldName: column.field, | ||
| depth: rowGroupingModel.indexOf(column.field), | ||
| }; | ||
| if (columnDefinition) { |
There was a problem hiding this comment.
Note:
We shouldn't be able to reach this line with columnDefinition === undefined, since there is isColumnGrouped check above. But we can keep the check to avoid casting.
There was a problem hiding this comment.
now that we are here, this condition is unnecessary.
At line 345, isColumnGrouped is truthy, which means that we can just assign getRowGroupingFieldFromGroupingCriteria result to it.
getCellParams/getColumnHeaderParams return params for any field string, so their colDef is undefined when the field is not in the current column set. Type it as such instead of casting, and guard the consumers that dereference it.
… into fix/datagrid-getcolumn-nullable
getColumn return type not reflecting that it can return undefined
Fixes #23143
Summary
gridColumnApi.getColumnis implemented as a bare lookup (gridColumnLookupSelector(apiRef)[field]) and returnsundefinedfor any field absent from the lookup — a reachable state during a column set transition — but its declared type promises a non-nullableGridStateColDef. Downstream TypeScript consumers dereferencing the result as the type invites crash with aTypeErrorat runtime, and the type can't be corrected downstream (return types are covariant in the api interface constraints).getColumnas(field: string) => GridStateColDef | undefinedto match the implementation, and update the JSDoc. This matchesgetRow/getRowNode, which are already typed nullable.undefinedfalls through safely, early return where the operation is meaningless without the column (sorting, reordering, resizing, editing a removed column — these paths previously threwTypeError).setColumnIndexwith an unknown field previously moved the last column viasplice(-1, 1); it is now a no-op.getRowValue/getRowFormattedValue/getCellParamsForRowcolDefparameter types to| undefined— the implementations already guard it.getCellParams/getColumnHeaderParamsruntime behavior is unchanged (params may carry anundefinedcolDefduring a transition, as before; the public params types are untouched).Note: the
getColumnreturn type change is compile-time breaking for strict TypeScript consumers that dereference the result without a guard — flagging in case it should wait for a major or get a changelog callout.