((props, ref) => {
return ;
});
-const RowWithTracer = React.forwardRef((props, ref) => {
- return ;
+const CellWithTracer = React.forwardRef((props, ref) => {
+ return ;
});
-const ColumnHeadersWithTracer = React.forwardRef((props, ref) => {
- return ;
-});
-
-const MemoizedRow = React.memo(RowWithTracer);
-const MemoizedColumnHeaders = React.memo(ColumnHeadersWithTracer);
+const slots = {
+ cell: CellWithTracer,
+};
-export default function GridWithReactMemo() {
+export default function GridVisualization() {
const { data } = useDemoData({
dataSet: 'Commodity',
rowLength: 100,
@@ -57,7 +54,7 @@ export default function GridWithReactMemo() {
}),
},
'&&& .updating': {
- backgroundColor: 'rgb(92 199 68 / 25%)',
+ backgroundColor: 'rgb(92 199 68 / 20%)',
outline: '1px solid rgb(92 199 68 / 35%)',
outlineOffset: '-1px',
transition: 'none',
@@ -69,10 +66,7 @@ export default function GridWithReactMemo() {
rowHeight={38}
checkboxSelection
disableRowSelectionOnClick
- slots={{
- row: MemoizedRow,
- columnHeaders: MemoizedColumnHeaders,
- }}
+ slots={slots}
/>
);
diff --git a/docs/data/data-grid/performance/GridWithReactMemo.tsx.preview b/docs/data/data-grid/performance/GridVisualization.tsx.preview
similarity index 53%
rename from docs/data/data-grid/performance/GridWithReactMemo.tsx.preview
rename to docs/data/data-grid/performance/GridVisualization.tsx.preview
index f1bd1aea7658a..beb57bed0de15 100644
--- a/docs/data/data-grid/performance/GridWithReactMemo.tsx.preview
+++ b/docs/data/data-grid/performance/GridVisualization.tsx.preview
@@ -3,8 +3,5 @@
rowHeight={38}
checkboxSelection
disableRowSelectionOnClick
- slots={{
- row: MemoizedRow,
- columnHeaders: MemoizedColumnHeaders,
- }}
+ slots={slots}
/>
\ No newline at end of file
diff --git a/docs/data/data-grid/performance/performance.md b/docs/data/data-grid/performance/performance.md
index a26c29d8d89d4..1eaa98dcf03e5 100644
--- a/docs/data/data-grid/performance/performance.md
+++ b/docs/data/data-grid/performance/performance.md
@@ -2,43 +2,51 @@
Improve the performance of the DataGrid using the recommendations from this guide.
-## Memoize inner components with `React.memo`
+## Extract static objects and memoize root props
-The `DataGrid` component is composed of a central state object where all data is stored.
-When an API method is called, a prop changes, or the user interacts with the UI (e.g. filtering a column), this state object is updated with the changes made.
-To reflect the changes in the interface, the component must re-render.
-Since the state behaves like `React.useState`, the `DataGrid` component will re-render its children, including column headers, rows, and cells.
-With smaller datasets, this is not a problem for concern, but it can become a bottleneck if the number of rows increases, especially if many columns render [custom content](/x/react-data-grid/column-definition/#rendering-cells).
-One way to overcome this issue is using `React.memo` to only re-render the child components when their props have changed.
-To start using memoization, import the inner components, then pass their memoized version to the respective slots, as follow:
+The `DataGrid` component uses `React.memo` to optimize its performance, which means itself and its subcomponents only
+re-render when their props change. But it's very easy to cause unnecessary re-renders if the root props of your
+`DataGrid` aren't memoized. Take the example below, the `slots` and `initialState` objects are re-created on every
+render, which means the `DataGrid` itself has no choice but to re-render as well.
```tsx
-import {
- GridRow,
- GridColumnHeaders,
- DataGrid, // or DataGridPro, DataGridPremium
-} from '@mui/x-data-grid';
-
-const MemoizedRow = React.memo(GridRow);
-const MemoizedColumnHeaders = React.memo(GridColumnHeaders);
-
-;
+function Component({ rows }) {
+ return (
+
+ );
+}
```
-The following demo show this trick in action.
-It also contains additional logic to highlight the components when they re-render.
+An easy way to prevent re-renders is to extract any object that can be a static object, and to memoize any object that
+depends on another object. This applies to any prop that is an object or a function.
-{{"demo": "GridWithReactMemo.js", "bg": "inline", "defaultCodeOpen": false}}
+```tsx
+const slots = {
+ row: CustomRow,
+};
+
+function Component({ rows }) {
+ const cellModesModel = React.useMemo(
+ () => ({ [rows[0].id]: { name: { mode: GridCellModes.Edit } } }),
+ [rows],
+ );
+
+ return ;
+}
+```
+
+## Visualization
+
+The DataGrid memoizes some of its subcomponents to avoid re-rendering more than needed. Below is a visualization that
+shows you which cells re-render in reaction to your interaction with the grid.
-:::warning
-We do not ship the components above already wrapped with `React.memo` because if you have rows whose cells display custom content not derived from the received props, e.g. selectors, these cells may display outdated information.
-If you define a column with a custom cell renderer where content comes from a [selector](/x/react-data-grid/state/#catalog-of-selectors) that changes more often than the props passed to `GridRow`, the row component should not be memoized.
-:::
+{{"demo": "GridVisualization.js", "bg": "inline", "defaultCodeOpen": false}}
## API
diff --git a/docs/data/data-grid/state/state.md b/docs/data/data-grid/state/state.md
index dc3501325c658..fc6d84fb87cd1 100644
--- a/docs/data/data-grid/state/state.md
+++ b/docs/data/data-grid/state/state.md
@@ -54,7 +54,8 @@ const paginationModel = gridPaginationModelSelector(
### With useGridSelector
-If you only need to access the state value in the render of your components, use the `useGridSelector` hook:
+If you only need to access the state value in the render of your components, use the `useGridSelector` hook.
+This hook ensures there is a reactive binding such that when the state changes, the component in which this hook is used is re-rendered.
```tsx
const paginationModel = useGridSelector(apiRef, gridPaginationModelSelector);
diff --git a/package.json b/package.json
index ae3f0e572f0a1..d403cad6d8609 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
"l10n": "babel-node -x .ts ./scripts/l10n.ts",
"jsonlint": "node ./scripts/jsonlint.mjs",
"eslint": "eslint . --cache --report-unused-disable-directives --ext .js,.ts,.tsx --max-warnings 0",
+ "eslint:fix": "yarn eslint --fix",
"eslint:ci": "eslint . --report-unused-disable-directives --ext .js,.ts,.tsx --max-warnings 0",
"markdownlint": "markdownlint-cli2 \"**/*.md\"",
"postinstall": "patch-package",
diff --git a/packages/grid/x-data-grid-pro/src/tests/rowEditing.DataGridPro.test.tsx b/packages/grid/x-data-grid-pro/src/tests/rowEditing.DataGridPro.test.tsx
index 16abc7fa74224..cd7361ff0898f 100644
--- a/packages/grid/x-data-grid-pro/src/tests/rowEditing.DataGridPro.test.tsx
+++ b/packages/grid/x-data-grid-pro/src/tests/rowEditing.DataGridPro.test.tsx
@@ -168,7 +168,7 @@ describe(' - Row Editing', () => {
apiRef.current.setEditCellValue({ id: 0, field: 'currencyPair', value: ' usdgbp ' }),
);
await act(() => apiRef.current.setEditCellValue({ id: 0, field: 'price1M', value: 100 }));
- expect(renderEditCell1.lastCall.args[0].row).to.deep.equal({
+ expect(renderEditCell2.lastCall.args[0].row).to.deep.equal({
...defaultData.rows[0],
currencyPair: 'usdgbp',
price1M: 100,
diff --git a/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx b/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx
index b3cf08ab69b37..2791f94b63071 100644
--- a/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx
+++ b/packages/grid/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx
@@ -300,15 +300,12 @@ describe(' - Rows', () => {
);
}
- // For some reason the number of renders in test env is 2x the number of renders in the browser
- const renrederMultiplier = 2;
-
render();
const initialRendersCount = 2;
- expect(renderCellSpy.callCount).to.equal(initialRendersCount * renrederMultiplier);
+ expect(renderCellSpy.callCount).to.equal(initialRendersCount);
act(() => apiRef.current.updateRows([{ id: 1, name: 'John' }]));
- expect(renderCellSpy.callCount).to.equal((initialRendersCount + 2) * renrederMultiplier);
+ expect(renderCellSpy.callCount).to.equal(initialRendersCount + 2);
});
});
diff --git a/packages/grid/x-data-grid/src/components/GridColumnHeaders.tsx b/packages/grid/x-data-grid/src/components/GridColumnHeaders.tsx
index 2ab53c7bebf0b..917099255831a 100644
--- a/packages/grid/x-data-grid/src/components/GridColumnHeaders.tsx
+++ b/packages/grid/x-data-grid/src/components/GridColumnHeaders.tsx
@@ -1,5 +1,6 @@
import * as React from 'react';
import PropTypes from 'prop-types';
+import { fastMemo } from '../utils/fastMemo';
import {
useGridColumnHeaders,
UseGridColumnHeadersProps,
@@ -113,4 +114,6 @@ GridColumnHeaders.propTypes = {
visibleColumns: PropTypes.arrayOf(PropTypes.object).isRequired,
} as any;
-export { GridColumnHeaders };
+const MemoizedGridColumnHeaders = fastMemo(GridColumnHeaders);
+
+export { MemoizedGridColumnHeaders as GridColumnHeaders };
diff --git a/packages/grid/x-data-grid/src/components/GridRow.tsx b/packages/grid/x-data-grid/src/components/GridRow.tsx
index 066f0421356a0..125bb597a189c 100644
--- a/packages/grid/x-data-grid/src/components/GridRow.tsx
+++ b/packages/grid/x-data-grid/src/components/GridRow.tsx
@@ -5,8 +5,9 @@ import {
unstable_composeClasses as composeClasses,
unstable_useForkRef as useForkRef,
} from '@mui/utils';
+import { fastMemo } from '../utils/fastMemo';
import { GridRowEventLookup } from '../models/events';
-import { GridRowId, GridRowModel, GridTreeNodeWithRender } from '../models/gridRows';
+import { GridRowId, GridRowModel } from '../models/gridRows';
import { GridEditModes, GridRowModes, GridCellModes } from '../models/gridEditRowModel';
import { useGridApiContext } from '../hooks/utils/useGridApiContext';
import { getDataGridUtilityClass, gridClasses } from '../constants/gridClasses';
@@ -14,18 +15,18 @@ import { useGridRootProps } from '../hooks/utils/useGridRootProps';
import type { DataGridProcessedProps } from '../models/props/DataGridProps';
import { GridStateColDef } from '../models/colDef/gridColDef';
import { gridColumnsTotalWidthSelector } from '../hooks/features/columns/gridColumnsSelector';
-import { useGridSelector } from '../hooks/utils/useGridSelector';
+import { useGridSelector, objectShallowCompare } from '../hooks/utils/useGridSelector';
import { GridRowClassNameParams } from '../models/params/gridRowParams';
import { useGridVisibleRows } from '../hooks/utils/useGridVisibleRows';
import { findParentElementFromClassName } from '../utils/domUtils';
import { GRID_CHECKBOX_SELECTION_COL_DEF } from '../colDef/gridCheckboxSelectionColDef';
import { GRID_ACTIONS_COLUMN_TYPE } from '../colDef/gridActionsColDef';
-import { GridRenderEditCellParams } from '../models/params/gridCellParams';
import { GRID_DETAIL_PANEL_TOGGLE_FIELD } from '../constants/gridDetailPanelToggleField';
import { gridSortModelSelector } from '../hooks/features/sorting/gridSortingSelector';
import { gridRowMaximumTreeDepthSelector } from '../hooks/features/rows/gridRowsSelector';
import { gridColumnGroupsHeaderMaxDepthSelector } from '../hooks/features/columnGrouping/gridColumnGroupsSelector';
import { randomNumberBetween } from '../utils/utils';
+import { GridCellWrapper, GridCellV7 } from './cell/GridCell';
import type { GridCellProps } from './cell/GridCell';
import { gridEditRowsStateSelector } from '../hooks/features/editing/gridEditingSelectors';
@@ -256,141 +257,51 @@ const GridRow = React.forwardRef(function GridRow(
[apiRef, onClick, publish, rowId],
);
- const {
- slots,
- slotProps,
- classes: rootClasses,
- disableColumnReorder,
- getCellClassName,
- } = rootProps;
+ const { slots, slotProps, disableColumnReorder } = rootProps;
+ const CellComponent = slots.cell === GridCellV7 ? GridCellV7 : GridCellWrapper;
const rowReordering = (rootProps as any).rowReordering as boolean;
- const CellComponent = slots.cell;
-
- const getCell = React.useCallback(
- (
- column: GridStateColDef,
- cellProps: Pick<
- GridCellProps,
- 'width' | 'colSpan' | 'showRightBorder' | 'indexRelativeToAllColumns'
- >,
- ) => {
- const cellParams = apiRef.current.getCellParams(
- rowId,
- column.field,
- );
-
- const classNames = apiRef.current.unstable_applyPipeProcessors('cellClassName', [], {
- id: rowId,
- field: column.field,
- });
-
- const disableDragEvents =
- (disableColumnReorder && column.disableReorder) ||
- (!rowReordering &&
- !!sortModel.length &&
- treeDepth > 1 &&
- Object.keys(editRowsState).length > 0);
-
- if (column.cellClassName) {
- classNames.push(
- clsx(
- typeof column.cellClassName === 'function'
- ? column.cellClassName(cellParams)
- : column.cellClassName,
- ),
- );
- }
-
- const editCellState = editRowsState[rowId] ? editRowsState[rowId][column.field] : null;
- let content: React.ReactNode;
-
- if (editCellState == null && column.renderCell) {
- content = column.renderCell({ ...cellParams, api: apiRef.current });
- // TODO move to GridCell
- classNames.push(
- clsx(gridClasses['cell--withRenderer'], rootClasses?.['cell--withRenderer']),
- );
- }
-
- if (editCellState != null && column.renderEditCell) {
- const updatedRow = apiRef.current.getRowWithUpdatedValues(rowId, column.field);
-
- // eslint-disable-next-line @typescript-eslint/naming-convention
- const { changeReason, unstable_updateValueOnRender, ...editCellStateRest } = editCellState;
-
- const params: GridRenderEditCellParams = {
- ...cellParams,
- row: updatedRow,
- ...editCellStateRest,
- api: apiRef.current,
- };
-
- content = column.renderEditCell(params);
- // TODO move to GridCell
- classNames.push(clsx(gridClasses['cell--editing'], rootClasses?.['cell--editing']));
- }
-
- if (getCellClassName) {
- // TODO move to GridCell
- classNames.push(getCellClassName(cellParams));
- }
+ const getCell = (
+ column: GridStateColDef,
+ cellProps: Pick<
+ GridCellProps,
+ 'width' | 'colSpan' | 'showRightBorder' | 'indexRelativeToAllColumns'
+ >,
+ ) => {
+ const disableDragEvents =
+ (disableColumnReorder && column.disableReorder) ||
+ (!rowReordering &&
+ !!sortModel.length &&
+ treeDepth > 1 &&
+ Object.keys(editRowsState).length > 0);
+
+ const editCellState = editRowsState[rowId]?.[column.field] ?? null;
+
+ return (
+
+ );
+ };
- const hasFocus = focusedCell === column.field;
- const tabIndex = tabbableCell === column.field ? 0 : -1;
-
- const isSelected = apiRef.current.unstable_applyPipeProcessors('isCellSelected', false, {
- id: rowId,
- field: column.field,
- });
-
- return (
-
- {content}
-
- );
- },
- [
- apiRef,
- rowId,
- disableColumnReorder,
- rowReordering,
- sortModel.length,
- treeDepth,
- editRowsState,
- getCellClassName,
- focusedCell,
- tabbableCell,
- CellComponent,
- rowHeight,
- slotProps?.cell,
- rootClasses,
- ],
+ const sizes = useGridSelector(
+ apiRef,
+ () => ({ ...apiRef.current.unstable_getRowInternalSizes(rowId) }),
+ objectShallowCompare,
);
- const sizes = apiRef.current.unstable_getRowInternalSizes(rowId);
-
let minHeight = rowHeight;
if (minHeight === 'auto' && sizes) {
let numberOfBaseSizes = 0;
@@ -448,7 +359,13 @@ const GridRow = React.forwardRef(function GridRow(
}
const randomNumber = randomNumberBetween(10000, 20, 80);
- const rowType = apiRef.current.getRowNode(rowId)!.type;
+
+ const rowNode = apiRef.current.getRowNode(rowId);
+ if (!rowNode) {
+ return null;
+ }
+
+ const rowType = rowNode.type;
const cells: JSX.Element[] = [];
for (let i = 0; i < renderedColumns.length; i += 1) {
@@ -475,7 +392,7 @@ const GridRow = React.forwardRef(function GridRow(
const contentWidth = Math.round(randomNumber());
cells.push(
- {
+type GridCellV7Props = {
align: GridAlignment;
className?: string;
colIndex: number;
- field: string;
+ column: GridColDef;
rowId: GridRowId;
- formattedValue?: F;
- hasFocus?: boolean;
height: number | 'auto';
- isEditable?: boolean;
- isSelected?: boolean;
showRightBorder?: boolean;
- value?: V;
width: number;
- cellMode?: GridCellMode;
- children: React.ReactNode;
- tabIndex: 0 | -1;
colSpan?: number;
disableDragEvents?: boolean;
+ editCellState: GridEditCellProps | null;
onClick?: React.MouseEventHandler;
onDoubleClick?: React.MouseEventHandler;
onMouseDown?: React.MouseEventHandler;
@@ -50,23 +54,53 @@ export interface GridCellProps {
onDragEnter?: React.DragEventHandler;
onDragOver?: React.DragEventHandler;
[x: string]: any;
-}
-
-// Based on https://stackoverflow.com/a/59518678
-let cachedSupportsPreventScroll: boolean;
-function doesSupportPreventScroll(): boolean {
- if (cachedSupportsPreventScroll === undefined) {
- document.createElement('div').focus({
- get preventScroll() {
- cachedSupportsPreventScroll = true;
- return false;
- },
- });
- }
- return cachedSupportsPreventScroll;
-}
+};
+
+type GridCellWrapperProps = GridCellV7Props;
+
+export type GridCellProps = GridCellWrapperProps & {
+ field: string;
+ formattedValue?: F;
+ hasFocus?: boolean;
+ isEditable?: boolean;
+ isSelected?: boolean;
+ value?: V;
+ cellMode?: GridCellMode;
+ children: React.ReactNode;
+ tabIndex: 0 | -1;
+};
-type OwnerState = Pick & {
+type CellParamsWithAPI = GridCellParams & {
+ api: GridApiCommunity;
+};
+const EMPTY_CELL_PARAMS: CellParamsWithAPI = {
+ id: -1,
+ field: '__unset__',
+ row: {},
+ rowNode: {
+ id: -1,
+ depth: 0,
+ type: 'leaf',
+ parent: -1,
+ groupingKey: null,
+ },
+ colDef: {
+ type: 'string',
+ field: '__unset__',
+ computedWidth: 0,
+ },
+ cellMode: GridCellModes.View,
+ hasFocus: false,
+ tabIndex: -1,
+ value: null,
+ formattedValue: '__unset__',
+ isEditable: false,
+ api: {} as any,
+};
+
+type OwnerState = Pick & {
+ isEditable?: boolean;
+ isSelected?: boolean;
classes?: DataGridProcessedProps['classes'];
};
@@ -90,12 +124,131 @@ const useUtilityClasses = (ownerState: OwnerState) => {
let warnedOnce = false;
+// GridCellWrapper is a compatibility layer for the V6 cell slot. If we can use the more efficient
+// `GridCellV7`, we should. That component is a merge of `GridCellWrapper` and `GridCell`.
+// TODO(v7): Remove the wrapper & cellV6 and use the cellV7 exclusively.
+// TODO(v7): Removing the wrapper will break the docs performance visualization demo.
+const GridCellWrapper = React.forwardRef((props, ref) => {
+ const { column, rowId, editCellState } = props;
+
+ const apiRef = useGridApiContext();
+ const rootProps = useGridRootProps();
+
+ const field = column.field;
+
+ const cellParamsWithAPI = useGridSelector(
+ apiRef,
+ () => {
+ // This is required because `.getCellParams` tries to get the `state.rows.tree` entry
+ // associated with `rowId`/`fieldId`, but this selector runs after the state has been
+ // updated, while `rowId`/`fieldId` reference an entry in the old state.
+ try {
+ const cellParams = apiRef.current.getCellParams(
+ rowId,
+ field,
+ );
+
+ const result = cellParams as CellParamsWithAPI;
+ result.api = apiRef.current;
+ return result;
+ } catch (e) {
+ if (e instanceof MissingRowIdError) {
+ return EMPTY_CELL_PARAMS;
+ }
+ throw e;
+ }
+ },
+ objectShallowCompare,
+ );
+
+ const isSelected = useGridSelector(apiRef, () =>
+ apiRef.current.unstable_applyPipeProcessors('isCellSelected', false, {
+ id: rowId,
+ field,
+ }),
+ );
+
+ if (cellParamsWithAPI === EMPTY_CELL_PARAMS) {
+ return null;
+ }
+
+ const { cellMode, hasFocus, isEditable, value, formattedValue } = cellParamsWithAPI;
+
+ const managesOwnFocus = column.type === 'actions';
+ const tabIndex =
+ (cellMode === 'view' || !isEditable) && !managesOwnFocus ? cellParamsWithAPI.tabIndex : -1;
+
+ const { classes: rootClasses, getCellClassName } = rootProps;
+
+ const classNames = apiRef.current.unstable_applyPipeProcessors('cellClassName', [], {
+ id: rowId,
+ field,
+ }) as (string | undefined)[];
+
+ if (column.cellClassName) {
+ classNames.push(
+ typeof column.cellClassName === 'function'
+ ? column.cellClassName(cellParamsWithAPI)
+ : column.cellClassName,
+ );
+ }
+
+ if (getCellClassName) {
+ classNames.push(getCellClassName(cellParamsWithAPI));
+ }
+
+ let children: React.ReactNode;
+ if (editCellState == null && column.renderCell) {
+ children = column.renderCell(cellParamsWithAPI);
+ classNames.push(gridClasses['cell--withRenderer']);
+ classNames.push(rootClasses?.['cell--withRenderer']);
+ }
+
+ if (editCellState != null && column.renderEditCell) {
+ const updatedRow = apiRef.current.getRowWithUpdatedValues(rowId, column.field);
+
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ const { changeReason, unstable_updateValueOnRender, ...editCellStateRest } = editCellState;
+
+ const params: GridRenderEditCellParams = {
+ ...cellParamsWithAPI,
+ row: updatedRow,
+ ...editCellStateRest,
+ };
+
+ children = column.renderEditCell(params);
+ classNames.push(gridClasses['cell--editing']);
+ classNames.push(rootClasses?.['cell--editing']);
+ }
+
+ const { slots } = rootProps;
+
+ const CellComponent = slots.cell;
+
+ const cellProps: GridCellProps = {
+ ...props,
+ ref,
+ field,
+ formattedValue,
+ hasFocus,
+ isEditable,
+ isSelected,
+ value,
+ cellMode,
+ children,
+ tabIndex,
+ className: clsx(classNames),
+ };
+
+ return React.createElement(CellComponent, cellProps);
+});
+
const GridCell = React.forwardRef((props, ref) => {
const {
align,
- children,
+ children: childrenProp,
colIndex,
- colDef,
+ column,
cellMode,
field,
formattedValue,
@@ -130,7 +283,6 @@ const GridCell = React.forwardRef((props, ref) =>
const handleRef = useForkRef(ref, cellRef);
const focusElementRef = React.useRef(null);
const apiRef = useGridApiContext();
-
const rootProps = useGridRootProps();
const ownerState = { align, showRightBorder, isEditable, classes: rootProps.classes, isSelected };
const classes = useUtilityClasses(ownerState);
@@ -235,25 +387,21 @@ const GridCell = React.forwardRef((props, ref) =>
};
}
- const column = apiRef.current.getColumn(field);
const managesOwnFocus = column.type === 'actions';
- const renderChildren = () => {
- if (children === undefined) {
- const valueString = valueToRender?.toString();
- return (
-
- {valueString}
-
- );
- }
-
- if (React.isValidElement(children) && managesOwnFocus) {
- return React.cloneElement(children, { focusElementRef });
- }
+ let children: React.ReactNode = childrenProp;
+ if (children === undefined) {
+ const valueString = valueToRender?.toString();
+ children = (
+
+ {valueString}
+
+ );
+ }
- return children;
- };
+ if (React.isValidElement(children) && managesOwnFocus) {
+ children = React.cloneElement(children, { focusElementRef });
+ }
const draggableEventHandlers = disableDragEvents
? null
@@ -272,7 +420,7 @@ const GridCell = React.forwardRef((props, ref) =>
aria-colindex={colIndex + 1}
aria-colspan={colSpan}
style={style}
- tabIndex={(cellMode === 'view' || !isEditable) && !managesOwnFocus ? tabIndex : -1}
+ tabIndex={tabIndex}
onClick={publish('cellClick', onClick)}
onDoubleClick={publish('cellDoubleClick', onDoubleClick)}
onMouseOver={publish('cellMouseOver', onMouseOver)}
@@ -284,12 +432,36 @@ const GridCell = React.forwardRef((props, ref) =>
{...other}
onFocus={handleFocus}
>
- {renderChildren()}
+ {children}
);
});
-const MemoizedCell = React.memo(GridCell);
+const MemoizedCellWrapper = fastMemo(GridCellWrapper);
+
+GridCellWrapper.propTypes = {
+ // ----------------------------- Warning --------------------------------
+ // | These PropTypes are generated from the TypeScript type definitions |
+ // | To update them edit the TypeScript types and run "yarn proptypes" |
+ // ----------------------------------------------------------------------
+ align: PropTypes.oneOf(['center', 'left', 'right']),
+ className: PropTypes.string,
+ colIndex: PropTypes.number,
+ colSpan: PropTypes.number,
+ column: PropTypes.object,
+ disableDragEvents: PropTypes.bool,
+ height: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number]),
+ onClick: PropTypes.func,
+ onDoubleClick: PropTypes.func,
+ onDragEnter: PropTypes.func,
+ onDragOver: PropTypes.func,
+ onKeyDown: PropTypes.func,
+ onMouseDown: PropTypes.func,
+ onMouseUp: PropTypes.func,
+ rowId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
+ showRightBorder: PropTypes.bool,
+ width: PropTypes.number,
+} as any;
GridCell.propTypes = {
// ----------------------------- Warning --------------------------------
@@ -302,13 +474,319 @@ GridCell.propTypes = {
className: PropTypes.string,
colIndex: PropTypes.number,
colSpan: PropTypes.number,
+ column: PropTypes.object,
+ disableDragEvents: PropTypes.bool,
+ editCellState: PropTypes.shape({
+ changeReason: PropTypes.oneOf(['debouncedSetEditCellValue', 'setEditCellValue']),
+ isProcessingProps: PropTypes.bool,
+ isValidating: PropTypes.bool,
+ value: PropTypes.any,
+ }),
+ height: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number]),
+ onClick: PropTypes.func,
+ onDoubleClick: PropTypes.func,
+ onDragEnter: PropTypes.func,
+ onDragOver: PropTypes.func,
+ onKeyDown: PropTypes.func,
+ onMouseDown: PropTypes.func,
+ onMouseUp: PropTypes.func,
+ rowId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
+ showRightBorder: PropTypes.bool,
+ width: PropTypes.number,
+} as any;
+
+export { MemoizedCellWrapper as GridCellWrapper, GridCell };
+
+const GridCellV7 = React.forwardRef((props, ref) => {
+ const {
+ column,
+ rowId,
+ editCellState,
+ align,
+ children: childrenProp,
+ colIndex,
+ height,
+ width,
+ className,
+ showRightBorder,
+ extendRowFullWidth,
+ row,
+ colSpan,
+ disableDragEvents,
+ onClick,
+ onDoubleClick,
+ onMouseDown,
+ onMouseUp,
+ onMouseOver,
+ onKeyDown,
+ onKeyUp,
+ onDragEnter,
+ onDragOver,
+ ...other
+ } = props;
+
+ const apiRef = useGridApiContext();
+ const rootProps = useGridRootProps();
+
+ const field = column.field;
+
+ const cellParamsWithAPI = useGridSelector(
+ apiRef,
+ () => {
+ // This is required because `.getCellParams` tries to get the `state.rows.tree` entry
+ // associated with `rowId`/`fieldId`, but this selector runs after the state has been
+ // updated, while `rowId`/`fieldId` reference an entry in the old state.
+ try {
+ const cellParams = apiRef.current.getCellParams(
+ rowId,
+ field,
+ );
+
+ const result = cellParams as CellParamsWithAPI;
+ result.api = apiRef.current;
+ return result;
+ } catch (e) {
+ if (e instanceof MissingRowIdError) {
+ return EMPTY_CELL_PARAMS;
+ }
+ throw e;
+ }
+ },
+ objectShallowCompare,
+ );
+
+ const isSelected = useGridSelector(apiRef, () =>
+ apiRef.current.unstable_applyPipeProcessors('isCellSelected', false, {
+ id: rowId,
+ field,
+ }),
+ );
+
+ const { cellMode, hasFocus, isEditable, value, formattedValue } = cellParamsWithAPI;
+
+ const managesOwnFocus = column.type === 'actions';
+ const tabIndex =
+ (cellMode === 'view' || !isEditable) && !managesOwnFocus ? cellParamsWithAPI.tabIndex : -1;
+
+ const { classes: rootClasses, getCellClassName } = rootProps;
+
+ const classNames = apiRef.current.unstable_applyPipeProcessors('cellClassName', [], {
+ id: rowId,
+ field,
+ }) as (string | undefined)[];
+
+ if (column.cellClassName) {
+ classNames.push(
+ typeof column.cellClassName === 'function'
+ ? column.cellClassName(cellParamsWithAPI)
+ : column.cellClassName,
+ );
+ }
+
+ if (getCellClassName) {
+ classNames.push(getCellClassName(cellParamsWithAPI));
+ }
+
+ const valueToRender = formattedValue == null ? value : formattedValue;
+ const cellRef = React.useRef(null);
+ const handleRef = useForkRef(ref, cellRef);
+ const focusElementRef = React.useRef(null);
+ const ownerState = { align, showRightBorder, isEditable, classes: rootProps.classes, isSelected };
+ const classes = useUtilityClasses(ownerState);
+
+ const publishMouseUp = React.useCallback(
+ (eventName: GridEvents) => (event: React.MouseEvent) => {
+ const params = apiRef.current.getCellParams(rowId, field || '');
+ apiRef.current.publishEvent(eventName as any, params as any, event);
+
+ if (onMouseUp) {
+ onMouseUp(event);
+ }
+ },
+ [apiRef, field, onMouseUp, rowId],
+ );
+
+ const publishMouseDown = React.useCallback(
+ (eventName: GridEvents) => (event: React.MouseEvent) => {
+ const params = apiRef.current.getCellParams(rowId, field || '');
+ apiRef.current.publishEvent(eventName as any, params as any, event);
+
+ if (onMouseDown) {
+ onMouseDown(event);
+ }
+ },
+ [apiRef, field, onMouseDown, rowId],
+ );
+
+ const publish = React.useCallback(
+ (eventName: keyof GridCellEventLookup, propHandler: any) =>
+ (event: React.SyntheticEvent) => {
+ // The row might have been deleted during the click
+ if (!apiRef.current.getRow(rowId)) {
+ return;
+ }
+
+ const params = apiRef.current.getCellParams(rowId!, field || '');
+ apiRef.current.publishEvent(eventName, params, event as any);
+
+ if (propHandler) {
+ propHandler(event);
+ }
+ },
+ [apiRef, field, rowId],
+ );
+
+ const style = {
+ minWidth: width,
+ maxWidth: width,
+ minHeight: height,
+ maxHeight: height === 'auto' ? 'none' : height, // max-height doesn't support "auto"
+ };
+
+ React.useEffect(() => {
+ if (!hasFocus || cellMode === GridCellModes.Edit) {
+ return;
+ }
+
+ const doc = ownerDocument(apiRef.current.rootElementRef!.current)!;
+
+ if (cellRef.current && !cellRef.current.contains(doc.activeElement!)) {
+ const focusableElement = cellRef.current!.querySelector('[tabindex="0"]');
+ const elementToFocus = focusElementRef.current || focusableElement || cellRef.current;
+
+ if (doesSupportPreventScroll()) {
+ elementToFocus.focus({ preventScroll: true });
+ } else {
+ const scrollPosition = apiRef.current.getScrollPosition();
+ elementToFocus.focus();
+ apiRef.current.scroll(scrollPosition);
+ }
+ }
+ }, [hasFocus, cellMode, apiRef]);
+
+ if (cellParamsWithAPI === EMPTY_CELL_PARAMS) {
+ return null;
+ }
+
+ let handleFocus: any = other.onFocus;
+
+ if (
+ process.env.NODE_ENV === 'test' &&
+ rootProps.experimentalFeatures?.warnIfFocusStateIsNotSynced
+ ) {
+ handleFocus = (event: React.FocusEvent) => {
+ const focusedCell = gridFocusCellSelector(apiRef);
+ if (focusedCell?.id === rowId && focusedCell.field === field) {
+ if (typeof other.onFocus === 'function') {
+ other.onFocus(event);
+ }
+ return;
+ }
+
+ if (!warnedOnce) {
+ console.warn(
+ [
+ `MUI: The cell with id=${rowId} and field=${field} received focus.`,
+ `According to the state, the focus should be at id=${focusedCell?.id}, field=${focusedCell?.field}.`,
+ "Not syncing the state may cause unwanted behaviors since the `cellFocusIn` event won't be fired.",
+ 'Call `fireEvent.mouseUp` before the `fireEvent.click` to sync the focus with the state.',
+ ].join('\n'),
+ );
+
+ warnedOnce = true;
+ }
+ };
+ }
+
+ let children: React.ReactNode;
+ if (editCellState == null && column.renderCell) {
+ children = column.renderCell(cellParamsWithAPI);
+ classNames.push(gridClasses['cell--withRenderer']);
+ classNames.push(rootClasses?.['cell--withRenderer']);
+ }
+
+ if (editCellState != null && column.renderEditCell) {
+ const updatedRow = apiRef.current.getRowWithUpdatedValues(rowId, column.field);
+
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ const { changeReason, unstable_updateValueOnRender, ...editCellStateRest } = editCellState;
+
+ const params: GridRenderEditCellParams = {
+ ...cellParamsWithAPI,
+ row: updatedRow,
+ ...editCellStateRest,
+ };
+
+ children = column.renderEditCell(params);
+ classNames.push(gridClasses['cell--editing']);
+ classNames.push(rootClasses?.['cell--editing']);
+ }
+
+ if (children === undefined) {
+ const valueString = valueToRender?.toString();
+ children = (
+
+ {valueString}
+
+ );
+ }
+
+ if (React.isValidElement(children) && managesOwnFocus) {
+ children = React.cloneElement(children, { focusElementRef });
+ }
+
+ const draggableEventHandlers = disableDragEvents
+ ? null
+ : {
+ onDragEnter: publish('cellDragEnter', onDragEnter),
+ onDragOver: publish('cellDragOver', onDragOver),
+ };
+
+ return (
+
+ {children}
+
+ );
+});
+
+GridCellV7.propTypes = {
+ // ----------------------------- Warning --------------------------------
+ // | These PropTypes are generated from the TypeScript type definitions |
+ // | To update them edit the TypeScript types and run "yarn proptypes" |
+ // ----------------------------------------------------------------------
+ align: PropTypes.oneOf(['center', 'left', 'right']),
+ className: PropTypes.string,
+ colIndex: PropTypes.number,
+ colSpan: PropTypes.number,
+ column: PropTypes.object,
disableDragEvents: PropTypes.bool,
- field: PropTypes.string,
- formattedValue: PropTypes.any,
- hasFocus: PropTypes.bool,
+ editCellState: PropTypes.shape({
+ changeReason: PropTypes.oneOf(['debouncedSetEditCellValue', 'setEditCellValue']),
+ isProcessingProps: PropTypes.bool,
+ isValidating: PropTypes.bool,
+ value: PropTypes.any,
+ }),
height: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number]),
- isEditable: PropTypes.bool,
- isSelected: PropTypes.bool,
onClick: PropTypes.func,
onDoubleClick: PropTypes.func,
onDragEnter: PropTypes.func,
@@ -318,9 +796,9 @@ GridCell.propTypes = {
onMouseUp: PropTypes.func,
rowId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
showRightBorder: PropTypes.bool,
- tabIndex: PropTypes.oneOf([-1, 0]),
- value: PropTypes.any,
width: PropTypes.number,
} as any;
-export { MemoizedCell as GridCell };
+const MemoizedGridCellV7 = fastMemo(GridCellV7);
+
+export { MemoizedGridCellV7 as GridCellV7 };
diff --git a/packages/grid/x-data-grid/src/components/cell/index.ts b/packages/grid/x-data-grid/src/components/cell/index.ts
index 2a54901cc8c11..c92abc5571bba 100644
--- a/packages/grid/x-data-grid/src/components/cell/index.ts
+++ b/packages/grid/x-data-grid/src/components/cell/index.ts
@@ -1,4 +1,5 @@
-export * from './GridCell';
+export { GridCell } from './GridCell';
+export type { GridCellProps } from './GridCell';
export * from './GridBooleanCell';
export * from './GridEditBooleanCell';
export * from './GridEditDateCell';
diff --git a/packages/grid/x-data-grid/src/components/menu/columnMenu/GridColumnHeaderMenu.tsx b/packages/grid/x-data-grid/src/components/menu/columnMenu/GridColumnHeaderMenu.tsx
index 5c067a68b37f5..6785a115ba93c 100644
--- a/packages/grid/x-data-grid/src/components/menu/columnMenu/GridColumnHeaderMenu.tsx
+++ b/packages/grid/x-data-grid/src/components/menu/columnMenu/GridColumnHeaderMenu.tsx
@@ -40,7 +40,7 @@ function GridColumnHeaderMenu({
[apiRef, target],
);
- if (!target) {
+ if (!target || !colDef) {
return null;
}
diff --git a/packages/grid/x-data-grid/src/components/virtualization/GridVirtualScroller.tsx b/packages/grid/x-data-grid/src/components/virtualization/GridVirtualScroller.tsx
index 00da9b239deb2..4390ebb329ea4 100644
--- a/packages/grid/x-data-grid/src/components/virtualization/GridVirtualScroller.tsx
+++ b/packages/grid/x-data-grid/src/components/virtualization/GridVirtualScroller.tsx
@@ -36,16 +36,15 @@ const GridVirtualScroller = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes & { sx?: SxProps }
>(function GridVirtualScroller(props, ref) {
- const { className, ...other } = props;
const rootProps = useGridRootProps();
const classes = useUtilityClasses(rootProps);
return (
);
});
diff --git a/packages/grid/x-data-grid/src/components/virtualization/GridVirtualScrollerContent.tsx b/packages/grid/x-data-grid/src/components/virtualization/GridVirtualScrollerContent.tsx
index 47d3bc2fff0df..95e05d1a48a55 100644
--- a/packages/grid/x-data-grid/src/components/virtualization/GridVirtualScrollerContent.tsx
+++ b/packages/grid/x-data-grid/src/components/virtualization/GridVirtualScrollerContent.tsx
@@ -6,10 +6,10 @@ import { useGridRootProps } from '../../hooks/utils/useGridRootProps';
import { getDataGridUtilityClass } from '../../constants/gridClasses';
import { DataGridProcessedProps } from '../../models/props/DataGridProps';
-type OwnerState = DataGridProcessedProps & { overflowedContent: boolean };
+type OwnerState = DataGridProcessedProps;
-const useUtilityClasses = (ownerState: OwnerState) => {
- const { classes, overflowedContent } = ownerState;
+const useUtilityClasses = (props: DataGridProcessedProps, overflowedContent: boolean) => {
+ const { classes } = props;
const slots = {
root: ['virtualScrollerContent', overflowedContent && 'virtualScrollerContent--overflowed'],
@@ -28,21 +28,16 @@ const GridVirtualScrollerContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes & { sx?: SxProps }
>(function GridVirtualScrollerContent(props, ref) {
- const { className, style, ...other } = props;
const rootProps = useGridRootProps();
- const ownerState = {
- ...rootProps,
- overflowedContent: !rootProps.autoHeight && style?.minHeight === 'auto',
- };
- const classes = useUtilityClasses(ownerState);
+ const overflowedContent = !rootProps.autoHeight && props.style?.minHeight === 'auto';
+ const classes = useUtilityClasses(rootProps, overflowedContent);
return (
);
});
diff --git a/packages/grid/x-data-grid/src/constants/defaultGridSlotsComponents.ts b/packages/grid/x-data-grid/src/constants/defaultGridSlotsComponents.ts
index 784b5b052bea5..def1b9bea037f 100644
--- a/packages/grid/x-data-grid/src/constants/defaultGridSlotsComponents.ts
+++ b/packages/grid/x-data-grid/src/constants/defaultGridSlotsComponents.ts
@@ -1,6 +1,5 @@
import { GridSlotsComponent } from '../models';
import {
- GridCell,
GridSkeletonCell,
GridColumnsPanel,
GridFilterPanel,
@@ -13,6 +12,7 @@ import {
GridRow,
GridColumnHeaderFilterIconButton,
} from '../components';
+import { GridCellV7 } from '../components/cell/GridCell';
import { GridColumnHeaders } from '../components/GridColumnHeaders';
import { GridColumnMenu } from '../components/menu/columnMenu/GridColumnMenu';
import { GridNoResultsOverlay } from '../components/GridNoResultsOverlay';
@@ -22,7 +22,7 @@ import materialSlots from '../material';
// Remove then need to call `uncapitalizeObjectKeys`.
export const DATA_GRID_DEFAULT_SLOTS_COMPONENTS: GridSlotsComponent = {
...materialSlots,
- Cell: GridCell,
+ Cell: GridCellV7,
SkeletonCell: GridSkeletonCell,
ColumnHeaderFilterIconButton: GridColumnHeaderFilterIconButton,
ColumnMenu: GridColumnMenu,
diff --git a/packages/grid/x-data-grid/src/hooks/core/useGridApiInitialization.ts b/packages/grid/x-data-grid/src/hooks/core/useGridApiInitialization.ts
index c5086ff638db9..ddcb9ef60e86f 100644
--- a/packages/grid/x-data-grid/src/hooks/core/useGridApiInitialization.ts
+++ b/packages/grid/x-data-grid/src/hooks/core/useGridApiInitialization.ts
@@ -1,4 +1,5 @@
import * as React from 'react';
+import { Store } from '../../utils/Store';
import { useGridApiMethod } from '../utils/useGridApiMethod';
import { GridSignature } from '../utils/useGridApiEventHandler';
import { DataGridProcessedProps } from '../../models/props/DataGridProps';
@@ -60,8 +61,11 @@ export function useGridApiInitialization<
const publicApiRef = React.useRef() as React.MutableRefObject;
if (!publicApiRef.current) {
+ const state = {} as Api['state'];
+
publicApiRef.current = {
- state: {} as Api['state'],
+ state,
+ store: Store.create(state),
instanceId: { id: globalId },
} as Api;
diff --git a/packages/grid/x-data-grid/src/hooks/core/useGridStateInitialization.ts b/packages/grid/x-data-grid/src/hooks/core/useGridStateInitialization.ts
index ffcf0197ee047..860d4d5055783 100644
--- a/packages/grid/x-data-grid/src/hooks/core/useGridStateInitialization.ts
+++ b/packages/grid/x-data-grid/src/hooks/core/useGridStateInitialization.ts
@@ -19,12 +19,7 @@ export const useGridStateInitialization = ['registerControlState']
>((controlStateItem) => {
- const { stateId, ...others } = controlStateItem;
-
- controlStateMapRef.current[stateId] = {
- ...others,
- stateId,
- };
+ controlStateMapRef.current[controlStateItem.stateId] = controlStateItem;
}, []);
const setState = React.useCallback['setState']>(
@@ -87,6 +82,8 @@ export const useGridStateInitialization = {
const rootProps = useGridRootProps();
const innerRef = React.useRef(null);
const handleInnerRef = useForkRef(innerRefProp, innerRef);
- const [renderContext, setRenderContext] = React.useState(null);
+ const [renderContext, setRenderContextRaw] = React.useState(null);
const prevRenderContext = React.useRef(renderContext);
const prevScrollLeft = React.useRef(0);
const currentPage = useGridVisibleRows(apiRef, rootProps);
const totalHeaderHeight = getTotalHeaderHeight(apiRef, rootProps.columnHeaderHeight);
const headerHeight = Math.floor(rootProps.columnHeaderHeight * densityFactor);
+ const setRenderContext = React.useCallback(
+ (nextRenderContext: GridRenderContext | null) => {
+ if (
+ renderContext &&
+ nextRenderContext &&
+ areRenderContextsEqual(renderContext, nextRenderContext)
+ ) {
+ return;
+ }
+ setRenderContextRaw(nextRenderContext);
+ },
+ [renderContext],
+ );
+
React.useEffect(() => {
apiRef.current.columnHeadersContainerElementRef!.current!.scrollLeft = 0;
}, [apiRef]);
@@ -210,7 +227,7 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => {
updateInnerPosition(nextRenderContext);
}
},
- [updateInnerPosition],
+ [updateInnerPosition, setRenderContext],
);
const handleColumnResizeStart = React.useCallback>(
diff --git a/packages/grid/x-data-grid/src/hooks/features/focus/useGridFocus.ts b/packages/grid/x-data-grid/src/hooks/features/focus/useGridFocus.ts
index 992534803521e..33ee2e250583e 100644
--- a/packages/grid/x-data-grid/src/hooks/features/focus/useGridFocus.ts
+++ b/packages/grid/x-data-grid/src/hooks/features/focus/useGridFocus.ts
@@ -1,5 +1,6 @@
import * as React from 'react';
import { unstable_ownerDocument as ownerDocument } from '@mui/utils';
+import { gridClasses } from '../../../constants/gridClasses';
import { GridEventListener, GridEventLookup } from '../../../models/events';
import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity';
import { GridFocusApi, GridFocusPrivateApi } from '../../../models/api/gridFocusApi';
@@ -304,8 +305,6 @@ export const useGridFocus = (
[apiRef],
);
- const focussedColumnGroup = unstable_gridFocusColumnGroupHeaderSelector(apiRef);
-
const handleColumnGroupHeaderFocus = React.useCallback<
GridEventListener<'columnGroupHeaderFocus'>
>(
@@ -313,26 +312,38 @@ export const useGridFocus = (
if (event.target !== event.currentTarget) {
return;
}
+ const focusedColumnGroup = unstable_gridFocusColumnGroupHeaderSelector(apiRef);
if (
- focussedColumnGroup !== null &&
- focussedColumnGroup.depth === depth &&
- fields.includes(focussedColumnGroup.field)
+ focusedColumnGroup !== null &&
+ focusedColumnGroup.depth === depth &&
+ fields.includes(focusedColumnGroup.field)
) {
// This group cell has already been focused
return;
}
apiRef.current.setColumnGroupHeaderFocus(fields[0], depth, event);
},
- [apiRef, focussedColumnGroup],
+ [apiRef],
);
- const handleBlur = React.useCallback>(() => {
- logger.debug(`Clearing focus`);
- apiRef.current.setState((state) => ({
- ...state,
- focus: { cell: null, columnHeader: null, columnHeaderFilter: null, columnGroupHeader: null },
- }));
- }, [logger, apiRef]);
+ const handleBlur = React.useCallback>(
+ (_, ev) => {
+ if (ev.relatedTarget?.className.includes(gridClasses.columnHeader)) {
+ return;
+ }
+ logger.debug(`Clearing focus`);
+ apiRef.current.setState((state) => ({
+ ...state,
+ focus: {
+ cell: null,
+ columnHeader: null,
+ columnHeaderFilter: null,
+ columnGroupHeader: null,
+ },
+ }));
+ },
+ [logger, apiRef],
+ );
const handleCellMouseDown = React.useCallback>((params) => {
lastClickedCell.current = params;
diff --git a/packages/grid/x-data-grid/src/hooks/features/keyboardNavigation/useGridKeyboardNavigation.ts b/packages/grid/x-data-grid/src/hooks/features/keyboardNavigation/useGridKeyboardNavigation.ts
index d52065399e7a0..bf58d31341507 100644
--- a/packages/grid/x-data-grid/src/hooks/features/keyboardNavigation/useGridKeyboardNavigation.ts
+++ b/packages/grid/x-data-grid/src/hooks/features/keyboardNavigation/useGridKeyboardNavigation.ts
@@ -18,7 +18,6 @@ import { GridRowEntry, GridRowId } from '../../../models';
import { gridPinnedRowsSelector } from '../rows/gridRowsSelector';
import { unstable_gridFocusColumnGroupHeaderSelector } from '../focus';
import { gridColumnGroupsHeaderMaxDepthSelector } from '../columnGrouping/gridColumnGroupsSelector';
-import { useGridSelector } from '../../utils/useGridSelector';
import {
unstable_gridHeaderFilteringEditFieldSelector,
unstable_gridHeaderFilteringMenuSelector,
@@ -421,7 +420,6 @@ export const useGridKeyboardNavigation = (
],
);
- const focusedColumnGroup = useGridSelector(apiRef, unstable_gridFocusColumnGroupHeaderSelector);
const handleColumnGroupHeaderKeyDown = React.useCallback<
GridEventListener<'columnGroupHeaderKeyDown'>
>(
@@ -431,6 +429,7 @@ export const useGridKeyboardNavigation = (
return;
}
+ const focusedColumnGroup = unstable_gridFocusColumnGroupHeaderSelector(apiRef);
if (focusedColumnGroup === null) {
return;
}
@@ -517,15 +516,7 @@ export const useGridKeyboardNavigation = (
event.preventDefault();
}
},
- [
- apiRef,
- focusedColumnGroup,
- currentPageRows.length,
- goToHeader,
- goToGroupHeader,
- goToCell,
- getRowIdFromIndex,
- ],
+ [apiRef, currentPageRows.length, goToHeader, goToGroupHeader, goToCell, getRowIdFromIndex],
);
const handleCellKeyDown = React.useCallback>(
diff --git a/packages/grid/x-data-grid/src/hooks/features/rows/useGridParamsApi.ts b/packages/grid/x-data-grid/src/hooks/features/rows/useGridParamsApi.ts
index 67f86592f8354..a7b0df731337d 100644
--- a/packages/grid/x-data-grid/src/hooks/features/rows/useGridParamsApi.ts
+++ b/packages/grid/x-data-grid/src/hooks/features/rows/useGridParamsApi.ts
@@ -12,16 +12,7 @@ import {
import { useGridApiMethod } from '../../utils/useGridApiMethod';
import { gridFocusCellSelector, gridTabIndexCellSelector } from '../focus/gridFocusStateSelector';
-let warnedOnceMissingColumn = false;
-function warnMissingColumn(field: string) {
- console.warn(
- [
- `MUI: You are calling getValue('${field}') but the column \`${field}\` is not defined.`,
- `Instead, you can access the data from \`params.row.${field}\`.`,
- ].join('\n'),
- );
- warnedOnceMissingColumn = true;
-}
+export class MissingRowIdError extends Error {}
/**
* @requires useGridColumns (method)
@@ -45,7 +36,7 @@ export function useGridParamsApi(apiRef: React.MutableRefObject(id);
if (!row || !rowNode) {
- throw new Error(`No row with id #${id} found`);
+ throw new MissingRowIdError(`No row with id #${id} found`);
}
const cellFocus = gridFocusCellSelector(apiRef);
@@ -96,7 +87,7 @@ export function useGridParamsApi(apiRef: React.MutableRefObject {
const colDef = apiRef.current.getColumn(field);
- if (process.env.NODE_ENV !== 'production') {
- if (!colDef && !warnedOnceMissingColumn) {
- warnMissingColumn(field);
- }
- }
-
if (!colDef || !colDef.valueGetter) {
const rowModel = apiRef.current.getRow(id);
if (!rowModel) {
- throw new Error(`No row with id #${id} found`);
+ throw new MissingRowIdError(`No row with id #${id} found`);
}
return rowModel[field];
diff --git a/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx b/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx
index 2549a4dea8075..a2ee064c3f264 100644
--- a/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx
+++ b/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx
@@ -3,6 +3,7 @@ import * as ReactDOM from 'react-dom';
import {
unstable_useForkRef as useForkRef,
unstable_useEnhancedEffect as useEnhancedEffect,
+ unstable_useEventCallback as useEventCallback,
} from '@mui/utils';
import { useTheme } from '@mui/material/styles';
import { defaultMemoize } from 'reselect';
@@ -80,7 +81,10 @@ export const getRenderableIndexes = ({
];
};
-const areRenderContextsEqual = (context1: GridRenderContext, context2: GridRenderContext) => {
+export const areRenderContextsEqual = (
+ context1: GridRenderContext,
+ context2: GridRenderContext,
+) => {
if (context1 === context2) {
return true;
}
@@ -106,6 +110,10 @@ interface ContainerDimensions {
height: number | null;
}
+// The `maxSize` is 3 so that reselect caches the `renderedColumns` values for the pinned left,
+// unpinned, and pinned right sections.
+const MEMOIZE_OPTIONS = { maxSize: 3 };
+
export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => {
const apiRef = useGridPrivateApiContext();
const rootProps = useGridRootProps();
@@ -149,6 +157,7 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => {
(columns: GridStateColDef[], firstColumnToRender: number, lastColumnToRender: number) => {
return columns.slice(firstColumnToRender, lastColumnToRender);
},
+ MEMOIZE_OPTIONS,
),
);
@@ -369,7 +378,7 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => {
apiRef.current.publishEvent('scrollPositionChange', params);
}, [apiRef, computeRenderContext, containerDimensions.width, updateRenderContext]);
- const handleScroll = (event: React.UIEvent) => {
+ const handleScroll = useEventCallback((event: React.UIEvent) => {
const { scrollTop, scrollLeft } = event.currentTarget;
scrollPosition.current.top = scrollTop;
scrollPosition.current.left = scrollLeft;
@@ -432,15 +441,15 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => {
});
prevTotalWidth.current = columnsTotalWidth;
}
- };
+ });
- const handleWheel = (event: React.WheelEvent) => {
+ const handleWheel = useEventCallback((event: React.WheelEvent) => {
apiRef.current.publishEvent('virtualScrollerWheel', {}, event);
- };
+ });
- const handleTouchMove = (event: React.TouchEvent) => {
+ const handleTouchMove = useEventCallback((event: React.TouchEvent) => {
apiRef.current.publishEvent('virtualScrollerTouchMove', {}, event);
- };
+ });
const getRows = (
params: {
@@ -627,24 +636,36 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => {
minHeight: shouldExtendContent ? '100%' : 'auto',
};
+ if (rootProps.autoHeight && currentPage.rows.length === 0) {
+ size.height = getMinimalContentHeight(apiRef, rootProps.rowHeight); // Give room to show the overlay when there no rows.
+ }
+
return size;
- }, [rootRef, columnsTotalWidth, rowsMeta.currentPageTotalHeight, needsHorizontalScrollbar]);
+ }, [
+ apiRef,
+ rootRef,
+ columnsTotalWidth,
+ rowsMeta.currentPageTotalHeight,
+ needsHorizontalScrollbar,
+ rootProps.autoHeight,
+ rootProps.rowHeight,
+ currentPage.rows.length,
+ ]);
React.useEffect(() => {
apiRef.current.publishEvent('virtualScrollerContentSizeChange');
}, [apiRef, contentSize]);
- if (rootProps.autoHeight && currentPage.rows.length === 0) {
- contentSize.height = getMinimalContentHeight(apiRef, rootProps.rowHeight); // Give room to show the overlay when there no rows.
- }
-
- const rootStyle = {} as React.CSSProperties;
- if (!needsHorizontalScrollbar) {
- rootStyle.overflowX = 'hidden';
- }
- if (rootProps.autoHeight) {
- rootStyle.overflowY = 'hidden';
- }
+ const rootStyle = React.useMemo(() => {
+ const style = {} as React.CSSProperties;
+ if (!needsHorizontalScrollbar) {
+ style.overflowX = 'hidden';
+ }
+ if (rootProps.autoHeight) {
+ style.overflowY = 'hidden';
+ }
+ return style;
+ }, [needsHorizontalScrollbar, rootProps.autoHeight]);
const getRenderContext = React.useCallback((): GridRenderContext => {
return prevRenderContext.current!;
@@ -656,15 +677,17 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => {
renderContext,
updateRenderZonePosition,
getRows,
- getRootProps: ({ style = {}, ...other } = {}) => ({
+ getRootProps: (inputProps: { style?: object } = {}) => ({
ref: handleRef,
onScroll: handleScroll,
onWheel: handleWheel,
onTouchMove: handleTouchMove,
- style: { ...style, ...rootStyle },
- ...other,
+ ...inputProps,
+ style: inputProps.style ? { ...inputProps.style, ...rootStyle } : rootStyle,
+ }),
+ getContentProps: ({ style }: { style?: object } = {}) => ({
+ style: style ? { ...style, ...contentSize } : contentSize,
}),
- getContentProps: ({ style = {} } = {}) => ({ style: { ...style, ...contentSize } }),
getRenderZoneProps: () => ({ ref: renderZoneRef }),
};
};
diff --git a/packages/grid/x-data-grid/src/hooks/utils/index.ts b/packages/grid/x-data-grid/src/hooks/utils/index.ts
index 30ce9b567da3a..00201c605b971 100644
--- a/packages/grid/x-data-grid/src/hooks/utils/index.ts
+++ b/packages/grid/x-data-grid/src/hooks/utils/index.ts
@@ -1,6 +1,6 @@
export * from './useGridApiEventHandler';
export * from './useGridApiMethod';
export * from './useGridLogger';
-export * from './useGridSelector';
+export { useGridSelector } from './useGridSelector';
export * from './useGridNativeEventListener';
export * from './useFirstRender';
diff --git a/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts b/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts
index 5aaddf04fcf61..5902549e2e3f6 100644
--- a/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts
+++ b/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts
@@ -1,7 +1,15 @@
import * as React from 'react';
import { GridApiCommon } from '../../models/api/gridApiCommon';
import { OutputSelector } from '../../utils/createSelector';
+import { useLazyRef } from './useLazyRef';
+import { useOnMount } from './useOnMount';
import { buildWarning } from '../../utils/warning';
+import { fastObjectShallowCompare } from '../../utils/fastObjectShallowCompare';
+
+const stateNotInitializedWarning = buildWarning([
+ 'MUI: `useGridSelector` has been called before the initialization of the state.',
+ 'This hook can only be used inside the context of the grid.',
+]);
function isOutputSelector(
selector: any,
@@ -9,14 +17,25 @@ function isOutputSelector(
return selector.acceptsApiRef;
}
-const stateNotInitializedWarning = buildWarning([
- 'MUI: `useGridSelector` has been called before the initialization of the state.',
- 'This hook can only be used inside the context of the grid.',
-]);
+function applySelector(
+ apiRef: React.MutableRefObject,
+ selector: ((state: Api['state']) => T) | OutputSelector,
+) {
+ if (isOutputSelector(selector)) {
+ return selector(apiRef);
+ }
+ return selector(apiRef.current.state);
+}
+
+const defaultCompare = Object.is;
+export const objectShallowCompare = fastObjectShallowCompare;
+
+const createRefs = () => ({ state: null, equals: null, selector: null } as any);
export const useGridSelector = (
apiRef: React.MutableRefObject,
selector: ((state: Api['state']) => T) | OutputSelector,
+ equals: (a: T, b: T) => boolean = defaultCompare,
) => {
if (process.env.NODE_ENV !== 'production') {
if (!apiRef.current.state) {
@@ -24,9 +43,34 @@ export const useGridSelector = (
}
}
- if (isOutputSelector(selector)) {
- return selector(apiRef);
- }
+ const refs = useLazyRef<
+ {
+ state: T;
+ equals: typeof equals;
+ selector: typeof selector;
+ },
+ never
+ >(createRefs);
+ const didInit = refs.current.selector !== null;
- return selector(apiRef.current.state);
+ const [state, setState] = React.useState(
+ // We don't use an initialization function to avoid allocations
+ (didInit ? null : applySelector(apiRef, selector)) as T,
+ );
+
+ refs.current.state = state;
+ refs.current.equals = equals;
+ refs.current.selector = selector;
+
+ useOnMount(() => {
+ return apiRef.current.store.subscribe(() => {
+ const newState = applySelector(apiRef, refs.current.selector);
+ if (!refs.current.equals(refs.current.state, newState)) {
+ refs.current.state = newState;
+ setState(newState);
+ }
+ });
+ });
+
+ return state;
};
diff --git a/packages/grid/x-data-grid/src/hooks/utils/useLazyRef.ts b/packages/grid/x-data-grid/src/hooks/utils/useLazyRef.ts
new file mode 100644
index 0000000000000..1adcde01a3e68
--- /dev/null
+++ b/packages/grid/x-data-grid/src/hooks/utils/useLazyRef.ts
@@ -0,0 +1,13 @@
+import * as React from 'react';
+
+const UNINITIALIZED = {};
+
+export function useLazyRef(init: (arg?: U) => T, initArg?: U) {
+ const ref = React.useRef(UNINITIALIZED as unknown as T);
+
+ if (ref.current === UNINITIALIZED) {
+ ref.current = init(initArg);
+ }
+
+ return ref;
+}
diff --git a/packages/grid/x-data-grid/src/hooks/utils/useOnMount.ts b/packages/grid/x-data-grid/src/hooks/utils/useOnMount.ts
new file mode 100644
index 0000000000000..fe624200f7b2d
--- /dev/null
+++ b/packages/grid/x-data-grid/src/hooks/utils/useOnMount.ts
@@ -0,0 +1,9 @@
+import * as React from 'react';
+
+const EMPTY = [] as unknown[];
+
+export function useOnMount(fn: React.EffectCallback) {
+ /* eslint-disable react-hooks/exhaustive-deps */
+ React.useEffect(fn, EMPTY);
+ /* eslint-enable react-hooks/exhaustive-deps */
+}
diff --git a/packages/grid/x-data-grid/src/models/api/gridCoreApi.ts b/packages/grid/x-data-grid/src/models/api/gridCoreApi.ts
index cdf04b24c0f71..e69a0917f7a66 100644
--- a/packages/grid/x-data-grid/src/models/api/gridCoreApi.ts
+++ b/packages/grid/x-data-grid/src/models/api/gridCoreApi.ts
@@ -1,5 +1,6 @@
import * as React from 'react';
import { GridEventPublisher, GridEventListener, GridEvents } from '../events';
+import { Store } from '../../utils/Store';
import { EventManager, EventListenerOptions } from '../../utils/EventManager';
import { GridApiCaches } from '../gridApiCaches';
import type { GridApiCommon, GridPrivateApiCommon } from './gridApiCommon';
@@ -37,6 +38,11 @@ export interface GridCoreApi {
* @ignore - do not document.
*/
instanceId: { id: number };
+ /**
+ * The pub/sub store containing a reference to the public state.
+ * @ignore - do not document.
+ */
+ store: Store;
}
export interface GridCorePrivateApi<
diff --git a/packages/grid/x-data-grid/src/tests/keyboard.DataGrid.test.tsx b/packages/grid/x-data-grid/src/tests/keyboard.DataGrid.test.tsx
index 38a7cfaaba715..06f067396b145 100644
--- a/packages/grid/x-data-grid/src/tests/keyboard.DataGrid.test.tsx
+++ b/packages/grid/x-data-grid/src/tests/keyboard.DataGrid.test.tsx
@@ -685,13 +685,13 @@ describe(' - Keyboard', () => {
,
);
- expect(renderCell.callCount).to.equal(4);
+ expect(renderCell.callCount).to.equal(2);
const input = screen.getByTestId('custom-input');
input.focus();
fireEvent.keyDown(input, { key: 'a' });
- expect(renderCell.callCount).to.equal(6);
+ expect(renderCell.callCount).to.equal(4);
fireEvent.keyDown(input, { key: 'b' });
- expect(renderCell.callCount).to.equal(6);
+ expect(renderCell.callCount).to.equal(4);
});
it('should not scroll horizontally when cell is wider than viewport', () => {
diff --git a/packages/grid/x-data-grid/src/tests/rowSelection.DataGrid.test.tsx b/packages/grid/x-data-grid/src/tests/rowSelection.DataGrid.test.tsx
index 07b3b25aa382d..f24c7f694e12e 100644
--- a/packages/grid/x-data-grid/src/tests/rowSelection.DataGrid.test.tsx
+++ b/packages/grid/x-data-grid/src/tests/rowSelection.DataGrid.test.tsx
@@ -10,7 +10,6 @@ import {
GridEditModes,
useGridApiRef,
GridApi,
- GridRow,
} from '@mui/x-data-grid';
import {
getCell,
@@ -777,10 +776,7 @@ describe(' - Row Selection', () => {
});
describe('performance', () => {
- it('should not rerender unrelated rows', () => {
- // TODO: remove this requirement, find ways to scope down react tree rerenders.
- const MemoizedRow = React.memo(GridRow);
-
+ it('should not rerender unrelated nodes', () => {
// Couldn't use because we need to track multiple components
let commits: any[] = [];
function CustomCell(props: any) {
@@ -803,9 +799,6 @@ describe(' - Row Selection', () => {
renderCell: (params) => ,
},
]}
- slots={{
- row: MemoizedRow,
- }}
rows={[
{ id: 0, currencyPair: 'USDGBP' },
{ id: 1, currencyPair: 'USDEUR' },
@@ -821,8 +814,8 @@ describe(' - Row Selection', () => {
fireEvent.click(getCell(0, 1));
expect(getSelectedRowIds()).to.deep.equal([0]);
expect(getRow(0).querySelector('input')).to.have.property('checked', true);
- // It shouldn't rerender rowId 1
- expect(commits).to.deep.equal([{ rowId: 0 }]);
+ // It shouldn't rerender any of the custom cells
+ expect(commits).to.deep.equal([]);
});
});
});
diff --git a/packages/grid/x-data-grid/src/utils/Store.ts b/packages/grid/x-data-grid/src/utils/Store.ts
new file mode 100644
index 0000000000000..4380d4c231719
--- /dev/null
+++ b/packages/grid/x-data-grid/src/utils/Store.ts
@@ -0,0 +1,32 @@
+type Listener = (value: T) => void;
+
+export class Store {
+ value: T;
+
+ listeners: Set>;
+
+ static create(value: T) {
+ return new Store(value);
+ }
+
+ constructor(value: T) {
+ this.value = value;
+ this.listeners = new Set>();
+ }
+
+ subscribe = (fn: Listener) => {
+ this.listeners.add(fn);
+ return () => {
+ this.listeners.delete(fn);
+ };
+ };
+
+ getSnapshot = () => {
+ return this.value;
+ };
+
+ update = (value: T) => {
+ this.value = value;
+ this.listeners.forEach((l) => l(value));
+ };
+}
diff --git a/packages/grid/x-data-grid/src/utils/doesSupportPreventScroll.ts b/packages/grid/x-data-grid/src/utils/doesSupportPreventScroll.ts
new file mode 100644
index 0000000000000..f51b2f11ce465
--- /dev/null
+++ b/packages/grid/x-data-grid/src/utils/doesSupportPreventScroll.ts
@@ -0,0 +1,13 @@
+// Based on https://stackoverflow.com/a/59518678
+let cachedSupportsPreventScroll: boolean;
+export function doesSupportPreventScroll(): boolean {
+ if (cachedSupportsPreventScroll === undefined) {
+ document.createElement('div').focus({
+ get preventScroll() {
+ cachedSupportsPreventScroll = true;
+ return false;
+ },
+ });
+ }
+ return cachedSupportsPreventScroll;
+}
diff --git a/packages/grid/x-data-grid/src/utils/fastMemo.ts b/packages/grid/x-data-grid/src/utils/fastMemo.ts
new file mode 100644
index 0000000000000..86780baa5e551
--- /dev/null
+++ b/packages/grid/x-data-grid/src/utils/fastMemo.ts
@@ -0,0 +1,6 @@
+import * as React from 'react';
+import { fastObjectShallowCompare } from './fastObjectShallowCompare';
+
+export function fastMemo(component: T): T {
+ return React.memo(component as any, fastObjectShallowCompare) as unknown as T;
+}
diff --git a/packages/grid/x-data-grid/src/utils/fastObjectShallowCompare.ts b/packages/grid/x-data-grid/src/utils/fastObjectShallowCompare.ts
new file mode 100644
index 0000000000000..940220d63849d
--- /dev/null
+++ b/packages/grid/x-data-grid/src/utils/fastObjectShallowCompare.ts
@@ -0,0 +1,35 @@
+const is = Object.is;
+
+export function fastObjectShallowCompare | null>(a: T, b: T) {
+ if (a === b) {
+ return true;
+ }
+ if (!(a instanceof Object) || !(b instanceof Object)) {
+ return false;
+ }
+
+ let aLength = 0;
+ let bLength = 0;
+
+ /* eslint-disable no-restricted-syntax */
+ /* eslint-disable guard-for-in */
+ for (const key in a) {
+ aLength += 1;
+
+ if (!is(a[key], b[key])) {
+ return false;
+ }
+ if (!(key in b)) {
+ return false;
+ }
+ }
+
+ /* eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unused-vars */
+ for (const _ in b) {
+ bLength += 1;
+ }
+ /* eslint-enable no-restricted-syntax */
+ /* eslint-enable guard-for-in */
+
+ return aLength === bLength;
+}
diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json
index 127b2d2ae22b5..72543f44050a3 100644
--- a/scripts/x-data-grid-premium.exports.json
+++ b/scripts/x-data-grid-premium.exports.json
@@ -128,7 +128,7 @@
{ "name": "GridCellModes", "kind": "Enum" },
{ "name": "GridCellModesModel", "kind": "TypeAlias" },
{ "name": "GridCellParams", "kind": "Interface" },
- { "name": "GridCellProps", "kind": "Interface" },
+ { "name": "GridCellProps", "kind": "TypeAlias" },
{ "name": "GridCellSelectionApi", "kind": "Interface" },
{ "name": "GridCellSelectionModel", "kind": "TypeAlias" },
{ "name": "GridCheckCircleIcon", "kind": "Variable" },
diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json
index 364a0fc3d5925..671b5a7feb983 100644
--- a/scripts/x-data-grid-pro.exports.json
+++ b/scripts/x-data-grid-pro.exports.json
@@ -106,7 +106,7 @@
{ "name": "GridCellModes", "kind": "Enum" },
{ "name": "GridCellModesModel", "kind": "TypeAlias" },
{ "name": "GridCellParams", "kind": "Interface" },
- { "name": "GridCellProps", "kind": "Interface" },
+ { "name": "GridCellProps", "kind": "TypeAlias" },
{ "name": "GridCheckCircleIcon", "kind": "Variable" },
{ "name": "GridCheckIcon", "kind": "Variable" },
{ "name": "GridChildrenFromPathLookup", "kind": "TypeAlias" },
diff --git a/scripts/x-data-grid.exports.json b/scripts/x-data-grid.exports.json
index 92ab4e601356c..7d8b7de2a4e51 100644
--- a/scripts/x-data-grid.exports.json
+++ b/scripts/x-data-grid.exports.json
@@ -99,7 +99,7 @@
{ "name": "GridCellModes", "kind": "Enum" },
{ "name": "GridCellModesModel", "kind": "TypeAlias" },
{ "name": "GridCellParams", "kind": "Interface" },
- { "name": "GridCellProps", "kind": "Interface" },
+ { "name": "GridCellProps", "kind": "TypeAlias" },
{ "name": "GridCheckCircleIcon", "kind": "Variable" },
{ "name": "GridCheckIcon", "kind": "Variable" },
{ "name": "GridChildrenFromPathLookup", "kind": "TypeAlias" },