From fc48f10a09c457486219a1845cf10c3c0264dafb Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Thu, 18 May 2023 11:11:53 -0400 Subject: [PATCH 01/72] feat: add react store --- .../hooks/core/useGridApiInitialization.ts | 6 +++- .../hooks/core/useGridStateInitialization.ts | 2 ++ .../src/hooks/utils/useGridSelector.ts | 32 ++++++++++++++----- .../x-data-grid/src/models/api/gridCoreApi.ts | 6 ++++ packages/grid/x-data-grid/src/utils/Store.tsx | 29 +++++++++++++++++ .../grid/x-data-grid/src/utils/useLazyRef.ts | 16 ++++++++++ 6 files changed, 82 insertions(+), 9 deletions(-) create mode 100644 packages/grid/x-data-grid/src/utils/Store.tsx create mode 100644 packages/grid/x-data-grid/src/utils/useLazyRef.ts 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..2423533abad24 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: 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..e934b9363e9b5 100644 --- a/packages/grid/x-data-grid/src/hooks/core/useGridStateInitialization.ts +++ b/packages/grid/x-data-grid/src/hooks/core/useGridStateInitialization.ts @@ -87,6 +87,8 @@ export const useGridStateInitialization = ( selector: any, ): selector is OutputSelector { 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.', -]); +export function applySelector( + apiRef: React.MutableRefObject, + selector: ((state: Api['state']) => T) | OutputSelector +) { + if (isOutputSelector(selector)) { + return selector(apiRef); + } + return selector(apiRef.current.state); +} export const useGridSelector = ( apiRef: React.MutableRefObject, @@ -24,9 +36,13 @@ export const useGridSelector = ( } } - if (isOutputSelector(selector)) { - return selector(apiRef); - } + const [state, setState] = React.useState(applySelector(apiRef, selector)) - return selector(apiRef.current.state); + React.useEffect(() => { + return apiRef.current.store.subscribe(() => { + setState(applySelector(apiRef, selector)); + }) + }, EMPTY); + + return state; }; 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 9483c6cf8b520..9cb134513f8ff 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/utils/Store.tsx b/packages/grid/x-data-grid/src/utils/Store.tsx new file mode 100644 index 0000000000000..b6f4699495aa0 --- /dev/null +++ b/packages/grid/x-data-grid/src/utils/Store.tsx @@ -0,0 +1,29 @@ +type Listener = (value: T) => void; + +export class Store { + value: T; + listeners; + + 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/useLazyRef.ts b/packages/grid/x-data-grid/src/utils/useLazyRef.ts new file mode 100644 index 0000000000000..47e548e372cb8 --- /dev/null +++ b/packages/grid/x-data-grid/src/utils/useLazyRef.ts @@ -0,0 +1,16 @@ +import React from 'react'; + +const UNINITIALIZED = {}; + +export default function useLazyRef( + initFn: (initVal?: any) => T, + initVal?: any, +): React.MutableRefObject { + const ref = React.useRef(UNINITIALIZED as any) as React.MutableRefObject; + + if (ref.current === (UNINITIALIZED as any)) { + ref.current = initFn(initVal); + } + + return ref; +} From 8c81c06274798ab153471307532953a3e18efe71 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Thu, 18 May 2023 11:19:21 -0400 Subject: [PATCH 02/72] refactor --- .../grid/x-data-grid/src/utils/useLazyRef.ts | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 packages/grid/x-data-grid/src/utils/useLazyRef.ts diff --git a/packages/grid/x-data-grid/src/utils/useLazyRef.ts b/packages/grid/x-data-grid/src/utils/useLazyRef.ts deleted file mode 100644 index 47e548e372cb8..0000000000000 --- a/packages/grid/x-data-grid/src/utils/useLazyRef.ts +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; - -const UNINITIALIZED = {}; - -export default function useLazyRef( - initFn: (initVal?: any) => T, - initVal?: any, -): React.MutableRefObject { - const ref = React.useRef(UNINITIALIZED as any) as React.MutableRefObject; - - if (ref.current === (UNINITIALIZED as any)) { - ref.current = initFn(initVal); - } - - return ref; -} From 0d1024add9fcaaa444e5178da430c7549be186a5 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Wed, 24 May 2023 16:35:33 -0400 Subject: [PATCH 03/72] perf: memoize rows & headers --- .../src/tests/rows.DataGridPro.test.tsx | 7 +- .../src/components/GridColumnHeaders.tsx | 4 +- .../x-data-grid/src/components/GridRow.tsx | 168 +++++------------ .../src/components/cell/GridCell.tsx | 170 ++++++++++++------ .../GridCellCheckboxRenderer.tsx | 2 + .../src/hooks/features/focus/useGridFocus.ts | 12 +- .../src/hooks/utils/useGridSelector.ts | 22 ++- .../src/tests/rowSelection.DataGrid.test.tsx | 11 +- .../src/utils/fastShallowCompare.ts | 22 +++ 9 files changed, 219 insertions(+), 199 deletions(-) create mode 100644 packages/grid/x-data-grid/src/utils/fastShallowCompare.ts 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 c2df1d6c2b83f..434d337c10b22 100644 --- a/packages/grid/x-data-grid/src/components/GridColumnHeaders.tsx +++ b/packages/grid/x-data-grid/src/components/GridColumnHeaders.tsx @@ -116,4 +116,6 @@ GridColumnHeaders.propTypes = { visibleColumns: PropTypes.arrayOf(PropTypes.object).isRequired, } as any; -export { GridColumnHeaders }; +const MemoizedGridColumnHeaders = React.memo(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..4908c9739ca6d 100644 --- a/packages/grid/x-data-grid/src/components/GridRow.tsx +++ b/packages/grid/x-data-grid/src/components/GridRow.tsx @@ -6,7 +6,7 @@ import { unstable_useForkRef as useForkRef, } from '@mui/utils'; 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'; @@ -20,7 +20,6 @@ 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'; @@ -259,135 +258,44 @@ const GridRow = React.forwardRef(function GridRow( const { slots, slotProps, - classes: rootClasses, disableColumnReorder, - getCellClassName, } = rootProps; 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 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 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); + + return ( + + ); + }; const sizes = apiRef.current.unstable_getRowInternalSizes(rowId); @@ -448,7 +356,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) { @@ -554,4 +468,6 @@ GridRow.propTypes = { visibleColumns: PropTypes.arrayOf(PropTypes.object), } as any; -export { GridRow }; +const MemoizedGridRow = React.memo(GridRow); + +export { MemoizedGridRow as GridRow }; diff --git a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx index a5a355b362455..6628c3e4ae863 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -8,7 +8,7 @@ import { unstable_ownerDocument as ownerDocument, unstable_capitalize as capitalize, } from '@mui/utils'; -import { getDataGridUtilityClass } from '../../constants/gridClasses'; +import { getDataGridUtilityClass, gridClasses } from '../../constants/gridClasses'; import { GridCellEventLookup, GridEvents, @@ -16,30 +16,27 @@ import { GridCellModes, GridRowId, } from '../../models'; -import { GridAlignment } from '../../models/colDef/gridColDef'; +import { GridRenderEditCellParams } from '../../models/params/gridCellParams'; +import { GridColDef, GridAlignment } from '../../models/colDef/gridColDef'; +import { GridTreeNodeWithRender } from '../../models/gridRows'; +import { useGridSelector, shallowCompare } from '../../hooks/utils/useGridSelector'; import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; -import { gridFocusCellSelector } from '../../hooks/features/focus/gridFocusStateSelector'; +import { gridFocusCellSelector, gridTabIndexCellSelector } from '../../hooks/features/focus/gridFocusStateSelector'; +import { gridEditRowsStateSelector } from '../../hooks/features/editing/gridEditingSelectors'; import type { DataGridProcessedProps } from '../../models/props/DataGridProps'; import { FocusElement } from '../../models/params/gridCellParams'; +import type { GridRowProps } from '../GridRow'; -export interface GridCellProps { +export interface GridCellProps { 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; onClick?: React.MouseEventHandler; @@ -66,7 +63,9 @@ function doesSupportPreventScroll(): boolean { return cachedSupportsPreventScroll; } -type OwnerState = Pick & { +type OwnerState = Pick & { + isEditable?: boolean, + isSelected?: boolean, classes?: DataGridProcessedProps['classes']; }; @@ -93,19 +92,10 @@ let warnedOnce = false; const GridCell = React.forwardRef((props, ref) => { const { align, - children, colIndex, - colDef, - cellMode, - field, - formattedValue, - hasFocus, + column, height, - isEditable, - isSelected, rowId, - tabIndex, - value, width, className, showRightBorder, @@ -125,16 +115,114 @@ const GridCell = React.forwardRef((props, ref) => ...other } = props; - const valueToRender = formattedValue == null ? value : formattedValue; + const field = column.field; const cellRef = React.useRef(null); const handleRef = useForkRef(ref, cellRef); const focusElementRef = React.useRef(null); const apiRef = useGridApiContext(); - const rootProps = useGridRootProps(); + + const editRowsState = useGridSelector(apiRef, gridEditRowsStateSelector); + + const { + classes: rootClasses, + getCellClassName, + } = rootProps; + + const isSelected = useGridSelector(apiRef, () => + apiRef.current.unstable_applyPipeProcessors('isCellSelected', false, { + id: rowId, + field: field, + }) + ); + + const cellParams = 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 { + return apiRef.current.getCellParams( + rowId, + field, + ); + } catch (e) { + if ((e as Error).message.startsWith('No row with id')) { + return null as any; + } + throw e; + } + }, shallowCompare); + + const classNames = apiRef.current.unstable_applyPipeProcessors('cellClassName', [], { + id: rowId, + field: field, + }); + + if (column.cellClassName) { + classNames.push( + clsx( + typeof column.cellClassName === 'function' + ? column.cellClassName(cellParams) + : column.cellClassName, + ), + ); + } + + const editCellState = editRowsState[rowId]?.[column.field] ?? null; + + if (getCellClassName) { + classNames.push(getCellClassName(cellParams)); + } + + const managesOwnFocus = column.type === 'actions'; + + const cellMode = cellParams.cellMode; + const isEditable = cellParams.isEditable; + const valueToRender = cellParams.formattedValue == null ? cellParams.value : cellParams.formattedValue; + const ownerState = { align, showRightBorder, isEditable, classes: rootProps.classes, isSelected }; const classes = useUtilityClasses(ownerState); + + let children: React.ReactNode; + + if (editCellState == null && column.renderCell) { + children = column.renderCell({ ...cellParams, api: apiRef.current }); + 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, + }; + + children = column.renderEditCell(params); + classNames.push(clsx(gridClasses['cell--editing'], rootClasses?.['cell--editing'])); + } + + if (children === undefined) { + const valueString = valueToRender?.toString(); + children = ( +
+ {valueString} +
+ ); + } + + if (React.isValidElement(children) && managesOwnFocus) { + children = React.cloneElement(children, { focusElementRef }); + } + const publishMouseUp = React.useCallback( (eventName: GridEvents) => (event: React.MouseEvent) => { const params = apiRef.current.getCellParams(rowId, field || ''); @@ -185,7 +273,7 @@ const GridCell = React.forwardRef((props, ref) => }; React.useEffect(() => { - if (!hasFocus || cellMode === GridCellModes.Edit) { + if (!cellParams.hasFocus || cellMode === GridCellModes.Edit) { return; } @@ -203,7 +291,7 @@ const GridCell = React.forwardRef((props, ref) => apiRef.current.scroll(scrollPosition); } } - }, [hasFocus, cellMode, apiRef]); + }, [cellParams.hasFocus, cellMode, apiRef]); let handleFocus: any = other.onFocus; @@ -235,26 +323,6 @@ 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 }); - } - - return children; - }; - const draggableEventHandlers = disableDragEvents ? null : { @@ -265,14 +333,14 @@ const GridCell = React.forwardRef((props, ref) => return (
((props, ref) => {...other} onFocus={handleFocus} > - {renderChildren()} + {children}
); }); diff --git a/packages/grid/x-data-grid/src/components/columnSelection/GridCellCheckboxRenderer.tsx b/packages/grid/x-data-grid/src/components/columnSelection/GridCellCheckboxRenderer.tsx index 7353974fabb98..add078df1f49a 100644 --- a/packages/grid/x-data-grid/src/components/columnSelection/GridCellCheckboxRenderer.tsx +++ b/packages/grid/x-data-grid/src/components/columnSelection/GridCellCheckboxRenderer.tsx @@ -6,8 +6,10 @@ import { } from '@mui/utils'; import type { GridRenderCellParams } from '../../models/params/gridCellParams'; import { isSpaceKey } from '../../utils/keyboardUtils'; +import { useGridSelector } from '../../hooks'; import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; +import { selectedIdsLookupSelector } from '../../hooks/features/rowSelection'; import { getDataGridUtilityClass } from '../../constants/gridClasses'; import type { DataGridProcessedProps } from '../../models/props/DataGridProps'; import type { GridRowSelectionCheckboxParams } from '../../models/params/gridRowSelectionCheckboxParams'; 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 ff7e05434ccb3..ec38189ca51d6 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 @@ -17,6 +17,7 @@ import { GridStateInitializer } from '../../utils/useGridInitializeState'; import { gridVisibleColumnDefinitionsSelector } from '../columns/gridColumnsSelector'; import { getVisibleRows } from '../../utils/useGridVisibleRows'; import { clamp } from '../../../utils/utils'; +import { useGridSelector } from '../../utils/useGridSelector'; import { GridCellCoordinates } from '../../../models/gridCell'; import { GridRowEntry } from '../../../models/gridRows'; import { gridPinnedRowsSelector } from '../rows/gridRowsSelector'; @@ -244,8 +245,6 @@ export const useGridFocus = ( [apiRef], ); - const focussedColumnGroup = unstable_gridFocusColumnGroupHeaderSelector(apiRef); - const handleColumnGroupHeaderFocus = React.useCallback< GridEventListener<'columnGroupHeaderFocus'> >( @@ -253,17 +252,18 @@ 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>(() => { 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 6ddd1b842afdf..8251afdfdc0ff 100644 --- a/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts +++ b/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts @@ -2,6 +2,7 @@ import * as React from 'react'; import { GridApiCommon } from '../../models/api/gridApiCommon'; import { OutputSelector } from '../../utils/createSelector'; import { buildWarning } from '../../utils/warning'; +import { fastShallowCompare } from '../../utils/fastShallowCompare'; const stateNotInitializedWarning = buildWarning([ 'MUI: `useGridSelector` has been called before the initialization of the state.', @@ -26,9 +27,14 @@ export function applySelector( return selector(apiRef.current.state); } + +export const defaultCompare = Object.is; // XXX: Do we need to polyfill? +export const shallowCompare = fastShallowCompare; + 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) { @@ -36,11 +42,23 @@ export const useGridSelector = ( } } - const [state, setState] = React.useState(applySelector(apiRef, selector)) + const selectorRef = React.useRef(); + const [state, setState] = React.useState( + (selectorRef.current ? null : applySelector(apiRef, selector)) as T + ); + const stateRef = React.useRef(state); + + stateRef.current = state; + selectorRef.current = selector; React.useEffect(() => { return apiRef.current.store.subscribe(() => { - setState(applySelector(apiRef, selector)); + const state = stateRef.current; + const newState = applySelector(apiRef, selectorRef.current!) + if (!equals(state, newState)) { + stateRef.current = newState; + setState(newState); + } }) }, EMPTY); 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..9c8135da0f7ec 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 @@ -777,9 +777,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[] = []; @@ -803,9 +801,6 @@ describe(' - Row Selection', () => { renderCell: (params) => , }, ]} - slots={{ - row: MemoizedRow, - }} rows={[ { id: 0, currencyPair: 'USDGBP' }, { id: 1, currencyPair: 'USDEUR' }, @@ -821,8 +816,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/fastShallowCompare.ts b/packages/grid/x-data-grid/src/utils/fastShallowCompare.ts new file mode 100644 index 0000000000000..f6df277089e41 --- /dev/null +++ b/packages/grid/x-data-grid/src/utils/fastShallowCompare.ts @@ -0,0 +1,22 @@ + +const is = Object.is; + +export function fastShallowCompare | null | undefined>(a: T, b: T) { + if (a === b) return true; + if (!(a instanceof Object) || !(b instanceof Object)) return false; + + for (let key in a) { + if (!(key in b)) + return false; + if (!is(a[key], b[key])) + return false; + } + + for (let key in b) { + if (!(key in a)) + return false; + } + + return true; +}; + From 33d09989c140c73bbe4694ee7d961aebb6167cea Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Wed, 24 May 2023 17:05:43 -0400 Subject: [PATCH 04/72] test: make them pass --- .../keyboardNavigation/useGridKeyboardNavigation.ts | 3 +-- .../x-data-grid/src/hooks/features/rows/useGridParamsApi.ts | 6 ------ .../grid/x-data-grid/src/tests/keyboard.DataGrid.test.tsx | 6 +++--- 3 files changed, 4 insertions(+), 11 deletions(-) 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 e15492ab8bd2b..dfc95b2d1560c 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 @@ -281,7 +281,6 @@ export const useGridKeyboardNavigation = ( ], ); - const focusedColumnGroup = useGridSelector(apiRef, unstable_gridFocusColumnGroupHeaderSelector); const handleColumnGroupHeaderKeyDown = React.useCallback< GridEventListener<'columnGroupHeaderKeyDown'> >( @@ -291,6 +290,7 @@ export const useGridKeyboardNavigation = ( return; } + const focusedColumnGroup = unstable_gridFocusColumnGroupHeaderSelector(apiRef); if (focusedColumnGroup === null) { return; } @@ -379,7 +379,6 @@ export const useGridKeyboardNavigation = ( }, [ apiRef, - focusedColumnGroup, currentPageRows.length, goToHeader, goToGroupHeader, 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..5c845a86f8c39 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 @@ -133,12 +133,6 @@ 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); 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', () => { From f8e7691da36ab2ac6972ed28737d9ecd3f91c3d8 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Wed, 24 May 2023 17:10:47 -0400 Subject: [PATCH 05/72] lint --- packages/grid/x-data-grid/src/components/cell/GridCell.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx index 6628c3e4ae863..8ace15be7cf93 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -177,6 +177,7 @@ const GridCell = React.forwardRef((props, ref) => const managesOwnFocus = column.type === 'actions'; const cellMode = cellParams.cellMode; + const hasFocus = cellParams.hasFocus; const isEditable = cellParams.isEditable; const valueToRender = cellParams.formattedValue == null ? cellParams.value : cellParams.formattedValue; @@ -273,7 +274,7 @@ const GridCell = React.forwardRef((props, ref) => }; React.useEffect(() => { - if (!cellParams.hasFocus || cellMode === GridCellModes.Edit) { + if (!hasFocus || cellMode === GridCellModes.Edit) { return; } @@ -291,7 +292,7 @@ const GridCell = React.forwardRef((props, ref) => apiRef.current.scroll(scrollPosition); } } - }, [cellParams.hasFocus, cellMode, apiRef]); + }, [hasFocus, cellMode, apiRef]); let handleFocus: any = other.onFocus; From c6455d9a581268a9bc44d2a917ad15cb8bd47d48 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Wed, 24 May 2023 17:13:07 -0400 Subject: [PATCH 06/72] lint --- packages/grid/x-data-grid/src/components/cell/GridCell.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx index 8ace15be7cf93..6600c1d0a1647 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -168,12 +168,11 @@ const GridCell = React.forwardRef((props, ref) => ); } - const editCellState = editRowsState[rowId]?.[column.field] ?? null; - if (getCellClassName) { classNames.push(getCellClassName(cellParams)); } + const editCellState = editRowsState[rowId]?.[column.field] ?? null; const managesOwnFocus = column.type === 'actions'; const cellMode = cellParams.cellMode; From 271c9bd19936709e1a3982e5737666aa73564050 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Wed, 24 May 2023 17:15:30 -0400 Subject: [PATCH 07/72] lint --- .../src/components/cell/GridCell.tsx | 61 ++++++++++--------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx index 6600c1d0a1647..02c17c7bcd06e 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -185,42 +185,43 @@ const GridCell = React.forwardRef((props, ref) => let children: React.ReactNode; + { + if (editCellState == null && column.renderCell) { + children = column.renderCell({ ...cellParams, api: apiRef.current }); + classNames.push( + clsx(gridClasses['cell--withRenderer'], rootClasses?.['cell--withRenderer']), + ); + } - if (editCellState == null && column.renderCell) { - children = column.renderCell({ ...cellParams, api: apiRef.current }); - classNames.push( - clsx(gridClasses['cell--withRenderer'], rootClasses?.['cell--withRenderer']), - ); - } - - if (editCellState != null && column.renderEditCell) { - const updatedRow = apiRef.current.getRowWithUpdatedValues(rowId, column.field); + 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; + // eslint-disable-next-line @typescript-eslint/naming-convention + const { changeReason, unstable_updateValueOnRender, ...editCellStateRest } = editCellState; - const params: GridRenderEditCellParams = { - ...cellParams, - row: updatedRow, - ...editCellStateRest, - api: apiRef.current, - }; + const params: GridRenderEditCellParams = { + ...cellParams, + row: updatedRow, + ...editCellStateRest, + api: apiRef.current, + }; - children = column.renderEditCell(params); - classNames.push(clsx(gridClasses['cell--editing'], rootClasses?.['cell--editing'])); - } + children = column.renderEditCell(params); + classNames.push(clsx(gridClasses['cell--editing'], rootClasses?.['cell--editing'])); + } - if (children === undefined) { - const valueString = valueToRender?.toString(); - children = ( -
- {valueString} -
- ); - } + if (children === undefined) { + const valueString = valueToRender?.toString(); + children = ( +
+ {valueString} +
+ ); + } - if (React.isValidElement(children) && managesOwnFocus) { - children = React.cloneElement(children, { focusElementRef }); + if (React.isValidElement(children) && managesOwnFocus) { + children = React.cloneElement(children, { focusElementRef }); + } } const publishMouseUp = React.useCallback( From a63e8e4bf48b6d966b28d7122af76cc2dbfda31e Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Wed, 24 May 2023 17:16:17 -0400 Subject: [PATCH 08/72] lint --- packages/grid/x-data-grid/src/components/cell/GridCell.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx index 02c17c7bcd06e..f8027dd975381 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -12,7 +12,6 @@ import { getDataGridUtilityClass, gridClasses } from '../../constants/gridClasse import { GridCellEventLookup, GridEvents, - GridCellMode, GridCellModes, GridRowId, } from '../../models'; @@ -22,11 +21,10 @@ import { GridTreeNodeWithRender } from '../../models/gridRows'; import { useGridSelector, shallowCompare } from '../../hooks/utils/useGridSelector'; import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; -import { gridFocusCellSelector, gridTabIndexCellSelector } from '../../hooks/features/focus/gridFocusStateSelector'; +import { gridFocusCellSelector } from '../../hooks/features/focus/gridFocusStateSelector'; import { gridEditRowsStateSelector } from '../../hooks/features/editing/gridEditingSelectors'; import type { DataGridProcessedProps } from '../../models/props/DataGridProps'; import { FocusElement } from '../../models/params/gridCellParams'; -import type { GridRowProps } from '../GridRow'; export interface GridCellProps { align: GridAlignment; From 6d8364defb1c6b1e93a3ae135103592328960ea9 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Wed, 24 May 2023 17:28:03 -0400 Subject: [PATCH 09/72] lint --- .../x-data-grid/src/components/GridRow.tsx | 9 +-- .../src/components/cell/GridCell.tsx | 74 +++++++------------ .../hooks/core/useGridApiInitialization.ts | 2 +- .../useGridKeyboardNavigation.ts | 9 +-- .../src/hooks/utils/useGridSelector.ts | 11 ++- .../src/tests/rowSelection.DataGrid.test.tsx | 1 - packages/grid/x-data-grid/src/utils/Store.tsx | 13 ++-- .../src/utils/fastShallowCompare.ts | 30 +++++--- 8 files changed, 64 insertions(+), 85 deletions(-) diff --git a/packages/grid/x-data-grid/src/components/GridRow.tsx b/packages/grid/x-data-grid/src/components/GridRow.tsx index 4908c9739ca6d..decbc6d24fb43 100644 --- a/packages/grid/x-data-grid/src/components/GridRow.tsx +++ b/packages/grid/x-data-grid/src/components/GridRow.tsx @@ -255,11 +255,7 @@ const GridRow = React.forwardRef(function GridRow( [apiRef, onClick, publish, rowId], ); - const { - slots, - slotProps, - disableColumnReorder, - } = rootProps; + const { slots, slotProps, disableColumnReorder } = rootProps; const rowReordering = (rootProps as any).rowReordering as boolean; @@ -272,7 +268,6 @@ const GridRow = React.forwardRef(function GridRow( 'width' | 'colSpan' | 'showRightBorder' | 'indexRelativeToAllColumns' >, ) => { - const disableDragEvents = (disableColumnReorder && column.disableReorder) || (!rowReordering && @@ -359,7 +354,7 @@ const GridRow = React.forwardRef(function GridRow( const rowNode = apiRef.current.getRowNode(rowId); if (!rowNode) { - return null + return null; } const rowType = rowNode.type; diff --git a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx index f8027dd975381..caea23b3f7368 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -9,13 +9,8 @@ import { unstable_capitalize as capitalize, } from '@mui/utils'; import { getDataGridUtilityClass, gridClasses } from '../../constants/gridClasses'; -import { - GridCellEventLookup, - GridEvents, - GridCellModes, - GridRowId, -} from '../../models'; -import { GridRenderEditCellParams } from '../../models/params/gridCellParams'; +import { GridCellEventLookup, GridEvents, GridCellModes, GridRowId } from '../../models'; +import { GridRenderEditCellParams, FocusElement } from '../../models/params/gridCellParams'; import { GridColDef, GridAlignment } from '../../models/colDef/gridColDef'; import { GridTreeNodeWithRender } from '../../models/gridRows'; import { useGridSelector, shallowCompare } from '../../hooks/utils/useGridSelector'; @@ -24,7 +19,6 @@ import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import { gridFocusCellSelector } from '../../hooks/features/focus/gridFocusStateSelector'; import { gridEditRowsStateSelector } from '../../hooks/features/editing/gridEditingSelectors'; import type { DataGridProcessedProps } from '../../models/props/DataGridProps'; -import { FocusElement } from '../../models/params/gridCellParams'; export interface GridCellProps { align: GridAlignment; @@ -62,8 +56,8 @@ function doesSupportPreventScroll(): boolean { } type OwnerState = Pick & { - isEditable?: boolean, - isSelected?: boolean, + isEditable?: boolean; + isSelected?: boolean; classes?: DataGridProcessedProps['classes']; }; @@ -122,38 +116,36 @@ const GridCell = React.forwardRef((props, ref) => const editRowsState = useGridSelector(apiRef, gridEditRowsStateSelector); - const { - classes: rootClasses, - getCellClassName, - } = rootProps; + const { classes: rootClasses, getCellClassName } = rootProps; const isSelected = useGridSelector(apiRef, () => apiRef.current.unstable_applyPipeProcessors('isCellSelected', false, { id: rowId, - field: field, - }) + field, + }), ); - const cellParams = 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 { - return apiRef.current.getCellParams( - rowId, - field, - ); - } catch (e) { - if ((e as Error).message.startsWith('No row with id')) { - return null as any; + const cellParams = 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 { + return apiRef.current.getCellParams(rowId, field); + } catch (e) { + if ((e as Error).message.startsWith('No row with id')) { + return null as any; + } + throw e; } - throw e; - } - }, shallowCompare); + }, + shallowCompare, + ); const classNames = apiRef.current.unstable_applyPipeProcessors('cellClassName', [], { id: rowId, - field: field, + field, }); if (column.cellClassName) { @@ -176,19 +168,17 @@ const GridCell = React.forwardRef((props, ref) => const cellMode = cellParams.cellMode; const hasFocus = cellParams.hasFocus; const isEditable = cellParams.isEditable; - const valueToRender = cellParams.formattedValue == null ? cellParams.value : cellParams.formattedValue; + const valueToRender = + cellParams.formattedValue == null ? cellParams.value : cellParams.formattedValue; const ownerState = { align, showRightBorder, isEditable, classes: rootProps.classes, isSelected }; const classes = useUtilityClasses(ownerState); - let children: React.ReactNode; { if (editCellState == null && column.renderCell) { children = column.renderCell({ ...cellParams, api: apiRef.current }); - classNames.push( - clsx(gridClasses['cell--withRenderer'], rootClasses?.['cell--withRenderer']), - ); + classNames.push(clsx(gridClasses['cell--withRenderer'], rootClasses?.['cell--withRenderer'])); } if (editCellState != null && column.renderEditCell) { @@ -364,18 +354,12 @@ GridCell.propTypes = { // | To update them edit the TypeScript types and run "yarn proptypes" | // ---------------------------------------------------------------------- align: PropTypes.oneOf(['center', 'left', 'right']), - cellMode: PropTypes.oneOf(['edit', 'view']), - children: PropTypes.node, className: PropTypes.string, colIndex: PropTypes.number, colSpan: PropTypes.number, + column: PropTypes.object, disableDragEvents: PropTypes.bool, - field: PropTypes.string, - formattedValue: PropTypes.any, - hasFocus: PropTypes.bool, height: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number]), - isEditable: PropTypes.bool, - isSelected: PropTypes.bool, onClick: PropTypes.func, onDoubleClick: PropTypes.func, onDragEnter: PropTypes.func, @@ -385,8 +369,6 @@ 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; 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 2423533abad24..ddcb9ef60e86f 100644 --- a/packages/grid/x-data-grid/src/hooks/core/useGridApiInitialization.ts +++ b/packages/grid/x-data-grid/src/hooks/core/useGridApiInitialization.ts @@ -64,7 +64,7 @@ export function useGridApiInitialization< const state = {} as Api['state']; publicApiRef.current = { - state: state, + state, store: Store.create(state), instanceId: { id: globalId }, } as Api; 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 3cfafbb14e248..8ea78aadb9b9e 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 @@ -517,14 +517,7 @@ export const useGridKeyboardNavigation = ( event.preventDefault(); } }, - [ - apiRef, - 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/utils/useGridSelector.ts b/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts index 8251afdfdc0ff..cc2ce5aabf9a5 100644 --- a/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts +++ b/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts @@ -19,7 +19,7 @@ function isOutputSelector( export function applySelector( apiRef: React.MutableRefObject, - selector: ((state: Api['state']) => T) | OutputSelector + selector: ((state: Api['state']) => T) | OutputSelector, ) { if (isOutputSelector(selector)) { return selector(apiRef); @@ -27,14 +27,13 @@ export function applySelector( return selector(apiRef.current.state); } - export const defaultCompare = Object.is; // XXX: Do we need to polyfill? export const shallowCompare = fastShallowCompare; export const useGridSelector = ( apiRef: React.MutableRefObject, selector: ((state: Api['state']) => T) | OutputSelector, - equals: ((a: T, b: T) => boolean) = defaultCompare + equals: (a: T, b: T) => boolean = defaultCompare, ) => { if (process.env.NODE_ENV !== 'production') { if (!apiRef.current.state) { @@ -44,7 +43,7 @@ export const useGridSelector = ( const selectorRef = React.useRef(); const [state, setState] = React.useState( - (selectorRef.current ? null : applySelector(apiRef, selector)) as T + (selectorRef.current ? null : applySelector(apiRef, selector)) as T, ); const stateRef = React.useRef(state); @@ -54,12 +53,12 @@ export const useGridSelector = ( React.useEffect(() => { return apiRef.current.store.subscribe(() => { const state = stateRef.current; - const newState = applySelector(apiRef, selectorRef.current!) + const newState = applySelector(apiRef, selectorRef.current!); if (!equals(state, newState)) { stateRef.current = newState; setState(newState); } - }) + }); }, EMPTY); return state; 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 9c8135da0f7ec..442fa6fdcaf81 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 @@ -778,7 +778,6 @@ describe(' - Row Selection', () => { describe('performance', () => { it('should not rerender unrelated nodes', () => { - // Couldn't use because we need to track multiple components let commits: any[] = []; function CustomCell(props: any) { diff --git a/packages/grid/x-data-grid/src/utils/Store.tsx b/packages/grid/x-data-grid/src/utils/Store.tsx index b6f4699495aa0..4380d4c231719 100644 --- a/packages/grid/x-data-grid/src/utils/Store.tsx +++ b/packages/grid/x-data-grid/src/utils/Store.tsx @@ -2,7 +2,8 @@ type Listener = (value: T) => void; export class Store { value: T; - listeners; + + listeners: Set>; static create(value: T) { return new Store(value); @@ -15,8 +16,10 @@ export class Store { subscribe = (fn: Listener) => { this.listeners.add(fn); - return () => { this.listeners.delete(fn) }; - } + return () => { + this.listeners.delete(fn); + }; + }; getSnapshot = () => { return this.value; @@ -24,6 +27,6 @@ export class Store { update = (value: T) => { this.value = value; - this.listeners.forEach(l => l(value)); - } + this.listeners.forEach((l) => l(value)); + }; } diff --git a/packages/grid/x-data-grid/src/utils/fastShallowCompare.ts b/packages/grid/x-data-grid/src/utils/fastShallowCompare.ts index f6df277089e41..0d9f9f0965569 100644 --- a/packages/grid/x-data-grid/src/utils/fastShallowCompare.ts +++ b/packages/grid/x-data-grid/src/utils/fastShallowCompare.ts @@ -1,22 +1,30 @@ - const is = Object.is; -export function fastShallowCompare | null | undefined>(a: T, b: T) { - if (a === b) return true; - if (!(a instanceof Object) || !(b instanceof Object)) return false; +export function fastShallowCompare | null | undefined>( + a: T, + b: T, +) { + if (a === b) { + return true; + } + if (!(a instanceof Object) || !(b instanceof Object)) { + return false; + } - for (let key in a) { - if (!(key in b)) + for (const key in a) { + if (!(key in b)) { return false; - if (!is(a[key], b[key])) + } + if (!is(a[key], b[key])) { return false; + } } - for (let key in b) { - if (!(key in a)) + for (const key in b) { + if (!(key in a)) { return false; + } } return true; -}; - +} From a81488b112d1dda6383a869150a995b7fdb4a62f Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Wed, 24 May 2023 17:45:41 -0400 Subject: [PATCH 10/72] doc: update performance.md --- .../performance/GridWithReactMemo.js | 79 ------------------- .../performance/GridWithReactMemo.tsx | 79 ------------------- .../performance/GridWithReactMemo.tsx.preview | 10 --- .../data/data-grid/performance/performance.md | 69 +++++++++------- 4 files changed, 39 insertions(+), 198 deletions(-) delete mode 100644 docs/data/data-grid/performance/GridWithReactMemo.js delete mode 100644 docs/data/data-grid/performance/GridWithReactMemo.tsx delete mode 100644 docs/data/data-grid/performance/GridWithReactMemo.tsx.preview diff --git a/docs/data/data-grid/performance/GridWithReactMemo.js b/docs/data/data-grid/performance/GridWithReactMemo.js deleted file mode 100644 index 1a4c6650f32e4..0000000000000 --- a/docs/data/data-grid/performance/GridWithReactMemo.js +++ /dev/null @@ -1,79 +0,0 @@ -import * as React from 'react'; -import Box from '@mui/material/Box'; -import { unstable_useForkRef as useForkRef } from '@mui/utils'; -import { DataGridPro, GridRow, GridColumnHeaders } from '@mui/x-data-grid-pro'; -import { useDemoData } from '@mui/x-data-grid-generator'; - -const TraceUpdates = React.forwardRef((props, ref) => { - const { Component, ...other } = props; - const rootRef = React.useRef(); - const handleRef = useForkRef(rootRef, ref); - - React.useEffect(() => { - const root = rootRef.current; - root.classList.add('updating'); - root.classList.add('updated'); - - const timer = setTimeout(() => { - root.classList.remove('updating'); - }, 360); - - return () => { - clearTimeout(timer); - }; - }); - - return ; -}); - -const RowWithTracer = React.forwardRef((props, ref) => { - return ; -}); - -const ColumnHeadersWithTracer = React.forwardRef((props, ref) => { - return ; -}); - -const MemoizedRow = React.memo(RowWithTracer); -const MemoizedColumnHeaders = React.memo(ColumnHeadersWithTracer); - -export default function GridWithReactMemo() { - const { data } = useDemoData({ - dataSet: 'Commodity', - rowLength: 100, - editable: true, - maxColumns: 15, - }); - - return ( - - theme.transitions.create(['background-color', 'outline'], { - duration: theme.transitions.duration.standard, - }), - }, - '&&& .updating': { - backgroundColor: 'rgb(92 199 68 / 25%)', - outline: '1px solid rgb(92 199 68 / 35%)', - outlineOffset: '-1px', - transition: 'none', - }, - }} - > - - - ); -} diff --git a/docs/data/data-grid/performance/GridWithReactMemo.tsx b/docs/data/data-grid/performance/GridWithReactMemo.tsx deleted file mode 100644 index db1a5ea400bb1..0000000000000 --- a/docs/data/data-grid/performance/GridWithReactMemo.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import * as React from 'react'; -import Box from '@mui/material/Box'; -import { unstable_useForkRef as useForkRef } from '@mui/utils'; -import { DataGridPro, GridRow, GridColumnHeaders } from '@mui/x-data-grid-pro'; -import { useDemoData } from '@mui/x-data-grid-generator'; - -const TraceUpdates = React.forwardRef((props, ref) => { - const { Component, ...other } = props; - const rootRef = React.useRef(); - const handleRef = useForkRef(rootRef, ref); - - React.useEffect(() => { - const root = rootRef.current; - root!.classList.add('updating'); - root!.classList.add('updated'); - - const timer = setTimeout(() => { - root!.classList.remove('updating'); - }, 360); - - return () => { - clearTimeout(timer); - }; - }); - - return ; -}); - -const RowWithTracer = React.forwardRef((props, ref) => { - return ; -}); - -const ColumnHeadersWithTracer = React.forwardRef((props, ref) => { - return ; -}); - -const MemoizedRow = React.memo(RowWithTracer); -const MemoizedColumnHeaders = React.memo(ColumnHeadersWithTracer); - -export default function GridWithReactMemo() { - const { data } = useDemoData({ - dataSet: 'Commodity', - rowLength: 100, - editable: true, - maxColumns: 15, - }); - - return ( - - theme.transitions.create(['background-color', 'outline'], { - duration: theme.transitions.duration.standard, - }), - }, - '&&& .updating': { - backgroundColor: 'rgb(92 199 68 / 25%)', - outline: '1px solid rgb(92 199 68 / 35%)', - outlineOffset: '-1px', - transition: 'none', - }, - }} - > - - - ); -} diff --git a/docs/data/data-grid/performance/GridWithReactMemo.tsx.preview b/docs/data/data-grid/performance/GridWithReactMemo.tsx.preview deleted file mode 100644 index f1bd1aea7658a..0000000000000 --- a/docs/data/data-grid/performance/GridWithReactMemo.tsx.preview +++ /dev/null @@ -1,10 +0,0 @@ - \ 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..ac329106c334c 100644 --- a/docs/data/data-grid/performance/performance.md +++ b/docs/data/data-grid/performance/performance.md @@ -2,43 +2,52 @@

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 ( + + ); +} -:::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. -::: +``` ## API From baa5dcadfd844f055b35c698e2df801537a494a8 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Wed, 24 May 2023 18:09:25 -0400 Subject: [PATCH 11/72] fix: add enough storage for the memoization cache --- .../hooks/features/virtualization/useGridVirtualScroller.tsx | 5 +++++ 1 file changed, 5 insertions(+) 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 f01c15850cd89..59ee2695add45 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 @@ -106,6 +106,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 +153,7 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { (columns: GridStateColDef[], firstColumnToRender: number, lastColumnToRender: number) => { return columns.slice(firstColumnToRender, lastColumnToRender); }, + MEMOIZE_OPTIONS, ), ); From 83394e77385a44d100c5533ebba7c91457599bc3 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Wed, 24 May 2023 21:39:36 -0400 Subject: [PATCH 12/72] fix: missing update --- packages/grid/x-data-grid/src/components/GridRow.tsx | 8 ++++++-- .../x-data-grid/src/hooks/features/focus/useGridFocus.ts | 1 - 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/grid/x-data-grid/src/components/GridRow.tsx b/packages/grid/x-data-grid/src/components/GridRow.tsx index decbc6d24fb43..09d410a6f41b1 100644 --- a/packages/grid/x-data-grid/src/components/GridRow.tsx +++ b/packages/grid/x-data-grid/src/components/GridRow.tsx @@ -14,7 +14,7 @@ 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, shallowCompare } from '../hooks/utils/useGridSelector'; import { GridRowClassNameParams } from '../models/params/gridRowParams'; import { useGridVisibleRows } from '../hooks/utils/useGridVisibleRows'; import { findParentElementFromClassName } from '../utils/domUtils'; @@ -292,7 +292,11 @@ const GridRow = React.forwardRef(function GridRow( ); }; - const sizes = apiRef.current.unstable_getRowInternalSizes(rowId); + const sizes = useGridSelector( + apiRef, + () => ({ ...apiRef.current.unstable_getRowInternalSizes(rowId) }), + shallowCompare, + ); let minHeight = rowHeight; if (minHeight === 'auto' && sizes) { 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 48fa7d52fc1d9..108855acedc4e 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 @@ -17,7 +17,6 @@ import { GridStateInitializer } from '../../utils/useGridInitializeState'; import { gridVisibleColumnDefinitionsSelector } from '../columns/gridColumnsSelector'; import { getVisibleRows } from '../../utils/useGridVisibleRows'; import { clamp } from '../../../utils/utils'; -import { useGridSelector } from '../../utils/useGridSelector'; import { GridCellCoordinates } from '../../../models/gridCell'; import { GridRowEntry } from '../../../models/gridRows'; import { gridPinnedRowsSelector } from '../rows/gridRowsSelector'; From 15df13b9c14ece8a7bdefb5b192511ff748eacea Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Wed, 24 May 2023 21:53:56 -0400 Subject: [PATCH 13/72] test: keyboard --- .../grid/x-data-grid/src/tests/keyboard.DataGrid.test.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 06f067396b145..95639f0db6dca 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 @@ -600,17 +600,17 @@ describe(' - Keyboard', () => { it('should go back to same header when pressing "ArrowUp" and "ArrowDown" from column header', () => { render(); - act(() => getColumnHeaderCell(4, 2).focus()); + act(() => getColumnHeaderCell(2, 2).focus()); // column with field "price3M" - expectAriaCoordinate(document.activeElement, { rowIndex: 3, colIndex: 5 }); + expectAriaCoordinate(document.activeElement, { rowIndex: 3, colIndex: 3 }); fireEvent.keyDown(document.activeElement!, { key: 'ArrowUp' }); // group "prices 234" - expectAriaCoordinate(document.activeElement, { rowIndex: 2, colIndex: 4 }); + expectAriaCoordinate(document.activeElement, { rowIndex: 2, colIndex: 3 }); fireEvent.keyDown(document.activeElement!, { key: 'ArrowDown' }); // column with field "price3M" - expectAriaCoordinate(document.activeElement, { rowIndex: 3, colIndex: 5 }); + expectAriaCoordinate(document.activeElement, { rowIndex: 3, colIndex: 3 }); }); }); From 8db4508bafae6bcc7305ed6b6dee4bcfc01d2659 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Wed, 24 May 2023 22:49:07 -0400 Subject: [PATCH 14/72] lint --- docs/data/data-grid/performance/performance.md | 16 ++++------------ .../x-data-grid/src/components/cell/GridCell.tsx | 1 + .../columnSelection/GridCellCheckboxRenderer.tsx | 2 -- .../useGridKeyboardNavigation.ts | 1 - .../src/hooks/features/rows/useGridParamsApi.ts | 11 ----------- .../virtualization/useGridVirtualScroller.tsx | 2 +- .../src/hooks/utils/useGridSelector.ts | 5 +++-- .../src/tests/rowSelection.DataGrid.test.tsx | 1 - .../x-data-grid/src/utils/fastShallowCompare.ts | 4 ++++ 9 files changed, 13 insertions(+), 30 deletions(-) diff --git a/docs/data/data-grid/performance/performance.md b/docs/data/data-grid/performance/performance.md index ac329106c334c..2853f200495b6 100644 --- a/docs/data/data-grid/performance/performance.md +++ b/docs/data/data-grid/performance/performance.md @@ -21,7 +21,6 @@ function Component({ rows }) { /> ); } - ``` An easy way to prevent re-renders is to extract any object that can be a static object, and to memoize any object that @@ -33,20 +32,13 @@ const slots = { }; function Component({ rows }) { - const cellModesModel = React.useMemo(() => - ({ [rows[0].id]: { name: { mode: GridCellModes.Edit } } }), - [rows] + const cellModesModel = React.useMemo( + () => ({ [rows[0].id]: { name: { mode: GridCellModes.Edit } } }), + [rows], ); - return ( - - ); + return ; } - ``` ## API diff --git a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx index caea23b3f7368..435b29f0eeebd 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -175,6 +175,7 @@ const GridCell = React.forwardRef((props, ref) => const classes = useUtilityClasses(ownerState); let children: React.ReactNode; + /* eslint-disable-next-line no-lone-blocks */ { if (editCellState == null && column.renderCell) { children = column.renderCell({ ...cellParams, api: apiRef.current }); diff --git a/packages/grid/x-data-grid/src/components/columnSelection/GridCellCheckboxRenderer.tsx b/packages/grid/x-data-grid/src/components/columnSelection/GridCellCheckboxRenderer.tsx index add078df1f49a..7353974fabb98 100644 --- a/packages/grid/x-data-grid/src/components/columnSelection/GridCellCheckboxRenderer.tsx +++ b/packages/grid/x-data-grid/src/components/columnSelection/GridCellCheckboxRenderer.tsx @@ -6,10 +6,8 @@ import { } from '@mui/utils'; import type { GridRenderCellParams } from '../../models/params/gridCellParams'; import { isSpaceKey } from '../../utils/keyboardUtils'; -import { useGridSelector } from '../../hooks'; import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; -import { selectedIdsLookupSelector } from '../../hooks/features/rowSelection'; import { getDataGridUtilityClass } from '../../constants/gridClasses'; import type { DataGridProcessedProps } from '../../models/props/DataGridProps'; import type { GridRowSelectionCheckboxParams } from '../../models/params/gridRowSelectionCheckboxParams'; 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 8ea78aadb9b9e..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, 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 5c845a86f8c39..4097418326c50 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,17 +12,6 @@ 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; -} - /** * @requires useGridColumns (method) * @requires useGridRows (method) 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 59ee2695add45..c1132d36fafb1 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 @@ -108,7 +108,7 @@ interface ContainerDimensions { // 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 } +const MEMOIZE_OPTIONS = { maxSize: 3 }; export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { const apiRef = useGridPrivateApiContext(); 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 cc2ce5aabf9a5..3bb0d24b84671 100644 --- a/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts +++ b/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts @@ -50,16 +50,17 @@ export const useGridSelector = ( stateRef.current = state; selectorRef.current = selector; + /* eslint-disable react-hooks/exhaustive-deps */ React.useEffect(() => { return apiRef.current.store.subscribe(() => { - const state = stateRef.current; const newState = applySelector(apiRef, selectorRef.current!); - if (!equals(state, newState)) { + if (!equals(stateRef.current, newState)) { stateRef.current = newState; setState(newState); } }); }, EMPTY); + /* eslint-enable react-hooks/exhaustive-deps */ return state; }; 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 442fa6fdcaf81..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, diff --git a/packages/grid/x-data-grid/src/utils/fastShallowCompare.ts b/packages/grid/x-data-grid/src/utils/fastShallowCompare.ts index 0d9f9f0965569..f2a7592282d57 100644 --- a/packages/grid/x-data-grid/src/utils/fastShallowCompare.ts +++ b/packages/grid/x-data-grid/src/utils/fastShallowCompare.ts @@ -11,6 +11,8 @@ export function fastShallowCompare | null | un return false; } + /* eslint-disable no-restricted-syntax */ + /* eslint-disable guard-for-in */ for (const key in a) { if (!(key in b)) { return false; @@ -25,6 +27,8 @@ export function fastShallowCompare | null | un return false; } } + /* eslint-enable no-restricted-syntax */ + /* eslint-enable guard-for-in */ return true; } From 138dba77936385405ec92688aaabe323c74deb05 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Wed, 24 May 2023 23:06:02 -0400 Subject: [PATCH 15/72] docs: autogenerate --- scripts/x-data-grid-premium.exports.json | 3 +++ scripts/x-data-grid-pro.exports.json | 3 +++ scripts/x-data-grid.exports.json | 3 +++ 3 files changed, 9 insertions(+) diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index ae513ba15ea75..bc7cf6f8b02d9 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -1,4 +1,5 @@ [ + { "name": "applySelector", "kind": "Function" }, { "name": "arSD", "kind": "Variable" }, { "name": "BaseButtonPropsOverrides", "kind": "Interface" }, { "name": "BaseCheckboxPropsOverrides", "kind": "Interface" }, @@ -32,6 +33,7 @@ { "name": "DataGridPro", "kind": "Function" }, { "name": "deDE", "kind": "Variable" }, { "name": "DEFAULT_GRID_COL_TYPE_KEY", "kind": "Variable" }, + { "name": "defaultCompare", "kind": "Variable" }, { "name": "ElementSize", "kind": "Interface" }, { "name": "elGR", "kind": "Variable" }, { "name": "enUS", "kind": "Variable" }, @@ -608,6 +610,7 @@ { "name": "selectedGridRowsSelector", "kind": "Variable" }, { "name": "selectedIdsLookupSelector", "kind": "Variable" }, { "name": "setupExcelExportWebWorker", "kind": "Function" }, + { "name": "shallowCompare", "kind": "Variable" }, { "name": "skSK", "kind": "Variable" }, { "name": "SUBMIT_FILTER_DATE_STROKE_TIME", "kind": "Variable" }, { "name": "SUBMIT_FILTER_STROKE_TIME", "kind": "Variable" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index 95b1e32a0b994..d80c1c769de83 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -1,4 +1,5 @@ [ + { "name": "applySelector", "kind": "Function" }, { "name": "arSD", "kind": "Variable" }, { "name": "BaseButtonPropsOverrides", "kind": "Interface" }, { "name": "BaseCheckboxPropsOverrides", "kind": "Interface" }, @@ -31,6 +32,7 @@ { "name": "DataGridProProps", "kind": "Interface" }, { "name": "deDE", "kind": "Variable" }, { "name": "DEFAULT_GRID_COL_TYPE_KEY", "kind": "Variable" }, + { "name": "defaultCompare", "kind": "Variable" }, { "name": "ElementSize", "kind": "Interface" }, { "name": "elGR", "kind": "Variable" }, { "name": "enUS", "kind": "Variable" }, @@ -560,6 +562,7 @@ { "name": "selectedGridRowsCountSelector", "kind": "Variable" }, { "name": "selectedGridRowsSelector", "kind": "Variable" }, { "name": "selectedIdsLookupSelector", "kind": "Variable" }, + { "name": "shallowCompare", "kind": "Variable" }, { "name": "skSK", "kind": "Variable" }, { "name": "SUBMIT_FILTER_DATE_STROKE_TIME", "kind": "Variable" }, { "name": "SUBMIT_FILTER_STROKE_TIME", "kind": "Variable" }, diff --git a/scripts/x-data-grid.exports.json b/scripts/x-data-grid.exports.json index 532a331907fa6..d710555731dc3 100644 --- a/scripts/x-data-grid.exports.json +++ b/scripts/x-data-grid.exports.json @@ -1,4 +1,5 @@ [ + { "name": "applySelector", "kind": "Function" }, { "name": "arSD", "kind": "Variable" }, { "name": "BaseButtonPropsOverrides", "kind": "Interface" }, { "name": "BaseCheckboxPropsOverrides", "kind": "Interface" }, @@ -29,6 +30,7 @@ { "name": "DataGridProps", "kind": "TypeAlias" }, { "name": "deDE", "kind": "Variable" }, { "name": "DEFAULT_GRID_COL_TYPE_KEY", "kind": "Variable" }, + { "name": "defaultCompare", "kind": "Variable" }, { "name": "ElementSize", "kind": "Interface" }, { "name": "elGR", "kind": "Variable" }, { "name": "enUS", "kind": "Variable" }, @@ -514,6 +516,7 @@ { "name": "selectedGridRowsCountSelector", "kind": "Variable" }, { "name": "selectedGridRowsSelector", "kind": "Variable" }, { "name": "selectedIdsLookupSelector", "kind": "Variable" }, + { "name": "shallowCompare", "kind": "Variable" }, { "name": "skSK", "kind": "Variable" }, { "name": "SUBMIT_FILTER_DATE_STROKE_TIME", "kind": "Variable" }, { "name": "SUBMIT_FILTER_STROKE_TIME", "kind": "Variable" }, From fa0fc7e53c18787d4fc1aa461cef49e1c7f159c4 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Thu, 25 May 2023 11:12:51 -0400 Subject: [PATCH 16/72] lint --- packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3bb0d24b84671..b4c716c0634c0 100644 --- a/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts +++ b/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts @@ -27,7 +27,7 @@ export function applySelector( return selector(apiRef.current.state); } -export const defaultCompare = Object.is; // XXX: Do we need to polyfill? +export const defaultCompare = Object.is; export const shallowCompare = fastShallowCompare; export const useGridSelector = ( From e70c982c7bbd57c036a927d43f35b2d84ccdf346 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Thu, 25 May 2023 14:22:56 -0400 Subject: [PATCH 17/72] lint --- packages/grid/x-data-grid/src/utils/fastShallowCompare.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grid/x-data-grid/src/utils/fastShallowCompare.ts b/packages/grid/x-data-grid/src/utils/fastShallowCompare.ts index f2a7592282d57..f042c643ca5a7 100644 --- a/packages/grid/x-data-grid/src/utils/fastShallowCompare.ts +++ b/packages/grid/x-data-grid/src/utils/fastShallowCompare.ts @@ -1,6 +1,6 @@ const is = Object.is; -export function fastShallowCompare | null | undefined>( +export function fastShallowCompare | null>( a: T, b: T, ) { From 3b81c02a53e1a753396c17a6d131c171e5108268 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Thu, 25 May 2023 15:31:05 -0400 Subject: [PATCH 18/72] lint --- .../src/components/cell/GridCell.tsx | 32 +++++++++++++++++-- .../hooks/features/rows/useGridParamsApi.ts | 1 + 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx index 435b29f0eeebd..6a8c751965a73 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -10,7 +10,7 @@ import { } from '@mui/utils'; import { getDataGridUtilityClass, gridClasses } from '../../constants/gridClasses'; import { GridCellEventLookup, GridEvents, GridCellModes, GridRowId } from '../../models'; -import { GridRenderEditCellParams, FocusElement } from '../../models/params/gridCellParams'; +import { GridRenderEditCellParams, FocusElement, GridCellParams } from '../../models/params/gridCellParams'; import { GridColDef, GridAlignment } from '../../models/colDef/gridColDef'; import { GridTreeNodeWithRender } from '../../models/gridRows'; import { useGridSelector, shallowCompare } from '../../hooks/utils/useGridSelector'; @@ -41,6 +41,31 @@ export interface GridCellProps { [x: string]: any; } +const EMPTY_CELL_PARAMS: GridCellParams = { + 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, +}; + + // Based on https://stackoverflow.com/a/59518678 let cachedSupportsPreventScroll: boolean; function doesSupportPreventScroll(): boolean { @@ -135,7 +160,7 @@ const GridCell = React.forwardRef((props, ref) => return apiRef.current.getCellParams(rowId, field); } catch (e) { if ((e as Error).message.startsWith('No row with id')) { - return null as any; + return EMPTY_CELL_PARAMS; } throw e; } @@ -313,6 +338,9 @@ const GridCell = React.forwardRef((props, ref) => }; } + if (cellParams === EMPTY_CELL_PARAMS) + return null; + const draggableEventHandlers = disableDragEvents ? null : { 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 4097418326c50..bc0aa480ede7a 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 @@ -102,6 +102,7 @@ export function useGridParamsApi(apiRef: React.MutableRefObject Date: Mon, 29 May 2023 18:52:54 -0400 Subject: [PATCH 19/72] refactor --- .../x-data-grid/src/components/cell/GridCell.tsx | 16 +--------------- .../src/utils/doesSupportPreventScroll.ts | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 15 deletions(-) create mode 100644 packages/grid/x-data-grid/src/utils/doesSupportPreventScroll.ts diff --git a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx index 6a8c751965a73..b11f03de3067b 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -8,6 +8,7 @@ import { unstable_ownerDocument as ownerDocument, unstable_capitalize as capitalize, } from '@mui/utils'; +import { doesSupportPreventScroll } from '../../utils/doesSupportPreventScroll'; import { getDataGridUtilityClass, gridClasses } from '../../constants/gridClasses'; import { GridCellEventLookup, GridEvents, GridCellModes, GridRowId } from '../../models'; import { GridRenderEditCellParams, FocusElement, GridCellParams } from '../../models/params/gridCellParams'; @@ -65,21 +66,6 @@ const EMPTY_CELL_PARAMS: GridCellParams = isEditable: false, }; - -// 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 OwnerState = Pick & { isEditable?: boolean; isSelected?: boolean; 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..eedb208536d56 --- /dev/null +++ b/packages/grid/x-data-grid/src/utils/doesSupportPreventScroll.ts @@ -0,0 +1,14 @@ + +// 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; +} From de7486fb5e65fd8835b9c5da23b622c3cde5d047 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Mon, 29 May 2023 19:28:55 -0400 Subject: [PATCH 20/72] refactor --- .../x-data-grid/src/components/GridRow.tsx | 7 +- .../src/components/cell/GridCell.tsx | 271 ++++++++++++------ .../src/utils/doesSupportPreventScroll.ts | 1 - .../src/utils/fastShallowCompare.ts | 5 +- 4 files changed, 187 insertions(+), 97 deletions(-) diff --git a/packages/grid/x-data-grid/src/components/GridRow.tsx b/packages/grid/x-data-grid/src/components/GridRow.tsx index 09d410a6f41b1..1e2746f4629a2 100644 --- a/packages/grid/x-data-grid/src/components/GridRow.tsx +++ b/packages/grid/x-data-grid/src/components/GridRow.tsx @@ -25,6 +25,7 @@ import { gridSortModelSelector } from '../hooks/features/sorting/gridSortingSele import { gridRowMaximumTreeDepthSelector } from '../hooks/features/rows/gridRowsSelector'; import { gridColumnGroupsHeaderMaxDepthSelector } from '../hooks/features/columnGrouping/gridColumnGroupsSelector'; import { randomNumberBetween } from '../utils/utils'; +import { GridCellWrapper } from './cell/GridCell'; import type { GridCellProps } from './cell/GridCell'; import { gridEditRowsStateSelector } from '../hooks/features/editing/gridEditingSelectors'; @@ -259,8 +260,6 @@ const GridRow = React.forwardRef(function GridRow( const rowReordering = (rootProps as any).rowReordering as boolean; - const CellComponent = slots.cell; - const getCell = ( column: GridStateColDef, cellProps: Pick< @@ -276,7 +275,7 @@ const GridRow = React.forwardRef(function GridRow( Object.keys(editRowsState).length > 0); return ( - (function GridRow( const contentWidth = Math.round(randomNumber()); cells.push( - = GridCellWrapperProps & { + field: string; + formattedValue?: F; + hasFocus?: boolean; + isEditable?: boolean; + isSelected?: boolean; + value?: V; + cellMode?: GridCellMode; + children: React.ReactNode; + tabIndex: 0 | -1; +}; + const EMPTY_CELL_PARAMS: GridCellParams = { id: -1, field: '__unset__', @@ -92,49 +114,13 @@ const useUtilityClasses = (ownerState: OwnerState) => { let warnedOnce = false; -const GridCell = React.forwardRef((props, ref) => { - const { - align, - colIndex, - column, - height, - rowId, - width, - className, - showRightBorder, - extendRowFullWidth, - row, - colSpan, - disableDragEvents, - onClick, - onDoubleClick, - onMouseDown, - onMouseUp, - onMouseOver, - onKeyDown, - onKeyUp, - onDragEnter, - onDragOver, - ...other - } = props; +const GridCellWrapper = React.forwardRef((props, ref) => { + const { column, rowId } = props; - const field = column.field; - const cellRef = React.useRef(null); - const handleRef = useForkRef(ref, cellRef); - const focusElementRef = React.useRef(null); const apiRef = useGridApiContext(); const rootProps = useGridRootProps(); - const editRowsState = useGridSelector(apiRef, gridEditRowsStateSelector); - - const { classes: rootClasses, getCellClassName } = rootProps; - - const isSelected = useGridSelector(apiRef, () => - apiRef.current.unstable_applyPipeProcessors('isCellSelected', false, { - id: rowId, - field, - }), - ); + const field = column.field; const cellParams = useGridSelector( apiRef, @@ -154,6 +140,23 @@ const GridCell = React.forwardRef((props, ref) => shallowCompare, ); + const isSelected = useGridSelector(apiRef, () => + apiRef.current.unstable_applyPipeProcessors('isCellSelected', false, { + id: rowId, + field, + }), + ); + + const { cellMode, hasFocus, isEditable, value, formattedValue } = cellParams; + + const managesOwnFocus = column.type === 'actions'; + const tabIndex = + (cellMode === 'view' || !isEditable) && !managesOwnFocus ? cellParams.tabIndex : -1; + + const editRowsState = useGridSelector(apiRef, gridEditRowsStateSelector); + + const { classes: rootClasses, getCellClassName } = rootProps; + const classNames = apiRef.current.unstable_applyPipeProcessors('cellClassName', [], { id: rowId, field, @@ -174,54 +177,116 @@ const GridCell = React.forwardRef((props, ref) => } const editCellState = editRowsState[rowId]?.[column.field] ?? null; - const managesOwnFocus = column.type === 'actions'; - const cellMode = cellParams.cellMode; - const hasFocus = cellParams.hasFocus; - const isEditable = cellParams.isEditable; - const valueToRender = - cellParams.formattedValue == null ? cellParams.value : cellParams.formattedValue; + let children: React.ReactNode; + if (editCellState == null && column.renderCell) { + children = column.renderCell({ ...cellParams, api: apiRef.current }); + classNames.push(clsx(gridClasses['cell--withRenderer'], rootClasses?.['cell--withRenderer'])); + } - const ownerState = { align, showRightBorder, isEditable, classes: rootProps.classes, isSelected }; - const classes = useUtilityClasses(ownerState); + if (editCellState != null && column.renderEditCell) { + const updatedRow = apiRef.current.getRowWithUpdatedValues(rowId, column.field); - let children: React.ReactNode; - /* eslint-disable-next-line no-lone-blocks */ - { - if (editCellState == null && column.renderCell) { - children = column.renderCell({ ...cellParams, api: apiRef.current }); - classNames.push(clsx(gridClasses['cell--withRenderer'], rootClasses?.['cell--withRenderer'])); - } + // eslint-disable-next-line @typescript-eslint/naming-convention + const { changeReason, unstable_updateValueOnRender, ...editCellStateRest } = editCellState; - if (editCellState != null && column.renderEditCell) { - const updatedRow = apiRef.current.getRowWithUpdatedValues(rowId, column.field); + const params: GridRenderEditCellParams = { + ...cellParams, + row: updatedRow, + ...editCellStateRest, + api: apiRef.current, + }; - // eslint-disable-next-line @typescript-eslint/naming-convention - const { changeReason, unstable_updateValueOnRender, ...editCellStateRest } = editCellState; + children = column.renderEditCell(params); + classNames.push(clsx(gridClasses['cell--editing'], rootClasses?.['cell--editing'])); + } - const params: GridRenderEditCellParams = { - ...cellParams, - row: updatedRow, - ...editCellStateRest, - api: apiRef.current, - }; + if (cellParams === EMPTY_CELL_PARAMS) return null; - children = column.renderEditCell(params); - classNames.push(clsx(gridClasses['cell--editing'], rootClasses?.['cell--editing'])); - } + const { slots, slotProps } = rootProps; - if (children === undefined) { - const valueString = valueToRender?.toString(); - children = ( -
- {valueString} -
- ); - } + const CellComponent = slots.cell; - if (React.isValidElement(children) && managesOwnFocus) { - children = React.cloneElement(children, { focusElementRef }); - } + const cellProps: GridCellProps = Object.assign( + { + ref, + field, + formattedValue, + hasFocus, + isEditable, + isSelected, + value, + cellMode, + children, + tabIndex, + }, + props, + slotProps?.cell, + ); + + return React.createElement(CellComponent, cellProps); +}); + +const GridCell = React.forwardRef((props, ref) => { + const { + align, + children: childrenProp, + colIndex, + column, + cellMode, + field, + formattedValue, + hasFocus, + height, + isEditable, + isSelected, + rowId, + tabIndex, + value, + width, + className, + showRightBorder, + extendRowFullWidth, + row, + colSpan, + disableDragEvents, + onClick, + onDoubleClick, + onMouseDown, + onMouseUp, + onMouseOver, + onKeyDown, + onKeyUp, + onDragEnter, + onDragOver, + ...other + } = props; + + const cellRef = React.useRef(null); + const handleRef = useForkRef(ref, cellRef); + const focusElementRef = React.useRef(null); + const apiRef = useGridApiContext(); + const rootProps = useGridRootProps(); + + const managesOwnFocus = column.type === 'actions'; + + const ownerState = { align, showRightBorder, isEditable, classes: rootProps.classes, isSelected }; + const classes = useUtilityClasses(ownerState); + + const valueToRender = formattedValue == null ? value : formattedValue; + + let children: React.ReactNode = childrenProp; + if (children === undefined) { + const valueString = valueToRender?.toString(); + children = ( +
+ {valueString} +
+ ); + } + + if (React.isValidElement(children) && managesOwnFocus) { + children = React.cloneElement(children, { focusElementRef }); } const publishMouseUp = React.useCallback( @@ -324,9 +389,6 @@ const GridCell = React.forwardRef((props, ref) => }; } - if (cellParams === EMPTY_CELL_PARAMS) - return null; - const draggableEventHandlers = disableDragEvents ? null : { @@ -337,14 +399,14 @@ const GridCell = React.forwardRef((props, ref) => return (
((props, ref) => ); }); -const MemoizedCell = React.memo(GridCell); +const MemoizedCellWrapper = React.memo(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 -------------------------------- @@ -369,12 +455,19 @@ GridCell.propTypes = { // | To update them edit the TypeScript types and run "yarn proptypes" | // ---------------------------------------------------------------------- align: PropTypes.oneOf(['center', 'left', 'right']), + cellMode: PropTypes.oneOf(['edit', 'view']), + children: PropTypes.node, className: PropTypes.string, colIndex: PropTypes.number, colSpan: PropTypes.number, column: PropTypes.object, disableDragEvents: PropTypes.bool, + field: PropTypes.string, + formattedValue: PropTypes.any, + hasFocus: PropTypes.bool, height: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number]), + isEditable: PropTypes.bool, + isSelected: PropTypes.bool, onClick: PropTypes.func, onDoubleClick: PropTypes.func, onDragEnter: PropTypes.func, @@ -384,7 +477,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 }; +export { MemoizedCellWrapper as GridCellWrapper, GridCell }; diff --git a/packages/grid/x-data-grid/src/utils/doesSupportPreventScroll.ts b/packages/grid/x-data-grid/src/utils/doesSupportPreventScroll.ts index eedb208536d56..f51b2f11ce465 100644 --- a/packages/grid/x-data-grid/src/utils/doesSupportPreventScroll.ts +++ b/packages/grid/x-data-grid/src/utils/doesSupportPreventScroll.ts @@ -1,4 +1,3 @@ - // Based on https://stackoverflow.com/a/59518678 let cachedSupportsPreventScroll: boolean; export function doesSupportPreventScroll(): boolean { diff --git a/packages/grid/x-data-grid/src/utils/fastShallowCompare.ts b/packages/grid/x-data-grid/src/utils/fastShallowCompare.ts index f042c643ca5a7..5eb3f84df2b32 100644 --- a/packages/grid/x-data-grid/src/utils/fastShallowCompare.ts +++ b/packages/grid/x-data-grid/src/utils/fastShallowCompare.ts @@ -1,9 +1,6 @@ const is = Object.is; -export function fastShallowCompare | null>( - a: T, - b: T, -) { +export function fastShallowCompare | null>(a: T, b: T) { if (a === b) { return true; } From 4ff14c71f2a4f0a7756dfb30ac25c92791d9ce66 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Mon, 29 May 2023 19:32:23 -0400 Subject: [PATCH 21/72] fix --- packages/grid/x-data-grid/src/components/cell/GridCell.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx index 88b6430acaa9c..384d98c392781 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -208,6 +208,8 @@ const GridCellWrapper = React.forwardRef(( const CellComponent = slots.cell; const cellProps: GridCellProps = Object.assign( + {}, + props, { ref, field, @@ -219,8 +221,8 @@ const GridCellWrapper = React.forwardRef(( cellMode, children, tabIndex, + className: clsx(classNames), }, - props, slotProps?.cell, ); From c47bf239934e1cdfe9ce8c456ff257959980a5f7 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Mon, 29 May 2023 19:35:18 -0400 Subject: [PATCH 22/72] lint --- packages/grid/x-data-grid/src/components/GridRow.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/grid/x-data-grid/src/components/GridRow.tsx b/packages/grid/x-data-grid/src/components/GridRow.tsx index 1e2746f4629a2..73875e55876be 100644 --- a/packages/grid/x-data-grid/src/components/GridRow.tsx +++ b/packages/grid/x-data-grid/src/components/GridRow.tsx @@ -293,7 +293,11 @@ const GridRow = React.forwardRef(function GridRow( const sizes = useGridSelector( apiRef, - () => ({ ...apiRef.current.unstable_getRowInternalSizes(rowId) }), + () => { + const sizes = apiRef.current.unstable_getRowInternalSizes(rowId); + if (!sizes) return null; + return Object.assign({}, sizes); + }, shallowCompare, ); From 5ac0d681278d316e99d8c5076af430e31e20a0e3 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Mon, 29 May 2023 19:39:10 -0400 Subject: [PATCH 23/72] lint --- packages/grid/x-data-grid/src/components/cell/GridCell.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx index 384d98c392781..e0f12987b02f4 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -114,6 +114,7 @@ const useUtilityClasses = (ownerState: OwnerState) => { let warnedOnce = false; +// TODO(v7): Remove the wrapper, merge with the cell component const GridCellWrapper = React.forwardRef((props, ref) => { const { column, rowId } = props; From 91aefeb9e6dc16ef0fa5b95543ed4256287c11d2 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Mon, 29 May 2023 19:40:41 -0400 Subject: [PATCH 24/72] lint --- packages/grid/x-data-grid/src/components/GridRow.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/grid/x-data-grid/src/components/GridRow.tsx b/packages/grid/x-data-grid/src/components/GridRow.tsx index 73875e55876be..4fe153908fa3f 100644 --- a/packages/grid/x-data-grid/src/components/GridRow.tsx +++ b/packages/grid/x-data-grid/src/components/GridRow.tsx @@ -256,7 +256,7 @@ const GridRow = React.forwardRef(function GridRow( [apiRef, onClick, publish, rowId], ); - const { slots, slotProps, disableColumnReorder } = rootProps; + const { slots, disableColumnReorder } = rootProps; const rowReordering = (rootProps as any).rowReordering as boolean; @@ -286,7 +286,6 @@ const GridRow = React.forwardRef(function GridRow( colIndex={cellProps.indexRelativeToAllColumns} colSpan={cellProps.colSpan} disableDragEvents={disableDragEvents} - {...slotProps?.cell} /> ); }; From 56e73e70a13dc54f5d9f199540bcad1c76792a33 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Mon, 29 May 2023 19:47:19 -0400 Subject: [PATCH 25/72] lint --- .../src/components/cell/GridCell.tsx | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx index e0f12987b02f4..0fcd8deb2318a 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -265,33 +265,15 @@ const GridCell = React.forwardRef((props, ref) => ...other } = props; + const valueToRender = formattedValue == null ? value : formattedValue; const cellRef = React.useRef(null); const handleRef = useForkRef(ref, cellRef); const focusElementRef = React.useRef(null); const apiRef = useGridApiContext(); const rootProps = useGridRootProps(); - - const managesOwnFocus = column.type === 'actions'; - const ownerState = { align, showRightBorder, isEditable, classes: rootProps.classes, isSelected }; const classes = useUtilityClasses(ownerState); - const valueToRender = formattedValue == null ? value : formattedValue; - - let children: React.ReactNode = childrenProp; - if (children === undefined) { - const valueString = valueToRender?.toString(); - children = ( -
- {valueString} -
- ); - } - - if (React.isValidElement(children) && managesOwnFocus) { - children = React.cloneElement(children, { focusElementRef }); - } - const publishMouseUp = React.useCallback( (eventName: GridEvents) => (event: React.MouseEvent) => { const params = apiRef.current.getCellParams(rowId, field || ''); @@ -392,6 +374,22 @@ const GridCell = React.forwardRef((props, ref) => }; } + const managesOwnFocus = column.type === 'actions'; + + let children: React.ReactNode = childrenProp; + if (children === undefined) { + const valueString = valueToRender?.toString(); + children = ( +
+ {valueString} +
+ ); + } + + if (React.isValidElement(children) && managesOwnFocus) { + children = React.cloneElement(children, { focusElementRef }); + } + const draggableEventHandlers = disableDragEvents ? null : { From 42b9b7234e022c3ce18bdd70cbc04e433d552ae2 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Mon, 29 May 2023 19:56:41 -0400 Subject: [PATCH 26/72] refactor --- .../src/hooks/utils/useGridSelector.ts | 49 +++++++++++++------ .../x-data-grid/src/hooks/utils/useLazyRef.ts | 13 +++++ 2 files changed, 47 insertions(+), 15 deletions(-) create mode 100644 packages/grid/x-data-grid/src/hooks/utils/useLazyRef.ts 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 b4c716c0634c0..02072523d2140 100644 --- a/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts +++ b/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts @@ -1,6 +1,7 @@ import * as React from 'react'; import { GridApiCommon } from '../../models/api/gridApiCommon'; import { OutputSelector } from '../../utils/createSelector'; +import { useLazyRef } from '../../hooks/utils/useLazyRef'; import { buildWarning } from '../../utils/warning'; import { fastShallowCompare } from '../../utils/fastShallowCompare'; @@ -9,6 +10,7 @@ const stateNotInitializedWarning = buildWarning([ 'This hook can only be used inside the context of the grid.', ]); +const noop = () => {}; const EMPTY = [] as unknown[]; function isOutputSelector( @@ -30,6 +32,8 @@ export function applySelector( export const defaultCompare = Object.is; export const shallowCompare = fastShallowCompare; +const createRefs = () => ({ state: null, equals: null, selector: null } as any); + export const useGridSelector = ( apiRef: React.MutableRefObject, selector: ((state: Api['state']) => T) | OutputSelector, @@ -41,26 +45,41 @@ export const useGridSelector = ( } } - const selectorRef = React.useRef(); + const refs = useLazyRef< + { + state: T; + equals: typeof equals; + selector: typeof selector; + }, + never + >(createRefs); + + const didInit = refs.current.state !== null; + const [state, setState] = React.useState( - (selectorRef.current ? null : applySelector(apiRef, selector)) as T, + (didInit ? null : applySelector(apiRef, selector)) as T, ); - const stateRef = React.useRef(state); - - stateRef.current = state; - selectorRef.current = selector; /* eslint-disable react-hooks/exhaustive-deps */ - React.useEffect(() => { - return apiRef.current.store.subscribe(() => { - const newState = applySelector(apiRef, selectorRef.current!); - if (!equals(stateRef.current, newState)) { - stateRef.current = newState; - setState(newState); - } - }); - }, EMPTY); + React.useEffect( + didInit + ? noop + : () => { + return apiRef.current.store.subscribe(() => { + const newState = applySelector(apiRef, refs.current.selector); + if (!equals(refs.current.state, newState)) { + refs.current.state = newState; + setState(newState); + } + }); + }, + EMPTY, + ); /* eslint-enable react-hooks/exhaustive-deps */ + refs.current.state = state; + refs.current.equals = equals; + refs.current.selector = selector; + 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; +} From 2105746035e16c106deaf9825617d2fd44b9853a Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Mon, 29 May 2023 20:14:38 -0400 Subject: [PATCH 27/72] lint --- .eslintrc.js | 1 + package.json | 1 + packages/grid/x-data-grid/src/components/GridRow.tsx | 6 +----- .../grid/x-data-grid/src/components/cell/GridCell.tsx | 9 +++++++-- .../grid/x-data-grid/src/hooks/utils/useGridSelector.ts | 2 +- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index e7bcdb065225d..67df2a1eb925f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -70,6 +70,7 @@ module.exports = { */ rules: { ...baseline.rules, + 'prefer-object-spread': 'off', 'import/prefer-default-export': 'off', // TODO move rule into the main repo once it has upgraded '@typescript-eslint/return-await': 'off', diff --git a/package.json b/package.json index 989d04a1b0d2d..3ce8d93ee5b5c 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": "eslint . --cache --report-unused-disable-directives --ext .js,.ts,.tsx --max-warnings 0 --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/src/components/GridRow.tsx b/packages/grid/x-data-grid/src/components/GridRow.tsx index 4fe153908fa3f..397c5a4608c10 100644 --- a/packages/grid/x-data-grid/src/components/GridRow.tsx +++ b/packages/grid/x-data-grid/src/components/GridRow.tsx @@ -292,11 +292,7 @@ const GridRow = React.forwardRef(function GridRow( const sizes = useGridSelector( apiRef, - () => { - const sizes = apiRef.current.unstable_getRowInternalSizes(rowId); - if (!sizes) return null; - return Object.assign({}, sizes); - }, + () => Object.assign({}, apiRef.current.unstable_getRowInternalSizes(rowId)), shallowCompare, ); diff --git a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx index 0fcd8deb2318a..bb516523a3b5c 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -31,7 +31,9 @@ import { gridFocusCellSelector } from '../../hooks/features/focus/gridFocusState import { gridEditRowsStateSelector } from '../../hooks/features/editing/gridEditingSelectors'; import type { DataGridProcessedProps } from '../../models/props/DataGridProps'; -export interface GridCellWrapperProps { +// These props are passed down to the cell component, eslint thinks they're not used +/* eslint-disable react/no-unused-prop-types */ +export type GridCellWrapperProps = { align: GridAlignment; className?: string; colIndex: number; @@ -51,6 +53,7 @@ export interface GridCellWrapperProps { onDragOver?: React.DragEventHandler; [x: string]: any; } +/* eslint-enable react/no-unused-prop-types */ export type GridCellProps = GridCellWrapperProps & { field: string; @@ -202,7 +205,9 @@ const GridCellWrapper = React.forwardRef(( classNames.push(clsx(gridClasses['cell--editing'], rootClasses?.['cell--editing'])); } - if (cellParams === EMPTY_CELL_PARAMS) return null; + if (cellParams === EMPTY_CELL_PARAMS) { + return null; + } const { slots, slotProps } = rootProps; 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 02072523d2140..7c93aa13cba18 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,7 @@ import * as React from 'react'; import { GridApiCommon } from '../../models/api/gridApiCommon'; import { OutputSelector } from '../../utils/createSelector'; -import { useLazyRef } from '../../hooks/utils/useLazyRef'; +import { useLazyRef } from './useLazyRef'; import { buildWarning } from '../../utils/warning'; import { fastShallowCompare } from '../../utils/fastShallowCompare'; From 5e1b1829b98fad438f7dd91df8ed5b00350ddefc Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Mon, 29 May 2023 20:34:45 -0400 Subject: [PATCH 28/72] lint# Changes to be committed: --- .../src/components/cell/GridCell.tsx | 2 +- .../src/hooks/utils/useGridSelector.ts | 32 ++++++++----------- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx index bb516523a3b5c..9b0a10fc5b016 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -52,7 +52,7 @@ export type GridCellWrapperProps = { onDragEnter?: React.DragEventHandler; onDragOver?: React.DragEventHandler; [x: string]: any; -} +}; /* eslint-enable react/no-unused-prop-types */ export type GridCellProps = GridCellWrapperProps & { 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 7c93aa13cba18..c46b6e91e2421 100644 --- a/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts +++ b/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts @@ -53,33 +53,27 @@ export const useGridSelector = ( }, never >(createRefs); - - const didInit = refs.current.state !== null; + const didInit = refs.current.selector !== null; const [state, setState] = React.useState( (didInit ? null : applySelector(apiRef, selector)) as T, ); - /* eslint-disable react-hooks/exhaustive-deps */ - React.useEffect( - didInit - ? noop - : () => { - return apiRef.current.store.subscribe(() => { - const newState = applySelector(apiRef, refs.current.selector); - if (!equals(refs.current.state, newState)) { - refs.current.state = newState; - setState(newState); - } - }); - }, - EMPTY, - ); - /* eslint-enable react-hooks/exhaustive-deps */ - refs.current.state = state; refs.current.equals = equals; refs.current.selector = selector; + /* eslint-disable react-hooks/exhaustive-deps */ + React.useEffect(() => { + 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); + } + }); + }, EMPTY); + /* eslint-enable react-hooks/exhaustive-deps */ + return state; }; From d804c32e3d417a314decaf0695b85c05f2e9e599 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Mon, 29 May 2023 20:51:10 -0400 Subject: [PATCH 29/72] doc --- .../performance/GridVisualization.js | 73 +++++++++++++++++++ .../performance/GridVisualization.tsx | 73 +++++++++++++++++++ .../data/data-grid/performance/performance.md | 7 ++ .../src/hooks/utils/useGridSelector.ts | 1 - 4 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 docs/data/data-grid/performance/GridVisualization.js create mode 100644 docs/data/data-grid/performance/GridVisualization.tsx diff --git a/docs/data/data-grid/performance/GridVisualization.js b/docs/data/data-grid/performance/GridVisualization.js new file mode 100644 index 0000000000000..b87db7d566d4d --- /dev/null +++ b/docs/data/data-grid/performance/GridVisualization.js @@ -0,0 +1,73 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { unstable_useForkRef as useForkRef } from '@mui/utils'; +import { DataGridPro, GridCell } from '@mui/x-data-grid-pro'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +const TraceUpdates = React.forwardRef((props, ref) => { + const { Component, ...other } = props; + const rootRef = React.useRef(); + const handleRef = useForkRef(rootRef, ref); + + React.useEffect(() => { + const root = rootRef.current; + root.classList.add('updating'); + root.classList.add('updated'); + + const timer = setTimeout(() => { + root.classList.remove('updating'); + }, 360); + + return () => { + clearTimeout(timer); + }; + }); + + return ; +}); + +const CellWithTracer = React.forwardRef((props, ref) => { + return ; +}); + +const slots = { + cell: CellWithTracer, +}; + +export default function GridVisualization() { + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 100, + editable: true, + maxColumns: 15, + }); + + return ( + + theme.transitions.create(['background-color', 'outline'], { + duration: theme.transitions.duration.standard, + }), + }, + '&&& .updating': { + backgroundColor: 'rgb(92 199 68 / 20%)', + outline: '1px solid rgb(92 199 68 / 35%)', + outlineOffset: '-1px', + transition: 'none', + }, + }} + > + + + ); +} diff --git a/docs/data/data-grid/performance/GridVisualization.tsx b/docs/data/data-grid/performance/GridVisualization.tsx new file mode 100644 index 0000000000000..2d62ee4dada1f --- /dev/null +++ b/docs/data/data-grid/performance/GridVisualization.tsx @@ -0,0 +1,73 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { unstable_useForkRef as useForkRef } from '@mui/utils'; +import { DataGridPro, GridCell } from '@mui/x-data-grid-pro'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +const TraceUpdates = React.forwardRef((props, ref) => { + const { Component, ...other } = props; + const rootRef = React.useRef(); + const handleRef = useForkRef(rootRef, ref); + + React.useEffect(() => { + const root = rootRef.current; + root!.classList.add('updating'); + root!.classList.add('updated'); + + const timer = setTimeout(() => { + root!.classList.remove('updating'); + }, 360); + + return () => { + clearTimeout(timer); + }; + }); + + return ; +}); + +const CellWithTracer = React.forwardRef((props, ref) => { + return ; +}); + +const slots = { + cell: CellWithTracer, +}; + +export default function GridVisualization() { + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 100, + editable: true, + maxColumns: 15, + }); + + return ( + + theme.transitions.create(['background-color', 'outline'], { + duration: theme.transitions.duration.standard, + }), + }, + '&&& .updating': { + backgroundColor: 'rgb(92 199 68 / 20%)', + outline: '1px solid rgb(92 199 68 / 35%)', + outlineOffset: '-1px', + transition: 'none', + }, + }} + > + + + ); +} diff --git a/docs/data/data-grid/performance/performance.md b/docs/data/data-grid/performance/performance.md index 2853f200495b6..1eaa98dcf03e5 100644 --- a/docs/data/data-grid/performance/performance.md +++ b/docs/data/data-grid/performance/performance.md @@ -41,6 +41,13 @@ function Component({ rows }) { } ``` +## 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. + +{{"demo": "GridVisualization.js", "bg": "inline", "defaultCodeOpen": false}} + ## API - [DataGrid](/x/api/data-grid/data-grid/) 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 c46b6e91e2421..d815dd1ab9ecf 100644 --- a/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts +++ b/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts @@ -10,7 +10,6 @@ const stateNotInitializedWarning = buildWarning([ 'This hook can only be used inside the context of the grid.', ]); -const noop = () => {}; const EMPTY = [] as unknown[]; function isOutputSelector( From 300f8c6b7385c386898d370b7c1099f64ca3bd04 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Mon, 29 May 2023 20:58:56 -0400 Subject: [PATCH 30/72] refactor --- .../grid/x-data-grid/src/components/cell/GridCell.tsx | 3 ++- .../src/hooks/features/rows/useGridParamsApi.ts | 10 ++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx index 9b0a10fc5b016..59db4bdd3a41f 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -29,6 +29,7 @@ import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import { gridFocusCellSelector } from '../../hooks/features/focus/gridFocusStateSelector'; import { gridEditRowsStateSelector } from '../../hooks/features/editing/gridEditingSelectors'; +import { MissingRowIdError } from '../../hooks/features/rows/useGridParamsApi'; import type { DataGridProcessedProps } from '../../models/props/DataGridProps'; // These props are passed down to the cell component, eslint thinks they're not used @@ -135,7 +136,7 @@ const GridCellWrapper = React.forwardRef(( try { return apiRef.current.getCellParams(rowId, field); } catch (e) { - if ((e as Error).message.startsWith('No row with id')) { + if (e instanceof MissingRowIdError) { return EMPTY_CELL_PARAMS; } throw e; 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 bc0aa480ede7a..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,6 +12,8 @@ import { import { useGridApiMethod } from '../../utils/useGridApiMethod'; import { gridFocusCellSelector, gridTabIndexCellSelector } from '../focus/gridFocusStateSelector'; +export class MissingRowIdError extends Error {} + /** * @requires useGridColumns (method) * @requires useGridRows (method) @@ -34,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); @@ -85,7 +87,7 @@ export function useGridParamsApi(apiRef: React.MutableRefObject Date: Mon, 29 May 2023 21:16:53 -0400 Subject: [PATCH 31/72] lint --- .../data-grid/performance/GridVisualization.tsx.preview | 7 +++++++ scripts/x-data-grid-premium.exports.json | 4 +++- scripts/x-data-grid-pro.exports.json | 4 +++- scripts/x-data-grid.exports.json | 4 +++- 4 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 docs/data/data-grid/performance/GridVisualization.tsx.preview diff --git a/docs/data/data-grid/performance/GridVisualization.tsx.preview b/docs/data/data-grid/performance/GridVisualization.tsx.preview new file mode 100644 index 0000000000000..beb57bed0de15 --- /dev/null +++ b/docs/data/data-grid/performance/GridVisualization.tsx.preview @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index bc7cf6f8b02d9..f427649dfc56e 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -130,9 +130,11 @@ { "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": "GridCellWrapper", "kind": "Variable" }, + { "name": "GridCellWrapperProps", "kind": "TypeAlias" }, { "name": "GridCheckCircleIcon", "kind": "Variable" }, { "name": "GridCheckIcon", "kind": "Variable" }, { "name": "GridChildrenFromPathLookup", "kind": "TypeAlias" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index d80c1c769de83..93e50aecc1846 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -108,7 +108,9 @@ { "name": "GridCellModes", "kind": "Enum" }, { "name": "GridCellModesModel", "kind": "TypeAlias" }, { "name": "GridCellParams", "kind": "Interface" }, - { "name": "GridCellProps", "kind": "Interface" }, + { "name": "GridCellProps", "kind": "TypeAlias" }, + { "name": "GridCellWrapper", "kind": "Variable" }, + { "name": "GridCellWrapperProps", "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 d710555731dc3..c6939009c14fe 100644 --- a/scripts/x-data-grid.exports.json +++ b/scripts/x-data-grid.exports.json @@ -101,7 +101,9 @@ { "name": "GridCellModes", "kind": "Enum" }, { "name": "GridCellModesModel", "kind": "TypeAlias" }, { "name": "GridCellParams", "kind": "Interface" }, - { "name": "GridCellProps", "kind": "Interface" }, + { "name": "GridCellProps", "kind": "TypeAlias" }, + { "name": "GridCellWrapper", "kind": "Variable" }, + { "name": "GridCellWrapperProps", "kind": "TypeAlias" }, { "name": "GridCheckCircleIcon", "kind": "Variable" }, { "name": "GridCheckIcon", "kind": "Variable" }, { "name": "GridChildrenFromPathLookup", "kind": "TypeAlias" }, From 04e8f3551b321de28cf149ddec84406b471c5adf Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Wed, 31 May 2023 20:02:52 -0400 Subject: [PATCH 32/72] perf: improve selector --- packages/grid/x-data-grid/src/components/cell/GridCell.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx index 59db4bdd3a41f..39971c90d48ea 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -158,7 +158,10 @@ const GridCellWrapper = React.forwardRef(( const tabIndex = (cellMode === 'view' || !isEditable) && !managesOwnFocus ? cellParams.tabIndex : -1; - const editRowsState = useGridSelector(apiRef, gridEditRowsStateSelector); + const editCellState = useGridSelector(apiRef, (state: any) => { + const editRowsState = gridEditRowsStateSelector(state) + return editRowsState[rowId]?.[column.field] ?? null; + }); const { classes: rootClasses, getCellClassName } = rootProps; @@ -181,8 +184,6 @@ const GridCellWrapper = React.forwardRef(( classNames.push(getCellClassName(cellParams)); } - const editCellState = editRowsState[rowId]?.[column.field] ?? null; - let children: React.ReactNode; if (editCellState == null && column.renderCell) { children = column.renderCell({ ...cellParams, api: apiRef.current }); From ab456dd095e12fa4a160c73f08b6191e93992872 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Thu, 1 Jun 2023 04:47:30 -0400 Subject: [PATCH 33/72] Revert "perf: improve selector" This reverts commit 04e8f3551b321de28cf149ddec84406b471c5adf. --- packages/grid/x-data-grid/src/components/cell/GridCell.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx index 39971c90d48ea..59db4bdd3a41f 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -158,10 +158,7 @@ const GridCellWrapper = React.forwardRef(( const tabIndex = (cellMode === 'view' || !isEditable) && !managesOwnFocus ? cellParams.tabIndex : -1; - const editCellState = useGridSelector(apiRef, (state: any) => { - const editRowsState = gridEditRowsStateSelector(state) - return editRowsState[rowId]?.[column.field] ?? null; - }); + const editRowsState = useGridSelector(apiRef, gridEditRowsStateSelector); const { classes: rootClasses, getCellClassName } = rootProps; @@ -184,6 +181,8 @@ const GridCellWrapper = React.forwardRef(( classNames.push(getCellClassName(cellParams)); } + const editCellState = editRowsState[rowId]?.[column.field] ?? null; + let children: React.ReactNode; if (editCellState == null && column.renderCell) { children = column.renderCell({ ...cellParams, api: apiRef.current }); From b9780773911aed652c5eb9c2d2a16b321d865c8c Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Thu, 1 Jun 2023 06:58:53 -0400 Subject: [PATCH 34/72] lint --- .eslintrc.js | 1 - .../grid/x-data-grid/src/components/GridRow.tsx | 2 +- .../x-data-grid/src/components/cell/GridCell.tsx | 14 ++++++-------- .../x-data-grid/src/hooks/utils/useGridSelector.ts | 1 + 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 67df2a1eb925f..e7bcdb065225d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -70,7 +70,6 @@ module.exports = { */ rules: { ...baseline.rules, - 'prefer-object-spread': 'off', 'import/prefer-default-export': 'off', // TODO move rule into the main repo once it has upgraded '@typescript-eslint/return-await': 'off', diff --git a/packages/grid/x-data-grid/src/components/GridRow.tsx b/packages/grid/x-data-grid/src/components/GridRow.tsx index 397c5a4608c10..695ed74d06da6 100644 --- a/packages/grid/x-data-grid/src/components/GridRow.tsx +++ b/packages/grid/x-data-grid/src/components/GridRow.tsx @@ -292,7 +292,7 @@ const GridRow = React.forwardRef(function GridRow( const sizes = useGridSelector( apiRef, - () => Object.assign({}, apiRef.current.unstable_getRowInternalSizes(rowId)), + () => ({ ...apiRef.current.unstable_getRowInternalSizes(rowId)}), shallowCompare, ); diff --git a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx index 59db4bdd3a41f..a49f586604e1b 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -214,11 +214,10 @@ const GridCellWrapper = React.forwardRef(( const CellComponent = slots.cell; - const cellProps: GridCellProps = Object.assign( - {}, - props, - { - ref, + const cellProps: GridCellProps = { + + ...props, + ref, field, formattedValue, hasFocus, @@ -229,9 +228,8 @@ const GridCellWrapper = React.forwardRef(( children, tabIndex, className: clsx(classNames), - }, - slotProps?.cell, - ); + ...slotProps?.cell, + }; return React.createElement(CellComponent, cellProps); }); 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 d815dd1ab9ecf..caa1ee763d91f 100644 --- a/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts +++ b/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts @@ -55,6 +55,7 @@ export const useGridSelector = ( const didInit = refs.current.selector !== null; const [state, setState] = React.useState( + // We don't use an initialization function to avoid allocations (didInit ? null : applySelector(apiRef, selector)) as T, ); From bfad5f8a204b49321215936f2f08e769c71f15f8 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Thu, 1 Jun 2023 07:43:31 -0400 Subject: [PATCH 35/72] lint --- .../x-data-grid/src/components/GridRow.tsx | 2 +- .../src/components/cell/GridCell.tsx | 21 +++++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/grid/x-data-grid/src/components/GridRow.tsx b/packages/grid/x-data-grid/src/components/GridRow.tsx index 695ed74d06da6..100824545ddb8 100644 --- a/packages/grid/x-data-grid/src/components/GridRow.tsx +++ b/packages/grid/x-data-grid/src/components/GridRow.tsx @@ -292,7 +292,7 @@ const GridRow = React.forwardRef(function GridRow( const sizes = useGridSelector( apiRef, - () => ({ ...apiRef.current.unstable_getRowInternalSizes(rowId)}), + () => ({ ...apiRef.current.unstable_getRowInternalSizes(rowId) }), shallowCompare, ); diff --git a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx index a49f586604e1b..bca7fe81792ca 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -215,19 +215,18 @@ const GridCellWrapper = React.forwardRef(( const CellComponent = slots.cell; const cellProps: GridCellProps = { - ...props, ref, - field, - formattedValue, - hasFocus, - isEditable, - isSelected, - value, - cellMode, - children, - tabIndex, - className: clsx(classNames), + field, + formattedValue, + hasFocus, + isEditable, + isSelected, + value, + cellMode, + children, + tabIndex, + className: clsx(classNames), ...slotProps?.cell, }; From a2052c5dcb7fb6ce0e3246d7cf791c38036446e0 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Thu, 1 Jun 2023 15:35:46 -0400 Subject: [PATCH 36/72] build Co-authored-by: Andrew Cherniavskii Signed-off-by: Rom Grk --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3ce8d93ee5b5c..00ce811591e2a 100644 --- a/package.json +++ b/package.json @@ -22,7 +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": "eslint . --cache --report-unused-disable-directives --ext .js,.ts,.tsx --max-warnings 0 --fix", + "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", From 9d9b0a740317017bfff24f28e1c13e02bae489f9 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Fri, 2 Jun 2023 05:32:38 -0400 Subject: [PATCH 37/72] perf: avoid costly update --- .../features/columnMenu/useGridColumnMenu.ts | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/packages/grid/x-data-grid/src/hooks/features/columnMenu/useGridColumnMenu.ts b/packages/grid/x-data-grid/src/hooks/features/columnMenu/useGridColumnMenu.ts index 5aa5b38b16c28..46774dcf25d0d 100644 --- a/packages/grid/x-data-grid/src/hooks/features/columnMenu/useGridColumnMenu.ts +++ b/packages/grid/x-data-grid/src/hooks/features/columnMenu/useGridColumnMenu.ts @@ -53,6 +53,10 @@ export const useGridColumnMenu = ( const hideColumnMenu = React.useCallback(() => { const columnMenuState = gridColumnMenuSelector(apiRef.current.state); + if (!columnMenuState.open) { + return; + } + if (columnMenuState.field) { const columnLookup = gridColumnLookupSelector(apiRef); const columnVisibilityModel = gridColumnVisibilityModelSelector(apiRef); @@ -79,22 +83,11 @@ export const useGridColumnMenu = ( apiRef.current.setColumnHeaderFocus(fieldToFocus); } - const shouldUpdate = apiRef.current.setState((state) => { - if (!state.columnMenu.open && state.columnMenu.field === undefined) { - return state; - } - - logger.debug('Hiding Column Menu'); - - return { - ...state, - columnMenu: { ...state.columnMenu, open: false, field: undefined }, - }; - }); - - if (shouldUpdate) { - apiRef.current.forceUpdate(); - } + apiRef.current.setState((state) => ({ + ...state, + columnMenu: { ...state.columnMenu, open: false, field: undefined }, + })); + apiRef.current.forceUpdate(); }, [apiRef, logger]); const toggleColumnMenu = React.useCallback( From c3c1a411109003b752d3e175dc207bf9f2e8ce41 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Fri, 2 Jun 2023 05:37:55 -0400 Subject: [PATCH 38/72] Revert "perf: avoid costly update" This reverts commit 9d9b0a740317017bfff24f28e1c13e02bae489f9. --- .../features/columnMenu/useGridColumnMenu.ts | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/packages/grid/x-data-grid/src/hooks/features/columnMenu/useGridColumnMenu.ts b/packages/grid/x-data-grid/src/hooks/features/columnMenu/useGridColumnMenu.ts index 46774dcf25d0d..5aa5b38b16c28 100644 --- a/packages/grid/x-data-grid/src/hooks/features/columnMenu/useGridColumnMenu.ts +++ b/packages/grid/x-data-grid/src/hooks/features/columnMenu/useGridColumnMenu.ts @@ -53,10 +53,6 @@ export const useGridColumnMenu = ( const hideColumnMenu = React.useCallback(() => { const columnMenuState = gridColumnMenuSelector(apiRef.current.state); - if (!columnMenuState.open) { - return; - } - if (columnMenuState.field) { const columnLookup = gridColumnLookupSelector(apiRef); const columnVisibilityModel = gridColumnVisibilityModelSelector(apiRef); @@ -83,11 +79,22 @@ export const useGridColumnMenu = ( apiRef.current.setColumnHeaderFocus(fieldToFocus); } - apiRef.current.setState((state) => ({ - ...state, - columnMenu: { ...state.columnMenu, open: false, field: undefined }, - })); - apiRef.current.forceUpdate(); + const shouldUpdate = apiRef.current.setState((state) => { + if (!state.columnMenu.open && state.columnMenu.field === undefined) { + return state; + } + + logger.debug('Hiding Column Menu'); + + return { + ...state, + columnMenu: { ...state.columnMenu, open: false, field: undefined }, + }; + }); + + if (shouldUpdate) { + apiRef.current.forceUpdate(); + } }, [apiRef, logger]); const toggleColumnMenu = React.useCallback( From d7eb7efd7904739000a806118122ed506fdad69e Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Fri, 2 Jun 2023 06:01:07 -0400 Subject: [PATCH 39/72] refactor --- .../grid/x-data-grid/src/hooks/utils/useGridSelector.ts | 9 +++------ packages/grid/x-data-grid/src/hooks/utils/useOnMount.ts | 9 +++++++++ 2 files changed, 12 insertions(+), 6 deletions(-) create mode 100644 packages/grid/x-data-grid/src/hooks/utils/useOnMount.ts 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 caa1ee763d91f..3de910899e79c 100644 --- a/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts +++ b/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts @@ -2,6 +2,7 @@ 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 { fastShallowCompare } from '../../utils/fastShallowCompare'; @@ -10,8 +11,6 @@ const stateNotInitializedWarning = buildWarning([ 'This hook can only be used inside the context of the grid.', ]); -const EMPTY = [] as unknown[]; - function isOutputSelector( selector: any, ): selector is OutputSelector { @@ -63,8 +62,7 @@ export const useGridSelector = ( refs.current.equals = equals; refs.current.selector = selector; - /* eslint-disable react-hooks/exhaustive-deps */ - React.useEffect(() => { + useOnMount(() => { return apiRef.current.store.subscribe(() => { const newState = applySelector(apiRef, refs.current.selector); if (!refs.current.equals(refs.current.state, newState)) { @@ -72,8 +70,7 @@ export const useGridSelector = ( setState(newState); } }); - }, EMPTY); - /* eslint-enable react-hooks/exhaustive-deps */ + }); return state; }; 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..4d751b1e42825 --- /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 */ +} From ebc5156dec50c4afbbc9b68a45ef0bf6d5f59ec7 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Fri, 2 Jun 2023 06:02:40 -0400 Subject: [PATCH 40/72] refactor# Changes to be committed: --- packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts | 4 ++-- .../{fastShallowCompare.ts => fastObjectShallowCompare.ts} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename packages/grid/x-data-grid/src/utils/{fastShallowCompare.ts => fastObjectShallowCompare.ts} (85%) 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 3de910899e79c..97c07377951fa 100644 --- a/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts +++ b/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts @@ -4,7 +4,7 @@ import { OutputSelector } from '../../utils/createSelector'; import { useLazyRef } from './useLazyRef'; import { useOnMount } from './useOnMount'; import { buildWarning } from '../../utils/warning'; -import { fastShallowCompare } from '../../utils/fastShallowCompare'; +import { fastObjectShallowCompare } from '../../utils/fastObjectShallowCompare'; const stateNotInitializedWarning = buildWarning([ 'MUI: `useGridSelector` has been called before the initialization of the state.', @@ -28,7 +28,7 @@ export function applySelector( } export const defaultCompare = Object.is; -export const shallowCompare = fastShallowCompare; +export const shallowCompare = fastObjectShallowCompare; const createRefs = () => ({ state: null, equals: null, selector: null } as any); diff --git a/packages/grid/x-data-grid/src/utils/fastShallowCompare.ts b/packages/grid/x-data-grid/src/utils/fastObjectShallowCompare.ts similarity index 85% rename from packages/grid/x-data-grid/src/utils/fastShallowCompare.ts rename to packages/grid/x-data-grid/src/utils/fastObjectShallowCompare.ts index 5eb3f84df2b32..fc78b772d8b6d 100644 --- a/packages/grid/x-data-grid/src/utils/fastShallowCompare.ts +++ b/packages/grid/x-data-grid/src/utils/fastObjectShallowCompare.ts @@ -1,6 +1,6 @@ const is = Object.is; -export function fastShallowCompare | null>(a: T, b: T) { +export function fastObjectShallowCompare | null>(a: T, b: T) { if (a === b) { return true; } From 21d225f794e9019c781f609e85c73938aa53b45b Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Mon, 15 May 2023 12:11:35 -0400 Subject: [PATCH 41/72] perf: avoid allocations & memo --- .../virtualization/GridVirtualScroller.tsx | 5 +-- .../GridVirtualScrollerContent.tsx | 21 ++++------ .../hooks/core/useGridStateInitialization.ts | 7 +--- .../columnHeaders/useGridColumnHeaders.tsx | 11 ++++- .../virtualization/useGridVirtualScroller.tsx | 40 ++++++++++--------- 5 files changed, 42 insertions(+), 42 deletions(-) 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..2347d052a0be6 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/hooks/core/useGridStateInitialization.ts b/packages/grid/x-data-grid/src/hooks/core/useGridStateInitialization.ts index e934b9363e9b5..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']>( diff --git a/packages/grid/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx b/packages/grid/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx index c8c1314294a40..4429696b0afc2 100644 --- a/packages/grid/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx +++ b/packages/grid/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx @@ -11,7 +11,7 @@ import { GridEventListener } from '../../../models/events'; import { GridColumnHeaderItem } from '../../../components/columnHeaders/GridColumnHeaderItem'; import { getFirstColumnIndexToRender, getTotalHeaderHeight } from '../columns/gridColumnsUtils'; import { useGridVisibleRows } from '../../utils/useGridVisibleRows'; -import { getRenderableIndexes } from '../virtualization/useGridVirtualScroller'; +import { areRenderContextsEqual, getRenderableIndexes } from '../virtualization/useGridVirtualScroller'; import { GridColumnGroupHeader } from '../../../components/columnHeaders/GridColumnGroupHeader'; import { GridColumnGroup } from '../../../models/gridColumnGrouping'; import { GridStateColDef } from '../../../models/colDef/gridColDef'; @@ -101,13 +101,20 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { 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 = (nextRenderContext: GridRenderContext | null) => { + if (renderContext && nextRenderContext && areRenderContextsEqual(renderContext, nextRenderContext)) { + return; + } + setRenderContextRaw(nextRenderContext); + } + React.useEffect(() => { apiRef.current.columnHeadersContainerElementRef!.current!.scrollLeft = 0; }, [apiRef]); 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 c1132d36fafb1..9530ef31b8954 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,7 @@ export const getRenderableIndexes = ({ ]; }; -const areRenderContextsEqual = (context1: GridRenderContext, context2: GridRenderContext) => { +export const areRenderContextsEqual = (context1: GridRenderContext, context2: GridRenderContext) => { if (context1 === context2) { return true; } @@ -374,7 +375,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; @@ -437,15 +438,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: { @@ -643,13 +644,16 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { 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 rootStyle = {} as React.CSSProperties; + if (!needsHorizontalScrollbar) { + rootStyle.overflowX = 'hidden'; + } + if (rootProps.autoHeight) { + rootStyle.overflowY = 'hidden'; + } + return rootStyle; + }, [needsHorizontalScrollbar, rootProps.autoHeight]); const getRenderContext = React.useCallback((): GridRenderContext => { return prevRenderContext.current!; @@ -661,15 +665,15 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { renderContext, updateRenderZonePosition, getRows, - getRootProps: ({ style = {}, ...other } = {}) => ({ + getRootProps: (props: { style?: object } = {}) => ({ ref: handleRef, onScroll: handleScroll, onWheel: handleWheel, onTouchMove: handleTouchMove, - style: { ...style, ...rootStyle }, - ...other, + ...props, + style: props.style ? { ...props.style, ...rootStyle } : rootStyle, }), - getContentProps: ({ style = {} } = {}) => ({ style: { ...style, ...contentSize } }), + getContentProps: ({ style }: { style?: object } = {}) => ({ style: style ? { ...style, ...contentSize } : contentSize }), getRenderZoneProps: () => ({ ref: renderZoneRef }), }; }; From 0cb952a917a17459f9196b323507ae1d5769a1ba Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Fri, 2 Jun 2023 07:52:20 -0400 Subject: [PATCH 42/72] perf: *fast* object compare --- .../src/utils/fastObjectShallowCompare.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/grid/x-data-grid/src/utils/fastObjectShallowCompare.ts b/packages/grid/x-data-grid/src/utils/fastObjectShallowCompare.ts index fc78b772d8b6d..bb595b40f9fb5 100644 --- a/packages/grid/x-data-grid/src/utils/fastObjectShallowCompare.ts +++ b/packages/grid/x-data-grid/src/utils/fastObjectShallowCompare.ts @@ -8,9 +8,14 @@ export function fastObjectShallowCompare | null>(a 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 (!(key in b)) { return false; } @@ -19,13 +24,11 @@ export function fastObjectShallowCompare | null>(a } } - for (const key in b) { - if (!(key in a)) { - return false; - } + for (const _ in b) { + bLength += 1; } /* eslint-enable no-restricted-syntax */ /* eslint-enable guard-for-in */ - return true; + return aLength === bLength; } From 9332b36add185583f21529c279fc8b182fc4e8ea Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Fri, 2 Jun 2023 08:41:17 -0400 Subject: [PATCH 43/72] perf: fast memo --- .../src/components/GridColumnHeaders.tsx | 3 ++- .../grid/x-data-grid/src/components/GridRow.tsx | 3 ++- .../x-data-grid/src/components/cell/GridCell.tsx | 3 ++- .../virtualization/GridVirtualScrollerContent.tsx | 2 +- .../features/columnHeaders/useGridColumnHeaders.tsx | 13 ++++++++++--- .../virtualization/useGridVirtualScroller.tsx | 9 +++++++-- .../grid/x-data-grid/src/hooks/utils/useOnMount.ts | 2 +- packages/grid/x-data-grid/src/utils/fastMemo.ts | 6 ++++++ 8 files changed, 31 insertions(+), 10 deletions(-) create mode 100644 packages/grid/x-data-grid/src/utils/fastMemo.ts diff --git a/packages/grid/x-data-grid/src/components/GridColumnHeaders.tsx b/packages/grid/x-data-grid/src/components/GridColumnHeaders.tsx index 434d337c10b22..05e7f53440e6f 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, @@ -116,6 +117,6 @@ GridColumnHeaders.propTypes = { visibleColumns: PropTypes.arrayOf(PropTypes.object).isRequired, } as any; -const MemoizedGridColumnHeaders = React.memo(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 100824545ddb8..7c385004c3352 100644 --- a/packages/grid/x-data-grid/src/components/GridRow.tsx +++ b/packages/grid/x-data-grid/src/components/GridRow.tsx @@ -5,6 +5,7 @@ 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 } from '../models/gridRows'; import { GridEditModes, GridRowModes, GridCellModes } from '../models/gridEditRowModel'; @@ -465,6 +466,6 @@ GridRow.propTypes = { visibleColumns: PropTypes.arrayOf(PropTypes.object), } as any; -const MemoizedGridRow = React.memo(GridRow); +const MemoizedGridRow = fastMemo(GridRow); export { MemoizedGridRow as GridRow }; diff --git a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx index bca7fe81792ca..48c25f37c4f38 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -8,6 +8,7 @@ import { unstable_ownerDocument as ownerDocument, unstable_capitalize as capitalize, } from '@mui/utils'; +import { fastMemo } from '../../utils/fastMemo'; import { doesSupportPreventScroll } from '../../utils/doesSupportPreventScroll'; import { getDataGridUtilityClass, gridClasses } from '../../constants/gridClasses'; import { @@ -427,7 +428,7 @@ const GridCell = React.forwardRef((props, ref) => ); }); -const MemoizedCellWrapper = React.memo(GridCellWrapper); +const MemoizedCellWrapper = fastMemo(GridCellWrapper); GridCellWrapper.propTypes = { // ----------------------------- Warning -------------------------------- 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 2347d052a0be6..95e05d1a48a55 100644 --- a/packages/grid/x-data-grid/src/components/virtualization/GridVirtualScrollerContent.tsx +++ b/packages/grid/x-data-grid/src/components/virtualization/GridVirtualScrollerContent.tsx @@ -8,7 +8,7 @@ import { DataGridProcessedProps } from '../../models/props/DataGridProps'; type OwnerState = DataGridProcessedProps; -const useUtilityClasses = (props: DataGridProcessedProps, overflowedContent: boolean ) => { +const useUtilityClasses = (props: DataGridProcessedProps, overflowedContent: boolean) => { const { classes } = props; const slots = { diff --git a/packages/grid/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx b/packages/grid/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx index 4429696b0afc2..450f7530915e5 100644 --- a/packages/grid/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx +++ b/packages/grid/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx @@ -11,7 +11,10 @@ import { GridEventListener } from '../../../models/events'; import { GridColumnHeaderItem } from '../../../components/columnHeaders/GridColumnHeaderItem'; import { getFirstColumnIndexToRender, getTotalHeaderHeight } from '../columns/gridColumnsUtils'; import { useGridVisibleRows } from '../../utils/useGridVisibleRows'; -import { areRenderContextsEqual, getRenderableIndexes } from '../virtualization/useGridVirtualScroller'; +import { + areRenderContextsEqual, + getRenderableIndexes, +} from '../virtualization/useGridVirtualScroller'; import { GridColumnGroupHeader } from '../../../components/columnHeaders/GridColumnGroupHeader'; import { GridColumnGroup } from '../../../models/gridColumnGrouping'; import { GridStateColDef } from '../../../models/colDef/gridColDef'; @@ -109,11 +112,15 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { const headerHeight = Math.floor(rootProps.columnHeaderHeight * densityFactor); const setRenderContext = (nextRenderContext: GridRenderContext | null) => { - if (renderContext && nextRenderContext && areRenderContextsEqual(renderContext, nextRenderContext)) { + if ( + renderContext && + nextRenderContext && + areRenderContextsEqual(renderContext, nextRenderContext) + ) { return; } setRenderContextRaw(nextRenderContext); - } + }; React.useEffect(() => { apiRef.current.columnHeadersContainerElementRef!.current!.scrollLeft = 0; 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 9530ef31b8954..076a38e4f7a90 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 @@ -81,7 +81,10 @@ export const getRenderableIndexes = ({ ]; }; -export const areRenderContextsEqual = (context1: GridRenderContext, context2: GridRenderContext) => { +export const areRenderContextsEqual = ( + context1: GridRenderContext, + context2: GridRenderContext, +) => { if (context1 === context2) { return true; } @@ -673,7 +676,9 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { ...props, style: props.style ? { ...props.style, ...rootStyle } : rootStyle, }), - getContentProps: ({ style }: { style?: object } = {}) => ({ style: style ? { ...style, ...contentSize } : contentSize }), + getContentProps: ({ style }: { style?: object } = {}) => ({ + style: style ? { ...style, ...contentSize } : contentSize, + }), getRenderZoneProps: () => ({ ref: renderZoneRef }), }; }; diff --git a/packages/grid/x-data-grid/src/hooks/utils/useOnMount.ts b/packages/grid/x-data-grid/src/hooks/utils/useOnMount.ts index 4d751b1e42825..fe624200f7b2d 100644 --- a/packages/grid/x-data-grid/src/hooks/utils/useOnMount.ts +++ b/packages/grid/x-data-grid/src/hooks/utils/useOnMount.ts @@ -1,4 +1,4 @@ -import * as React from 'react' +import * as React from 'react'; const EMPTY = [] as unknown[]; 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..ab3e8f2416875 --- /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: React.FunctionComponent) { + return React.memo(component, fastObjectShallowCompare); +} From 085a2320700dc510ff0617ee023c09c9c2fc7089 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Mon, 15 May 2023 13:21:43 -0400 Subject: [PATCH 44/72] lint --- .../columnHeaders/useGridColumnHeaders.tsx | 12 ++++-------- .../virtualization/useGridVirtualScroller.tsx | 14 +++++++------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/packages/grid/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx b/packages/grid/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx index 450f7530915e5..e76c9d0f99f28 100644 --- a/packages/grid/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx +++ b/packages/grid/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx @@ -111,16 +111,12 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { const totalHeaderHeight = getTotalHeaderHeight(apiRef, rootProps.columnHeaderHeight); const headerHeight = Math.floor(rootProps.columnHeaderHeight * densityFactor); - const setRenderContext = (nextRenderContext: GridRenderContext | null) => { - if ( - renderContext && - nextRenderContext && - areRenderContextsEqual(renderContext, nextRenderContext) - ) { + const setRenderContext = React.useCallback((nextRenderContext: GridRenderContext | null) => { + if (renderContext && nextRenderContext && areRenderContextsEqual(renderContext, nextRenderContext)) { return; } setRenderContextRaw(nextRenderContext); - }; + }, []) React.useEffect(() => { apiRef.current.columnHeadersContainerElementRef!.current!.scrollLeft = 0; @@ -224,7 +220,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/virtualization/useGridVirtualScroller.tsx b/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx index 076a38e4f7a90..1f35490b030ae 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 @@ -648,14 +648,14 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { } const rootStyle = React.useMemo(() => { - const rootStyle = {} as React.CSSProperties; + const style = {} as React.CSSProperties; if (!needsHorizontalScrollbar) { - rootStyle.overflowX = 'hidden'; + style.overflowX = 'hidden'; } if (rootProps.autoHeight) { - rootStyle.overflowY = 'hidden'; + style.overflowY = 'hidden'; } - return rootStyle; + return style; }, [needsHorizontalScrollbar, rootProps.autoHeight]); const getRenderContext = React.useCallback((): GridRenderContext => { @@ -668,13 +668,13 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { renderContext, updateRenderZonePosition, getRows, - getRootProps: (props: { style?: object } = {}) => ({ + getRootProps: (inputProps: { style?: object } = {}) => ({ ref: handleRef, onScroll: handleScroll, onWheel: handleWheel, onTouchMove: handleTouchMove, - ...props, - style: props.style ? { ...props.style, ...rootStyle } : rootStyle, + ...inputProps, + style: inputProps.style ? { ...inputProps.style, ...rootStyle } : rootStyle, }), getContentProps: ({ style }: { style?: object } = {}) => ({ style: style ? { ...style, ...contentSize } : contentSize, From afe972765b8c6475b311af0898b98ace21b1ad08 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Mon, 15 May 2023 13:23:59 -0400 Subject: [PATCH 45/72] lint --- .../src/hooks/features/columnHeaders/useGridColumnHeaders.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grid/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx b/packages/grid/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx index e76c9d0f99f28..0e367807e02e4 100644 --- a/packages/grid/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx +++ b/packages/grid/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx @@ -116,7 +116,7 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { return; } setRenderContextRaw(nextRenderContext); - }, []) + }, [renderContext]) React.useEffect(() => { apiRef.current.columnHeadersContainerElementRef!.current!.scrollLeft = 0; From 5cd1e04e84423fd89af74e38589fbf966558242b Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Fri, 2 Jun 2023 09:33:41 -0400 Subject: [PATCH 46/72] perf: more fast --- .../grid/x-data-grid/src/utils/fastObjectShallowCompare.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grid/x-data-grid/src/utils/fastObjectShallowCompare.ts b/packages/grid/x-data-grid/src/utils/fastObjectShallowCompare.ts index bb595b40f9fb5..3a47e7fe50fae 100644 --- a/packages/grid/x-data-grid/src/utils/fastObjectShallowCompare.ts +++ b/packages/grid/x-data-grid/src/utils/fastObjectShallowCompare.ts @@ -16,10 +16,10 @@ export function fastObjectShallowCompare | null>(a for (const key in a) { aLength += 1; - if (!(key in b)) { + if (!is(a[key], b[key])) { return false; } - if (!is(a[key], b[key])) { + if (!(key in b)) { return false; } } From 27fef4350bb6c82a480b18ef68d6cce9c0167157 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Mon, 5 Jun 2023 22:50:24 -0400 Subject: [PATCH 47/72] lint --- packages/grid/x-data-grid/src/components/cell/GridCell.tsx | 2 +- packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx index 48c25f37c4f38..81b7cd2d2c52d 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -35,7 +35,7 @@ import type { DataGridProcessedProps } from '../../models/props/DataGridProps'; // These props are passed down to the cell component, eslint thinks they're not used /* eslint-disable react/no-unused-prop-types */ -export type GridCellWrapperProps = { +type GridCellWrapperProps = { align: GridAlignment; className?: string; colIndex: number; 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 97c07377951fa..c584d9c2d0a23 100644 --- a/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts +++ b/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts @@ -17,7 +17,7 @@ function isOutputSelector( return selector.acceptsApiRef; } -export function applySelector( +function applySelector( apiRef: React.MutableRefObject, selector: ((state: Api['state']) => T) | OutputSelector, ) { @@ -27,7 +27,7 @@ export function applySelector( return selector(apiRef.current.state); } -export const defaultCompare = Object.is; +const defaultCompare = Object.is; export const shallowCompare = fastObjectShallowCompare; const createRefs = () => ({ state: null, equals: null, selector: null } as any); From 3b46c8c9c78eed3a0b41c3c32300d6bbdbc9eabf Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Mon, 5 Jun 2023 22:52:01 -0400 Subject: [PATCH 48/72] lint --- scripts/x-data-grid-premium.exports.json | 3 --- scripts/x-data-grid-pro.exports.json | 3 --- scripts/x-data-grid.exports.json | 3 --- 3 files changed, 9 deletions(-) diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index f427649dfc56e..a91b490535f88 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -1,5 +1,4 @@ [ - { "name": "applySelector", "kind": "Function" }, { "name": "arSD", "kind": "Variable" }, { "name": "BaseButtonPropsOverrides", "kind": "Interface" }, { "name": "BaseCheckboxPropsOverrides", "kind": "Interface" }, @@ -33,7 +32,6 @@ { "name": "DataGridPro", "kind": "Function" }, { "name": "deDE", "kind": "Variable" }, { "name": "DEFAULT_GRID_COL_TYPE_KEY", "kind": "Variable" }, - { "name": "defaultCompare", "kind": "Variable" }, { "name": "ElementSize", "kind": "Interface" }, { "name": "elGR", "kind": "Variable" }, { "name": "enUS", "kind": "Variable" }, @@ -134,7 +132,6 @@ { "name": "GridCellSelectionApi", "kind": "Interface" }, { "name": "GridCellSelectionModel", "kind": "TypeAlias" }, { "name": "GridCellWrapper", "kind": "Variable" }, - { "name": "GridCellWrapperProps", "kind": "TypeAlias" }, { "name": "GridCheckCircleIcon", "kind": "Variable" }, { "name": "GridCheckIcon", "kind": "Variable" }, { "name": "GridChildrenFromPathLookup", "kind": "TypeAlias" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index 93e50aecc1846..8d5a789d29fb1 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -1,5 +1,4 @@ [ - { "name": "applySelector", "kind": "Function" }, { "name": "arSD", "kind": "Variable" }, { "name": "BaseButtonPropsOverrides", "kind": "Interface" }, { "name": "BaseCheckboxPropsOverrides", "kind": "Interface" }, @@ -32,7 +31,6 @@ { "name": "DataGridProProps", "kind": "Interface" }, { "name": "deDE", "kind": "Variable" }, { "name": "DEFAULT_GRID_COL_TYPE_KEY", "kind": "Variable" }, - { "name": "defaultCompare", "kind": "Variable" }, { "name": "ElementSize", "kind": "Interface" }, { "name": "elGR", "kind": "Variable" }, { "name": "enUS", "kind": "Variable" }, @@ -110,7 +108,6 @@ { "name": "GridCellParams", "kind": "Interface" }, { "name": "GridCellProps", "kind": "TypeAlias" }, { "name": "GridCellWrapper", "kind": "Variable" }, - { "name": "GridCellWrapperProps", "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 c6939009c14fe..134b7876c2368 100644 --- a/scripts/x-data-grid.exports.json +++ b/scripts/x-data-grid.exports.json @@ -1,5 +1,4 @@ [ - { "name": "applySelector", "kind": "Function" }, { "name": "arSD", "kind": "Variable" }, { "name": "BaseButtonPropsOverrides", "kind": "Interface" }, { "name": "BaseCheckboxPropsOverrides", "kind": "Interface" }, @@ -30,7 +29,6 @@ { "name": "DataGridProps", "kind": "TypeAlias" }, { "name": "deDE", "kind": "Variable" }, { "name": "DEFAULT_GRID_COL_TYPE_KEY", "kind": "Variable" }, - { "name": "defaultCompare", "kind": "Variable" }, { "name": "ElementSize", "kind": "Interface" }, { "name": "elGR", "kind": "Variable" }, { "name": "enUS", "kind": "Variable" }, @@ -103,7 +101,6 @@ { "name": "GridCellParams", "kind": "Interface" }, { "name": "GridCellProps", "kind": "TypeAlias" }, { "name": "GridCellWrapper", "kind": "Variable" }, - { "name": "GridCellWrapperProps", "kind": "TypeAlias" }, { "name": "GridCheckCircleIcon", "kind": "Variable" }, { "name": "GridCheckIcon", "kind": "Variable" }, { "name": "GridChildrenFromPathLookup", "kind": "TypeAlias" }, From fbdeb31a62945b991fb372a48a9f55e224b5857c Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Tue, 6 Jun 2023 02:00:35 -0400 Subject: [PATCH 49/72] perf: small improvements --- .../x-data-grid/src/components/GridRow.tsx | 3 + .../src/components/cell/GridCell.tsx | 56 ++++++++++--------- .../grid/x-data-grid/src/utils/fastMemo.ts | 4 +- 3 files changed, 34 insertions(+), 29 deletions(-) diff --git a/packages/grid/x-data-grid/src/components/GridRow.tsx b/packages/grid/x-data-grid/src/components/GridRow.tsx index 7c385004c3352..389c57df902b8 100644 --- a/packages/grid/x-data-grid/src/components/GridRow.tsx +++ b/packages/grid/x-data-grid/src/components/GridRow.tsx @@ -275,6 +275,8 @@ const GridRow = React.forwardRef(function GridRow( treeDepth > 1 && Object.keys(editRowsState).length > 0); + const editCellState = editRowsState[rowId]?.[column.field] ?? null; + return ( (function GridRow( colIndex={cellProps.indexRelativeToAllColumns} colSpan={cellProps.colSpan} disableDragEvents={disableDragEvents} + editCellState={editCellState} /> ); }; diff --git a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx index 81b7cd2d2c52d..eb15273fce157 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -8,6 +8,7 @@ import { unstable_ownerDocument as ownerDocument, unstable_capitalize as capitalize, } from '@mui/utils'; +import type { GridApiCommunity } from '../../internals'; import { fastMemo } from '../../utils/fastMemo'; import { doesSupportPreventScroll } from '../../utils/doesSupportPreventScroll'; import { getDataGridUtilityClass, gridClasses } from '../../constants/gridClasses'; @@ -25,11 +26,11 @@ import { } from '../../models/params/gridCellParams'; import { GridColDef, GridAlignment } from '../../models/colDef/gridColDef'; import { GridTreeNodeWithRender } from '../../models/gridRows'; +import { GridEditCellProps } from '../../models/gridEditRowModel'; import { useGridSelector, shallowCompare } from '../../hooks/utils/useGridSelector'; import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import { gridFocusCellSelector } from '../../hooks/features/focus/gridFocusStateSelector'; -import { gridEditRowsStateSelector } from '../../hooks/features/editing/gridEditingSelectors'; import { MissingRowIdError } from '../../hooks/features/rows/useGridParamsApi'; import type { DataGridProcessedProps } from '../../models/props/DataGridProps'; @@ -46,6 +47,7 @@ type GridCellWrapperProps = { width: number; colSpan?: number; disableDragEvents?: boolean; + editCellState: GridEditCellProps | null, onClick?: React.MouseEventHandler; onDoubleClick?: React.MouseEventHandler; onMouseDown?: React.MouseEventHandler; @@ -69,7 +71,8 @@ export type GridCellProps = GridCellWrapperProps & { tabIndex: 0 | -1; }; -const EMPTY_CELL_PARAMS: GridCellParams = { +type CellParamsWithAPI = GridCellParams & { api: GridApiCommunity }; +const EMPTY_CELL_PARAMS: CellParamsWithAPI = { id: -1, field: '__unset__', row: {}, @@ -91,6 +94,7 @@ const EMPTY_CELL_PARAMS: GridCellParams = value: null, formattedValue: '__unset__', isEditable: false, + api: {} as any, }; type OwnerState = Pick & { @@ -121,21 +125,24 @@ let warnedOnce = false; // TODO(v7): Remove the wrapper, merge with the cell component const GridCellWrapper = React.forwardRef((props, ref) => { - const { column, rowId } = props; + const { column, rowId, editCellState } = props; const apiRef = useGridApiContext(); const rootProps = useGridRootProps(); const field = column.field; - const cellParams = useGridSelector( + 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 { - return apiRef.current.getCellParams(rowId, field); + 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; @@ -153,41 +160,40 @@ const GridCellWrapper = React.forwardRef(( }), ); - const { cellMode, hasFocus, isEditable, value, formattedValue } = cellParams; + 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 ? cellParams.tabIndex : -1; - - const editRowsState = useGridSelector(apiRef, gridEditRowsStateSelector); + (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( - clsx( - typeof column.cellClassName === 'function' - ? column.cellClassName(cellParams) - : column.cellClassName, - ), + typeof column.cellClassName === 'function' + ? column.cellClassName(cellParamsWithAPI) + : column.cellClassName, ); } if (getCellClassName) { - classNames.push(getCellClassName(cellParams)); + classNames.push(getCellClassName(cellParamsWithAPI)); } - const editCellState = editRowsState[rowId]?.[column.field] ?? null; - let children: React.ReactNode; if (editCellState == null && column.renderCell) { - children = column.renderCell({ ...cellParams, api: apiRef.current }); - classNames.push(clsx(gridClasses['cell--withRenderer'], rootClasses?.['cell--withRenderer'])); + children = column.renderCell(cellParamsWithAPI); + classNames.push(gridClasses['cell--withRenderer']); + classNames.push(rootClasses?.['cell--withRenderer']); } if (editCellState != null && column.renderEditCell) { @@ -197,18 +203,14 @@ const GridCellWrapper = React.forwardRef(( const { changeReason, unstable_updateValueOnRender, ...editCellStateRest } = editCellState; const params: GridRenderEditCellParams = { - ...cellParams, + ...cellParamsWithAPI, row: updatedRow, ...editCellStateRest, - api: apiRef.current, }; children = column.renderEditCell(params); - classNames.push(clsx(gridClasses['cell--editing'], rootClasses?.['cell--editing'])); - } - - if (cellParams === EMPTY_CELL_PARAMS) { - return null; + classNames.push(gridClasses['cell--editing']); + classNames.push(rootClasses?.['cell--editing']); } const { slots, slotProps } = rootProps; diff --git a/packages/grid/x-data-grid/src/utils/fastMemo.ts b/packages/grid/x-data-grid/src/utils/fastMemo.ts index ab3e8f2416875..86780baa5e551 100644 --- a/packages/grid/x-data-grid/src/utils/fastMemo.ts +++ b/packages/grid/x-data-grid/src/utils/fastMemo.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { fastObjectShallowCompare } from './fastObjectShallowCompare'; -export function fastMemo(component: React.FunctionComponent) { - return React.memo(component, fastObjectShallowCompare); +export function fastMemo(component: T): T { + return React.memo(component as any, fastObjectShallowCompare) as unknown as T; } From 52deddfe376c800f524c3d7d0a5fce75ca983f34 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Tue, 6 Jun 2023 03:22:49 -0400 Subject: [PATCH 50/72] perf: cellv7 single component --- .../x-data-grid/src/components/GridRow.tsx | 5 +- .../src/components/cell/GridCell.tsx | 276 +++++++++++++++++- .../constants/defaultGridSlotsComponents.ts | 4 +- 3 files changed, 280 insertions(+), 5 deletions(-) diff --git a/packages/grid/x-data-grid/src/components/GridRow.tsx b/packages/grid/x-data-grid/src/components/GridRow.tsx index 389c57df902b8..8c2d9ab74fb14 100644 --- a/packages/grid/x-data-grid/src/components/GridRow.tsx +++ b/packages/grid/x-data-grid/src/components/GridRow.tsx @@ -26,7 +26,7 @@ import { gridSortModelSelector } from '../hooks/features/sorting/gridSortingSele import { gridRowMaximumTreeDepthSelector } from '../hooks/features/rows/gridRowsSelector'; import { gridColumnGroupsHeaderMaxDepthSelector } from '../hooks/features/columnGrouping/gridColumnGroupsSelector'; import { randomNumberBetween } from '../utils/utils'; -import { GridCellWrapper } from './cell/GridCell'; +import { GridCellWrapper, GridCellV7 } from './cell/GridCell'; import type { GridCellProps } from './cell/GridCell'; import { gridEditRowsStateSelector } from '../hooks/features/editing/gridEditingSelectors'; @@ -258,6 +258,7 @@ const GridRow = React.forwardRef(function GridRow( ); const { slots, disableColumnReorder } = rootProps; + const CellComponent = slots.cell === GridCellV7 ? GridCellV7 : GridCellWrapper; const rowReordering = (rootProps as any).rowReordering as boolean; @@ -278,7 +279,7 @@ const GridRow = React.forwardRef(function GridRow( const editCellState = editRowsState[rowId]?.[column.field] ?? null; return ( - { let warnedOnce = false; -// TODO(v7): Remove the wrapper, merge with the cell component +// 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 const GridCellWrapper = React.forwardRef((props, ref) => { const { column, rowId, editCellState } = props; @@ -490,3 +492,275 @@ GridCell.propTypes = { } 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; + } + }, + shallowCompare, + ); + + 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)); + } + + 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]); + + 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} +
+ ); +}); + +const MemoizedGridCellV7 = fastMemo(GridCellV7); + +export { MemoizedGridCellV7 as GridCellV7 } diff --git a/packages/grid/x-data-grid/src/constants/defaultGridSlotsComponents.ts b/packages/grid/x-data-grid/src/constants/defaultGridSlotsComponents.ts index 3a867eb22aba2..407747e057c96 100644 --- a/packages/grid/x-data-grid/src/constants/defaultGridSlotsComponents.ts +++ b/packages/grid/x-data-grid/src/constants/defaultGridSlotsComponents.ts @@ -1,6 +1,6 @@ import { GridSlotsComponent } from '../models'; import { - GridCell, + GridCellV7, GridSkeletonCell, GridColumnsPanel, GridFilterPanel, @@ -20,7 +20,7 @@ import materialSlots from '../material'; export const DATA_GRID_DEFAULT_SLOTS_COMPONENTS: GridSlotsComponent = { ...materialSlots, - Cell: GridCell, + Cell: GridCellV7, SkeletonCell: GridSkeletonCell, ColumnHeaderFilterIconButton: GridColumnHeaderFilterIconButton, ColumnMenu: GridColumnMenu, From e58fdd9668ac4b37c015bfc944b98054810e7d14 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Tue, 6 Jun 2023 04:16:06 -0400 Subject: [PATCH 51/72] lint --- .../src/components/cell/GridCell.tsx | 32 +++++++++++-------- .../columnHeaders/useGridColumnHeaders.tsx | 19 +++++++---- .../src/utils/fastObjectShallowCompare.ts | 1 + 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx index eea5a37af797a..6338899954630 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -34,8 +34,6 @@ import { gridFocusCellSelector } from '../../hooks/features/focus/gridFocusState import { MissingRowIdError } from '../../hooks/features/rows/useGridParamsApi'; import type { DataGridProcessedProps } from '../../models/props/DataGridProps'; -// These props are passed down to the cell component, eslint thinks they're not used -/* eslint-disable react/no-unused-prop-types */ type GridCellWrapperProps = { align: GridAlignment; className?: string; @@ -47,7 +45,7 @@ type GridCellWrapperProps = { width: number; colSpan?: number; disableDragEvents?: boolean; - editCellState: GridEditCellProps | null, + editCellState: GridEditCellProps | null; onClick?: React.MouseEventHandler; onDoubleClick?: React.MouseEventHandler; onMouseDown?: React.MouseEventHandler; @@ -71,7 +69,9 @@ export type GridCellProps = GridCellWrapperProps & { tabIndex: 0 | -1; }; -type CellParamsWithAPI = GridCellParams & { api: GridApiCommunity }; +type CellParamsWithAPI = GridCellParams & { + api: GridApiCommunity; +}; const EMPTY_CELL_PARAMS: CellParamsWithAPI = { id: -1, field: '__unset__', @@ -141,10 +141,13 @@ const GridCellWrapper = React.forwardRef(( // 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 cellParams = apiRef.current.getCellParams( + rowId, + field, + ); const result = cellParams as CellParamsWithAPI; result.api = apiRef.current; - return result + return result; } catch (e) { if (e instanceof MissingRowIdError) { return EMPTY_CELL_PARAMS; @@ -533,10 +536,13 @@ const GridCellV7 = React.forwardRef((props // 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 cellParams = apiRef.current.getCellParams( + rowId, + field, + ); const result = cellParams as CellParamsWithAPI; result.api = apiRef.current; - return result + return result; } catch (e) { if (e instanceof MissingRowIdError) { return EMPTY_CELL_PARAMS; @@ -554,10 +560,6 @@ const GridCellV7 = React.forwardRef((props }), ); - if (cellParamsWithAPI === EMPTY_CELL_PARAMS) { - return null; - } - const { cellMode, hasFocus, isEditable, value, formattedValue } = cellParamsWithAPI; const managesOwnFocus = column.type === 'actions'; @@ -660,6 +662,10 @@ const GridCellV7 = React.forwardRef((props } }, [hasFocus, cellMode, apiRef]); + if (cellParamsWithAPI === EMPTY_CELL_PARAMS) { + return null; + } + let handleFocus: any = other.onFocus; if ( @@ -763,4 +769,4 @@ const GridCellV7 = React.forwardRef((props const MemoizedGridCellV7 = fastMemo(GridCellV7); -export { MemoizedGridCellV7 as GridCellV7 } +export { MemoizedGridCellV7 as GridCellV7 }; diff --git a/packages/grid/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx b/packages/grid/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx index 0e367807e02e4..694f5cc5cc7c3 100644 --- a/packages/grid/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx +++ b/packages/grid/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx @@ -111,12 +111,19 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { 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]) + 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; diff --git a/packages/grid/x-data-grid/src/utils/fastObjectShallowCompare.ts b/packages/grid/x-data-grid/src/utils/fastObjectShallowCompare.ts index 3a47e7fe50fae..940220d63849d 100644 --- a/packages/grid/x-data-grid/src/utils/fastObjectShallowCompare.ts +++ b/packages/grid/x-data-grid/src/utils/fastObjectShallowCompare.ts @@ -24,6 +24,7 @@ export function fastObjectShallowCompare | null>(a } } + /* eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unused-vars */ for (const _ in b) { bLength += 1; } From e0ac8bd4042eb677cf665a51522396be276109b6 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Tue, 6 Jun 2023 04:30:09 -0400 Subject: [PATCH 52/72] lint --- .../virtualization/useGridVirtualScroller.tsx | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) 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 1f35490b030ae..1ecbe0690a020 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 @@ -636,17 +636,26 @@ 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 = React.useMemo(() => { const style = {} as React.CSSProperties; if (!needsHorizontalScrollbar) { From 1cb49d1fe5e09984d73ef29387ea0262b5cb2172 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Tue, 6 Jun 2023 09:22:27 -0400 Subject: [PATCH 53/72] lint --- docs/data/data-grid/overview/DataGridProDemo.js | 10 +--------- docs/data/data-grid/overview/DataGridProDemo.tsx | 10 +--------- .../data-grid/overview/DataGridProDemo.tsx.preview | 4 ---- 3 files changed, 2 insertions(+), 22 deletions(-) diff --git a/docs/data/data-grid/overview/DataGridProDemo.js b/docs/data/data-grid/overview/DataGridProDemo.js index 3e0a22b660475..10cc6a348fe17 100644 --- a/docs/data/data-grid/overview/DataGridProDemo.js +++ b/docs/data/data-grid/overview/DataGridProDemo.js @@ -1,12 +1,8 @@ import * as React from 'react'; import Box from '@mui/material/Box'; -import { DataGridPro, GridRow, GridColumnHeaders } from '@mui/x-data-grid-pro'; +import { DataGridPro } from '@mui/x-data-grid-pro'; import { useDemoData } from '@mui/x-data-grid-generator'; -const MemoizedRow = React.memo(GridRow); - -const MemoizedColumnHeaders = React.memo(GridColumnHeaders); - export default function DataGridProDemo() { const { data } = useDemoData({ dataSet: 'Commodity', @@ -22,10 +18,6 @@ export default function DataGridProDemo() { rowHeight={38} checkboxSelection disableRowSelectionOnClick - components={{ - Row: MemoizedRow, - ColumnHeaders: MemoizedColumnHeaders, - }} /> ); diff --git a/docs/data/data-grid/overview/DataGridProDemo.tsx b/docs/data/data-grid/overview/DataGridProDemo.tsx index 3e0a22b660475..10cc6a348fe17 100644 --- a/docs/data/data-grid/overview/DataGridProDemo.tsx +++ b/docs/data/data-grid/overview/DataGridProDemo.tsx @@ -1,12 +1,8 @@ import * as React from 'react'; import Box from '@mui/material/Box'; -import { DataGridPro, GridRow, GridColumnHeaders } from '@mui/x-data-grid-pro'; +import { DataGridPro } from '@mui/x-data-grid-pro'; import { useDemoData } from '@mui/x-data-grid-generator'; -const MemoizedRow = React.memo(GridRow); - -const MemoizedColumnHeaders = React.memo(GridColumnHeaders); - export default function DataGridProDemo() { const { data } = useDemoData({ dataSet: 'Commodity', @@ -22,10 +18,6 @@ export default function DataGridProDemo() { rowHeight={38} checkboxSelection disableRowSelectionOnClick - components={{ - Row: MemoizedRow, - ColumnHeaders: MemoizedColumnHeaders, - }} /> ); diff --git a/docs/data/data-grid/overview/DataGridProDemo.tsx.preview b/docs/data/data-grid/overview/DataGridProDemo.tsx.preview index 272b35f8a747b..1f8c5e44257e8 100644 --- a/docs/data/data-grid/overview/DataGridProDemo.tsx.preview +++ b/docs/data/data-grid/overview/DataGridProDemo.tsx.preview @@ -4,8 +4,4 @@ rowHeight={38} checkboxSelection disableRowSelectionOnClick - components={{ - Row: MemoizedRow, - ColumnHeaders: MemoizedColumnHeaders, - }} /> \ No newline at end of file From 753137949aad815dc3fb5e35dc0e61b60dde0aa7 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Tue, 6 Jun 2023 09:30:17 -0400 Subject: [PATCH 54/72] lint --- packages/grid/x-data-grid/src/components/cell/GridCell.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx index 6338899954630..30d5a57785395 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -125,7 +125,8 @@ 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): 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; From f0ee84327042d33cd2a97dbf3bdae624d6b2828b Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Tue, 6 Jun 2023 09:51:24 -0400 Subject: [PATCH 55/72] fix: missing classnames --- packages/grid/x-data-grid/src/components/cell/GridCell.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx index 30d5a57785395..48b15c4775edb 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -744,7 +744,7 @@ const GridCellV7 = React.forwardRef((props return (
Date: Tue, 6 Jun 2023 10:15:48 -0400 Subject: [PATCH 56/72] fix: cell slotProps --- packages/grid/x-data-grid/src/components/GridRow.tsx | 3 ++- packages/grid/x-data-grid/src/components/cell/GridCell.tsx | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/grid/x-data-grid/src/components/GridRow.tsx b/packages/grid/x-data-grid/src/components/GridRow.tsx index 8c2d9ab74fb14..cc1e5868f9b5d 100644 --- a/packages/grid/x-data-grid/src/components/GridRow.tsx +++ b/packages/grid/x-data-grid/src/components/GridRow.tsx @@ -257,7 +257,7 @@ const GridRow = React.forwardRef(function GridRow( [apiRef, onClick, publish, rowId], ); - const { slots, disableColumnReorder } = rootProps; + const { slots, slotProps, disableColumnReorder } = rootProps; const CellComponent = slots.cell === GridCellV7 ? GridCellV7 : GridCellWrapper; const rowReordering = (rootProps as any).rowReordering as boolean; @@ -291,6 +291,7 @@ const GridRow = React.forwardRef(function GridRow( colSpan={cellProps.colSpan} disableDragEvents={disableDragEvents} editCellState={editCellState} + {...slotProps?.cell} /> ); }; diff --git a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx index 48b15c4775edb..13680ffb714cc 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -219,7 +219,7 @@ const GridCellWrapper = React.forwardRef(( classNames.push(rootClasses?.['cell--editing']); } - const { slots, slotProps } = rootProps; + const { slots } = rootProps; const CellComponent = slots.cell; @@ -236,7 +236,6 @@ const GridCellWrapper = React.forwardRef(( children, tabIndex, className: clsx(classNames), - ...slotProps?.cell, }; return React.createElement(CellComponent, cellProps); From e49394cd9cf6b0c3082abd34becc354c49efab4a Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Tue, 6 Jun 2023 10:39:17 -0400 Subject: [PATCH 57/72] test: fix --- .../x-data-grid-pro/src/tests/rowEditing.DataGridPro.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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, From 971cf3296de5927d04c18f483981a081f60ea06a Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Tue, 6 Jun 2023 11:45:29 -0400 Subject: [PATCH 58/72] fix: make things work --- .../src/hooks/features/focus/useGridFocus.ts | 26 ++++++++++++++----- .../src/tests/keyboard.DataGrid.test.tsx | 8 +++--- 2 files changed, 23 insertions(+), 11 deletions(-) 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 108855acedc4e..c254bf99ce168 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'; @@ -325,13 +326,24 @@ export const useGridFocus = ( [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/tests/keyboard.DataGrid.test.tsx b/packages/grid/x-data-grid/src/tests/keyboard.DataGrid.test.tsx index 95639f0db6dca..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 @@ -600,17 +600,17 @@ describe(' - Keyboard', () => { it('should go back to same header when pressing "ArrowUp" and "ArrowDown" from column header', () => { render(); - act(() => getColumnHeaderCell(2, 2).focus()); + act(() => getColumnHeaderCell(4, 2).focus()); // column with field "price3M" - expectAriaCoordinate(document.activeElement, { rowIndex: 3, colIndex: 3 }); + expectAriaCoordinate(document.activeElement, { rowIndex: 3, colIndex: 5 }); fireEvent.keyDown(document.activeElement!, { key: 'ArrowUp' }); // group "prices 234" - expectAriaCoordinate(document.activeElement, { rowIndex: 2, colIndex: 3 }); + expectAriaCoordinate(document.activeElement, { rowIndex: 2, colIndex: 4 }); fireEvent.keyDown(document.activeElement!, { key: 'ArrowDown' }); // column with field "price3M" - expectAriaCoordinate(document.activeElement, { rowIndex: 3, colIndex: 3 }); + expectAriaCoordinate(document.activeElement, { rowIndex: 3, colIndex: 5 }); }); }); From 5c884e7a12dbd87adfd38bddf1be58c434b3c564 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Tue, 6 Jun 2023 12:01:56 -0400 Subject: [PATCH 59/72] lint: proptypes --- .../src/components/cell/GridCell.tsx | 52 +++++++++++++++---- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx index 13680ffb714cc..a9190538b35fb 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -34,7 +34,7 @@ import { gridFocusCellSelector } from '../../hooks/features/focus/gridFocusState import { MissingRowIdError } from '../../hooks/features/rows/useGridParamsApi'; import type { DataGridProcessedProps } from '../../models/props/DataGridProps'; -type GridCellWrapperProps = { +type GridCellV7Props = { align: GridAlignment; className?: string; colIndex: number; @@ -55,7 +55,8 @@ type GridCellWrapperProps = { onDragOver?: React.DragEventHandler; [x: string]: any; }; -/* eslint-enable react/no-unused-prop-types */ + +type GridCellWrapperProps = GridCellV7Props; export type GridCellProps = GridCellWrapperProps & { field: string; @@ -146,6 +147,7 @@ const GridCellWrapper = React.forwardRef(( rowId, field, ); + const result = cellParams as CellParamsWithAPI; result.api = apiRef.current; return result; @@ -474,12 +476,13 @@ GridCell.propTypes = { 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, @@ -489,14 +492,12 @@ 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 { MemoizedCellWrapper as GridCellWrapper, GridCell }; -const GridCellV7 = React.forwardRef((props, ref) => { +const GridCellV7 = React.forwardRef((props, ref) => { const { column, rowId, @@ -540,6 +541,7 @@ const GridCellV7 = React.forwardRef((props rowId, field, ); + const result = cellParams as CellParamsWithAPI; result.api = apiRef.current; return result; @@ -767,6 +769,36 @@ const GridCellV7 = React.forwardRef((props ); }); +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, + 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; + const MemoizedGridCellV7 = fastMemo(GridCellV7); export { MemoizedGridCellV7 as GridCellV7 }; From 1d611c02e624ee63d358798914c0e890c81190d1 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Tue, 6 Jun 2023 13:10:49 -0400 Subject: [PATCH 60/72] lint --- scripts/x-data-grid-premium.exports.json | 1 + scripts/x-data-grid-pro.exports.json | 1 + scripts/x-data-grid.exports.json | 1 + 3 files changed, 3 insertions(+) diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index a91b490535f88..3046aea3f2bf9 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -131,6 +131,7 @@ { "name": "GridCellProps", "kind": "TypeAlias" }, { "name": "GridCellSelectionApi", "kind": "Interface" }, { "name": "GridCellSelectionModel", "kind": "TypeAlias" }, + { "name": "GridCellV7", "kind": "Variable" }, { "name": "GridCellWrapper", "kind": "Variable" }, { "name": "GridCheckCircleIcon", "kind": "Variable" }, { "name": "GridCheckIcon", "kind": "Variable" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index 8d5a789d29fb1..9e6f52bf13edc 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -107,6 +107,7 @@ { "name": "GridCellModesModel", "kind": "TypeAlias" }, { "name": "GridCellParams", "kind": "Interface" }, { "name": "GridCellProps", "kind": "TypeAlias" }, + { "name": "GridCellV7", "kind": "Variable" }, { "name": "GridCellWrapper", "kind": "Variable" }, { "name": "GridCheckCircleIcon", "kind": "Variable" }, { "name": "GridCheckIcon", "kind": "Variable" }, diff --git a/scripts/x-data-grid.exports.json b/scripts/x-data-grid.exports.json index 134b7876c2368..7e9cb3637f485 100644 --- a/scripts/x-data-grid.exports.json +++ b/scripts/x-data-grid.exports.json @@ -100,6 +100,7 @@ { "name": "GridCellModesModel", "kind": "TypeAlias" }, { "name": "GridCellParams", "kind": "Interface" }, { "name": "GridCellProps", "kind": "TypeAlias" }, + { "name": "GridCellV7", "kind": "Variable" }, { "name": "GridCellWrapper", "kind": "Variable" }, { "name": "GridCheckCircleIcon", "kind": "Variable" }, { "name": "GridCheckIcon", "kind": "Variable" }, From 2d419e747c3b9c2cb891d4b9c522d22a6cceee82 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Wed, 7 Jun 2023 09:13:37 -0400 Subject: [PATCH 61/72] refactor: shallowCompare => objectShallowCompare --- packages/grid/x-data-grid/src/components/GridRow.tsx | 4 ++-- packages/grid/x-data-grid/src/components/cell/GridCell.tsx | 6 +++--- .../grid/x-data-grid/src/hooks/utils/useGridSelector.ts | 2 +- scripts/x-data-grid-premium.exports.json | 2 +- scripts/x-data-grid-pro.exports.json | 2 +- scripts/x-data-grid.exports.json | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/grid/x-data-grid/src/components/GridRow.tsx b/packages/grid/x-data-grid/src/components/GridRow.tsx index cc1e5868f9b5d..125bb597a189c 100644 --- a/packages/grid/x-data-grid/src/components/GridRow.tsx +++ b/packages/grid/x-data-grid/src/components/GridRow.tsx @@ -15,7 +15,7 @@ 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, shallowCompare } 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'; @@ -299,7 +299,7 @@ const GridRow = React.forwardRef(function GridRow( const sizes = useGridSelector( apiRef, () => ({ ...apiRef.current.unstable_getRowInternalSizes(rowId) }), - shallowCompare, + objectShallowCompare, ); let minHeight = rowHeight; diff --git a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx index a9190538b35fb..4bb9eded44f78 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -27,7 +27,7 @@ import { import { GridColDef, GridAlignment } from '../../models/colDef/gridColDef'; import { GridTreeNodeWithRender } from '../../models/gridRows'; import { GridEditCellProps } from '../../models/gridEditRowModel'; -import { useGridSelector, shallowCompare } from '../../hooks/utils/useGridSelector'; +import { useGridSelector, objectShallowCompare } from '../../hooks/utils/useGridSelector'; import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import { gridFocusCellSelector } from '../../hooks/features/focus/gridFocusStateSelector'; @@ -158,7 +158,7 @@ const GridCellWrapper = React.forwardRef(( throw e; } }, - shallowCompare, + objectShallowCompare, ); const isSelected = useGridSelector(apiRef, () => @@ -552,7 +552,7 @@ const GridCellV7 = React.forwardRef((props, ref throw e; } }, - shallowCompare, + objectShallowCompare, ); const isSelected = useGridSelector(apiRef, () => 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 c584d9c2d0a23..5902549e2e3f6 100644 --- a/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts +++ b/packages/grid/x-data-grid/src/hooks/utils/useGridSelector.ts @@ -28,7 +28,7 @@ function applySelector( } const defaultCompare = Object.is; -export const shallowCompare = fastObjectShallowCompare; +export const objectShallowCompare = fastObjectShallowCompare; const createRefs = () => ({ state: null, equals: null, selector: null } as any); diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 3046aea3f2bf9..184b884e4f918 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -610,7 +610,7 @@ { "name": "selectedGridRowsSelector", "kind": "Variable" }, { "name": "selectedIdsLookupSelector", "kind": "Variable" }, { "name": "setupExcelExportWebWorker", "kind": "Function" }, - { "name": "shallowCompare", "kind": "Variable" }, + { "name": "objectShallowCompare", "kind": "Variable" }, { "name": "skSK", "kind": "Variable" }, { "name": "SUBMIT_FILTER_DATE_STROKE_TIME", "kind": "Variable" }, { "name": "SUBMIT_FILTER_STROKE_TIME", "kind": "Variable" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index 9e6f52bf13edc..c71b60c6dc968 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -562,7 +562,7 @@ { "name": "selectedGridRowsCountSelector", "kind": "Variable" }, { "name": "selectedGridRowsSelector", "kind": "Variable" }, { "name": "selectedIdsLookupSelector", "kind": "Variable" }, - { "name": "shallowCompare", "kind": "Variable" }, + { "name": "objectShallowCompare", "kind": "Variable" }, { "name": "skSK", "kind": "Variable" }, { "name": "SUBMIT_FILTER_DATE_STROKE_TIME", "kind": "Variable" }, { "name": "SUBMIT_FILTER_STROKE_TIME", "kind": "Variable" }, diff --git a/scripts/x-data-grid.exports.json b/scripts/x-data-grid.exports.json index 7e9cb3637f485..f412d8c38a6e0 100644 --- a/scripts/x-data-grid.exports.json +++ b/scripts/x-data-grid.exports.json @@ -516,7 +516,7 @@ { "name": "selectedGridRowsCountSelector", "kind": "Variable" }, { "name": "selectedGridRowsSelector", "kind": "Variable" }, { "name": "selectedIdsLookupSelector", "kind": "Variable" }, - { "name": "shallowCompare", "kind": "Variable" }, + { "name": "objectShallowCompare", "kind": "Variable" }, { "name": "skSK", "kind": "Variable" }, { "name": "SUBMIT_FILTER_DATE_STROKE_TIME", "kind": "Variable" }, { "name": "SUBMIT_FILTER_STROKE_TIME", "kind": "Variable" }, From bab0887f256a9be70dd44b584db34be9884bcdaf Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Thu, 8 Jun 2023 09:53:59 -0400 Subject: [PATCH 62/72] refactor: dont export GridCellV7 --- packages/grid/x-data-grid/src/components/cell/index.ts | 2 +- .../x-data-grid/src/constants/defaultGridSlotsComponents.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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..c615e8ce629da 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,4 @@ -export * from './GridCell'; +export { GridCell } from './GridCell'; export * from './GridBooleanCell'; export * from './GridEditBooleanCell'; export * from './GridEditDateCell'; diff --git a/packages/grid/x-data-grid/src/constants/defaultGridSlotsComponents.ts b/packages/grid/x-data-grid/src/constants/defaultGridSlotsComponents.ts index 407747e057c96..bea987d9cfc48 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 { - GridCellV7, 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'; From 3db4b83c5d86d3b3586063c801eb704ce60968b5 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Thu, 8 Jun 2023 10:05:43 -0400 Subject: [PATCH 63/72] refactor --- packages/grid/x-data-grid/src/components/cell/index.ts | 2 +- scripts/x-data-grid-premium.exports.json | 4 +--- scripts/x-data-grid-pro.exports.json | 4 +--- scripts/x-data-grid.exports.json | 4 +--- 4 files changed, 4 insertions(+), 10 deletions(-) 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 c615e8ce629da..b6f58236fd9d0 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,4 @@ -export { GridCell } from './GridCell'; +export { GridCell, GridCellProps } from './GridCell'; export * from './GridBooleanCell'; export * from './GridEditBooleanCell'; export * from './GridEditDateCell'; diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 184b884e4f918..4c7c8acd4753c 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -131,8 +131,6 @@ { "name": "GridCellProps", "kind": "TypeAlias" }, { "name": "GridCellSelectionApi", "kind": "Interface" }, { "name": "GridCellSelectionModel", "kind": "TypeAlias" }, - { "name": "GridCellV7", "kind": "Variable" }, - { "name": "GridCellWrapper", "kind": "Variable" }, { "name": "GridCheckCircleIcon", "kind": "Variable" }, { "name": "GridCheckIcon", "kind": "Variable" }, { "name": "GridChildrenFromPathLookup", "kind": "TypeAlias" }, @@ -591,6 +589,7 @@ { "name": "nlNL", "kind": "Variable" }, { "name": "NoResultsOverlayPropsOverrides", "kind": "Interface" }, { "name": "NoRowsOverlayPropsOverrides", "kind": "Interface" }, + { "name": "objectShallowCompare", "kind": "Variable" }, { "name": "OutputSelector", "kind": "Interface" }, { "name": "PaginationPropsOverrides", "kind": "Interface" }, { "name": "PanelPropsOverrides", "kind": "Interface" }, @@ -610,7 +609,6 @@ { "name": "selectedGridRowsSelector", "kind": "Variable" }, { "name": "selectedIdsLookupSelector", "kind": "Variable" }, { "name": "setupExcelExportWebWorker", "kind": "Function" }, - { "name": "objectShallowCompare", "kind": "Variable" }, { "name": "skSK", "kind": "Variable" }, { "name": "SUBMIT_FILTER_DATE_STROKE_TIME", "kind": "Variable" }, { "name": "SUBMIT_FILTER_STROKE_TIME", "kind": "Variable" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index c71b60c6dc968..5f64414d17150 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -107,8 +107,6 @@ { "name": "GridCellModesModel", "kind": "TypeAlias" }, { "name": "GridCellParams", "kind": "Interface" }, { "name": "GridCellProps", "kind": "TypeAlias" }, - { "name": "GridCellV7", "kind": "Variable" }, - { "name": "GridCellWrapper", "kind": "Variable" }, { "name": "GridCheckCircleIcon", "kind": "Variable" }, { "name": "GridCheckIcon", "kind": "Variable" }, { "name": "GridChildrenFromPathLookup", "kind": "TypeAlias" }, @@ -544,6 +542,7 @@ { "name": "nlNL", "kind": "Variable" }, { "name": "NoResultsOverlayPropsOverrides", "kind": "Interface" }, { "name": "NoRowsOverlayPropsOverrides", "kind": "Interface" }, + { "name": "objectShallowCompare", "kind": "Variable" }, { "name": "OutputSelector", "kind": "Interface" }, { "name": "PaginationPropsOverrides", "kind": "Interface" }, { "name": "PanelPropsOverrides", "kind": "Interface" }, @@ -562,7 +561,6 @@ { "name": "selectedGridRowsCountSelector", "kind": "Variable" }, { "name": "selectedGridRowsSelector", "kind": "Variable" }, { "name": "selectedIdsLookupSelector", "kind": "Variable" }, - { "name": "objectShallowCompare", "kind": "Variable" }, { "name": "skSK", "kind": "Variable" }, { "name": "SUBMIT_FILTER_DATE_STROKE_TIME", "kind": "Variable" }, { "name": "SUBMIT_FILTER_STROKE_TIME", "kind": "Variable" }, diff --git a/scripts/x-data-grid.exports.json b/scripts/x-data-grid.exports.json index f412d8c38a6e0..2b19329fe38f9 100644 --- a/scripts/x-data-grid.exports.json +++ b/scripts/x-data-grid.exports.json @@ -100,8 +100,6 @@ { "name": "GridCellModesModel", "kind": "TypeAlias" }, { "name": "GridCellParams", "kind": "Interface" }, { "name": "GridCellProps", "kind": "TypeAlias" }, - { "name": "GridCellV7", "kind": "Variable" }, - { "name": "GridCellWrapper", "kind": "Variable" }, { "name": "GridCheckCircleIcon", "kind": "Variable" }, { "name": "GridCheckIcon", "kind": "Variable" }, { "name": "GridChildrenFromPathLookup", "kind": "TypeAlias" }, @@ -498,6 +496,7 @@ { "name": "nlNL", "kind": "Variable" }, { "name": "NoResultsOverlayPropsOverrides", "kind": "Interface" }, { "name": "NoRowsOverlayPropsOverrides", "kind": "Interface" }, + { "name": "objectShallowCompare", "kind": "Variable" }, { "name": "OutputSelector", "kind": "Interface" }, { "name": "PaginationPropsOverrides", "kind": "Interface" }, { "name": "PanelPropsOverrides", "kind": "Interface" }, @@ -516,7 +515,6 @@ { "name": "selectedGridRowsCountSelector", "kind": "Variable" }, { "name": "selectedGridRowsSelector", "kind": "Variable" }, { "name": "selectedIdsLookupSelector", "kind": "Variable" }, - { "name": "objectShallowCompare", "kind": "Variable" }, { "name": "skSK", "kind": "Variable" }, { "name": "SUBMIT_FILTER_DATE_STROKE_TIME", "kind": "Variable" }, { "name": "SUBMIT_FILTER_STROKE_TIME", "kind": "Variable" }, From 659d966965544afb6c03970f4b61e68e85915256 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Thu, 8 Jun 2023 10:11:23 -0400 Subject: [PATCH 64/72] lint --- .../x-data-grid/src/constants/defaultGridSlotsComponents.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grid/x-data-grid/src/constants/defaultGridSlotsComponents.ts b/packages/grid/x-data-grid/src/constants/defaultGridSlotsComponents.ts index bea987d9cfc48..d0256ab5b76b4 100644 --- a/packages/grid/x-data-grid/src/constants/defaultGridSlotsComponents.ts +++ b/packages/grid/x-data-grid/src/constants/defaultGridSlotsComponents.ts @@ -12,7 +12,7 @@ import { GridRow, GridColumnHeaderFilterIconButton, } from '../components'; -import { GridCellV7 } from '../components/cell/GridCell' +import { GridCellV7 } from '../components/cell/GridCell'; import { GridColumnHeaders } from '../components/GridColumnHeaders'; import { GridColumnMenu } from '../components/menu/columnMenu/GridColumnMenu'; import { GridNoResultsOverlay } from '../components/GridNoResultsOverlay'; From c37f2af97b816320ebb83c25fa91827f06cbc0cc Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Fri, 9 Jun 2023 03:39:08 -0400 Subject: [PATCH 65/72] lint --- packages/grid/x-data-grid/src/components/cell/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 b6f58236fd9d0..e59c4532097b5 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 { GridCell, GridCellProps } from './GridCell'; +export { GridCell } from './GridCell'; +export type { GridCellProps } from './GridCell'; export * from './GridBooleanCell'; export * from './GridEditBooleanCell'; export * from './GridEditDateCell'; From 6e06534bd3f0d3e00aec6bb4b34454c572d1006d Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Fri, 9 Jun 2023 03:45:57 -0400 Subject: [PATCH 66/72] doc: update --- docs/data/data-grid/state/state.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/data/data-grid/state/state.md b/docs/data/data-grid/state/state.md index dc3501325c658..10c33d5e74a5b 100644 --- a/docs/data/data-grid/state/state.md +++ b/docs/data/data-grid/state/state.md @@ -54,7 +54,9 @@ 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); From a68f2730105ea5a923b16375d6eaef3d62179181 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Fri, 9 Jun 2023 04:10:03 -0400 Subject: [PATCH 67/72] lint --- packages/grid/x-data-grid/src/components/cell/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e59c4532097b5..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,5 +1,5 @@ export { GridCell } from './GridCell'; -export type { GridCellProps } from './GridCell'; +export type { GridCellProps } from './GridCell'; export * from './GridBooleanCell'; export * from './GridEditBooleanCell'; export * from './GridEditDateCell'; From 494d29086e26ebe9b640a604cb8fbc3018f27582 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Fri, 9 Jun 2023 04:25:35 -0400 Subject: [PATCH 68/72] lint --- packages/grid/x-data-grid/src/hooks/utils/index.ts | 2 +- scripts/x-data-grid-premium.exports.json | 1 - scripts/x-data-grid-pro.exports.json | 1 - scripts/x-data-grid.exports.json | 1 - 4 files changed, 1 insertion(+), 4 deletions(-) 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/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 4c7c8acd4753c..0416c08f69d2a 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -589,7 +589,6 @@ { "name": "nlNL", "kind": "Variable" }, { "name": "NoResultsOverlayPropsOverrides", "kind": "Interface" }, { "name": "NoRowsOverlayPropsOverrides", "kind": "Interface" }, - { "name": "objectShallowCompare", "kind": "Variable" }, { "name": "OutputSelector", "kind": "Interface" }, { "name": "PaginationPropsOverrides", "kind": "Interface" }, { "name": "PanelPropsOverrides", "kind": "Interface" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index 5f64414d17150..de47b1d304223 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -542,7 +542,6 @@ { "name": "nlNL", "kind": "Variable" }, { "name": "NoResultsOverlayPropsOverrides", "kind": "Interface" }, { "name": "NoRowsOverlayPropsOverrides", "kind": "Interface" }, - { "name": "objectShallowCompare", "kind": "Variable" }, { "name": "OutputSelector", "kind": "Interface" }, { "name": "PaginationPropsOverrides", "kind": "Interface" }, { "name": "PanelPropsOverrides", "kind": "Interface" }, diff --git a/scripts/x-data-grid.exports.json b/scripts/x-data-grid.exports.json index 2b19329fe38f9..f5a37f3b7e5d8 100644 --- a/scripts/x-data-grid.exports.json +++ b/scripts/x-data-grid.exports.json @@ -496,7 +496,6 @@ { "name": "nlNL", "kind": "Variable" }, { "name": "NoResultsOverlayPropsOverrides", "kind": "Interface" }, { "name": "NoRowsOverlayPropsOverrides", "kind": "Interface" }, - { "name": "objectShallowCompare", "kind": "Variable" }, { "name": "OutputSelector", "kind": "Interface" }, { "name": "PaginationPropsOverrides", "kind": "Interface" }, { "name": "PanelPropsOverrides", "kind": "Interface" }, From 0169a5a1ad651cd80cb2b7bcd57fd08cbe37631e Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Mon, 12 Jun 2023 10:55:58 -0400 Subject: [PATCH 69/72] ci: run From 096b73555cc58ba38adf089f730c9bc521459249 Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Tue, 13 Jun 2023 18:17:17 -0400 Subject: [PATCH 70/72] Update docs/data/data-grid/state/state.md Co-authored-by: Matheus Wichman Signed-off-by: Rom Grk --- docs/data/data-grid/state/state.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/data/data-grid/state/state.md b/docs/data/data-grid/state/state.md index 10c33d5e74a5b..fc6d84fb87cd1 100644 --- a/docs/data/data-grid/state/state.md +++ b/docs/data/data-grid/state/state.md @@ -54,9 +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. 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. +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); From 2028f8f53d9f5848d4ddb463c2d266226c9f807c Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Tue, 13 Jun 2023 18:22:48 -0400 Subject: [PATCH 71/72] lint --- packages/grid/x-data-grid/src/utils/{Store.tsx => Store.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/grid/x-data-grid/src/utils/{Store.tsx => Store.ts} (100%) diff --git a/packages/grid/x-data-grid/src/utils/Store.tsx b/packages/grid/x-data-grid/src/utils/Store.ts similarity index 100% rename from packages/grid/x-data-grid/src/utils/Store.tsx rename to packages/grid/x-data-grid/src/utils/Store.ts From d583d74c22497d1ca724bd2d5a698cd1acb4230d Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Tue, 13 Jun 2023 18:32:22 -0400 Subject: [PATCH 72/72] fix: column props propagation delay --- .../src/components/menu/columnMenu/GridColumnHeaderMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; }