From 05621934a804fbb8d07d2562854a4d649c412220 Mon Sep 17 00:00:00 2001 From: Matheus Wichman Date: Wed, 15 Feb 2023 14:55:06 -0300 Subject: [PATCH 01/15] [DataGrid] Make possible to memoize rows and cells --- .../data-grid/overview/DataGridProDemo.js | 18 +- .../data-grid/overview/DataGridProDemo.tsx | 18 +- .../overview/DataGridProDemo.tsx.preview | 5 + .../performance/GridWithReactMemo.js | 81 ++++ .../performance/GridWithReactMemo.tsx | 81 ++++ .../performance/GridWithReactMemo.tsx.preview | 11 + .../data/data-grid/performance/performance.md | 72 ++++ .../migration-data-grid-v5.md | 12 +- docs/data/pages.ts | 1 + .../x/api/data-grid/data-grid-premium.json | 1 + docs/pages/x/api/data-grid/data-grid-pro.json | 1 + docs/pages/x/api/data-grid/data-grid.json | 1 + docs/pages/x/react-data-grid/performance.js | 7 + .../api-docs/data-grid/data-grid-premium.json | 1 + .../api-docs/data-grid/data-grid-pro.json | 1 + .../api-docs/data-grid/data-grid.json | 1 + .../src/DataGridPremium/DataGridPremium.tsx | 11 +- .../grid/x-data-grid-premium/src/index.ts | 2 + .../src/DataGridPro/DataGridPro.tsx | 7 +- .../components/DataGridProColumnHeaders.tsx | 381 +++++++++++------- .../components/DataGridProVirtualScroller.tsx | 17 +- .../dataGridProDefaultSlotsComponents.ts | 2 + packages/grid/x-data-grid-pro/src/index.ts | 2 + .../src/components/DataGridColumnHeaders.tsx | 96 ++++- .../x-data-grid/src/components/GridRow.tsx | 97 +++-- .../src/components/base/GridBody.tsx | 87 +++- .../src/components/cell/GridCell.tsx | 22 +- .../constants/defaultGridSlotsComponents.ts | 2 + .../columnHeaders/useGridColumnHeaders.tsx | 94 ++--- .../virtualization/useGridVirtualScroller.tsx | 67 ++- packages/grid/x-data-grid/src/index.ts | 2 + .../grid/x-data-grid/src/internals/index.ts | 1 + .../src/models/gridSlotsComponent.ts | 5 + scripts/x-data-grid-premium.exports.json | 3 +- scripts/x-data-grid-pro.exports.json | 3 +- scripts/x-data-grid.exports.json | 3 +- 36 files changed, 923 insertions(+), 293 deletions(-) create mode 100644 docs/data/data-grid/performance/GridWithReactMemo.js create mode 100644 docs/data/data-grid/performance/GridWithReactMemo.tsx create mode 100644 docs/data/data-grid/performance/GridWithReactMemo.tsx.preview create mode 100644 docs/data/data-grid/performance/performance.md create mode 100644 docs/pages/x/react-data-grid/performance.js diff --git a/docs/data/data-grid/overview/DataGridProDemo.js b/docs/data/data-grid/overview/DataGridProDemo.js index 10cc6a348fe17..ee79a1a6c6267 100644 --- a/docs/data/data-grid/overview/DataGridProDemo.js +++ b/docs/data/data-grid/overview/DataGridProDemo.js @@ -1,8 +1,19 @@ import * as React from 'react'; import Box from '@mui/material/Box'; -import { DataGridPro } from '@mui/x-data-grid-pro'; +import { + DataGridPro, + GridRow, + GridCell, + DataGridProColumnHeaders, +} from '@mui/x-data-grid-pro'; import { useDemoData } from '@mui/x-data-grid-generator'; +const MemoizedRow = React.memo(GridRow); + +const MemoizedCell = React.memo(GridCell); + +const MemoizedColumnHeaders = React.memo(DataGridProColumnHeaders); + export default function DataGridProDemo() { const { data } = useDemoData({ dataSet: 'Commodity', @@ -18,6 +29,11 @@ export default function DataGridProDemo() { rowHeight={38} checkboxSelection disableRowSelectionOnClick + components={{ + Row: MemoizedRow, + Cell: MemoizedCell, + ColumnHeaders: MemoizedColumnHeaders, + }} /> ); diff --git a/docs/data/data-grid/overview/DataGridProDemo.tsx b/docs/data/data-grid/overview/DataGridProDemo.tsx index 10cc6a348fe17..ee79a1a6c6267 100644 --- a/docs/data/data-grid/overview/DataGridProDemo.tsx +++ b/docs/data/data-grid/overview/DataGridProDemo.tsx @@ -1,8 +1,19 @@ import * as React from 'react'; import Box from '@mui/material/Box'; -import { DataGridPro } from '@mui/x-data-grid-pro'; +import { + DataGridPro, + GridRow, + GridCell, + DataGridProColumnHeaders, +} from '@mui/x-data-grid-pro'; import { useDemoData } from '@mui/x-data-grid-generator'; +const MemoizedRow = React.memo(GridRow); + +const MemoizedCell = React.memo(GridCell); + +const MemoizedColumnHeaders = React.memo(DataGridProColumnHeaders); + export default function DataGridProDemo() { const { data } = useDemoData({ dataSet: 'Commodity', @@ -18,6 +29,11 @@ export default function DataGridProDemo() { rowHeight={38} checkboxSelection disableRowSelectionOnClick + components={{ + Row: MemoizedRow, + Cell: MemoizedCell, + ColumnHeaders: MemoizedColumnHeaders, + }} /> ); diff --git a/docs/data/data-grid/overview/DataGridProDemo.tsx.preview b/docs/data/data-grid/overview/DataGridProDemo.tsx.preview index 1f8c5e44257e8..8eb2d740cf670 100644 --- a/docs/data/data-grid/overview/DataGridProDemo.tsx.preview +++ b/docs/data/data-grid/overview/DataGridProDemo.tsx.preview @@ -4,4 +4,9 @@ rowHeight={38} checkboxSelection disableRowSelectionOnClick + components={{ + Row: MemoizedRow, + Cell: MemoizedCell, + ColumnHeaders: MemoizedColumnHeaders, + }} /> \ No newline at end of file diff --git a/docs/data/data-grid/performance/GridWithReactMemo.js b/docs/data/data-grid/performance/GridWithReactMemo.js new file mode 100644 index 0000000000000..2270085a90c11 --- /dev/null +++ b/docs/data/data-grid/performance/GridWithReactMemo.js @@ -0,0 +1,81 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { unstable_useForkRef as useForkRef } from '@mui/utils'; +import { + DataGridPro, + GridRow, + GridCell, + DataGridProColumnHeaders, +} 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(() => { + rootRef.current?.classList.add('updating'); + + const timer = setTimeout(() => { + rootRef.current?.classList.remove('updating'); + }, 500); + + return () => clearTimeout(timer); + }); + + return ; +}); + +const RowWithTracer = React.forwardRef((props, ref) => { + return ; +}); + +const CellWithTracer = React.forwardRef((props, ref) => { + return ; +}); + +const ColumnHeadersWithTracer = React.forwardRef((props, ref) => { + return ; +}); + +const MemoizedRow = React.memo(RowWithTracer); +const MemoizedCell = React.memo(CellWithTracer); +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', { + duration: theme.transitions.duration.standard, + }), + }, + }} + > + + + ); +} diff --git a/docs/data/data-grid/performance/GridWithReactMemo.tsx b/docs/data/data-grid/performance/GridWithReactMemo.tsx new file mode 100644 index 0000000000000..afa1ad116160b --- /dev/null +++ b/docs/data/data-grid/performance/GridWithReactMemo.tsx @@ -0,0 +1,81 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { unstable_useForkRef as useForkRef } from '@mui/utils'; +import { + DataGridPro, + GridRow, + GridCell, + DataGridProColumnHeaders, +} 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(() => { + rootRef.current?.classList.add('updating'); + + const timer = setTimeout(() => { + rootRef.current?.classList.remove('updating'); + }, 500); + + return () => clearTimeout(timer); + }); + + return ; +}); + +const RowWithTracer = React.forwardRef((props, ref) => { + return ; +}); + +const CellWithTracer = React.forwardRef((props, ref) => { + return ; +}); + +const ColumnHeadersWithTracer = React.forwardRef((props, ref) => { + return ; +}); + +const MemoizedRow = React.memo(RowWithTracer); +const MemoizedCell = React.memo(CellWithTracer); +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', { + duration: theme.transitions.duration.standard, + }), + }, + }} + > + + + ); +} diff --git a/docs/data/data-grid/performance/GridWithReactMemo.tsx.preview b/docs/data/data-grid/performance/GridWithReactMemo.tsx.preview new file mode 100644 index 0000000000000..6fa57a1bb7042 --- /dev/null +++ b/docs/data/data-grid/performance/GridWithReactMemo.tsx.preview @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/performance/performance.md b/docs/data/data-grid/performance/performance.md new file mode 100644 index 0000000000000..06b1b07affc23 --- /dev/null +++ b/docs/data/data-grid/performance/performance.md @@ -0,0 +1,72 @@ +# Data Grid - Performance + +

Improve the performance of the DataGrid using the recommendations from this guide.

+ +## Memoize inner components with `React.memo` + +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`, it means that the `DataGrid` component will re-render as well as its children, which includes 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 gets increased and, specially, 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 children 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: + +```tsx +import { + GridRow, + GridCell, + DataGrid, // or DataGridPro, DataGridPremium + DataGridColumnHeaders, // or DataGridProColumnHeaders, DataGridPremiumColumnHeaders +} from '@mui/x-data-grid'; + +const MemoizedRow = React.memo(GridRow); +const MemoizedCell = React.memo(GridCell); +const MemoizedColumnHeaders = React.memo(DataGridColumnHeaders); + +; +``` + +The following demo show this trick in action. +It also contains additional logic to highlight the components when they re-render. + +{{"demo": "GridWithReactMemo.js", "bg": "inline", "defaultCodeOpen": false}} + +:::warning +We do not ship the components above already wrapped with `React.memo` because if you have cells that display custom content whose source is not the received props, these cells may display outdated information. +For instance, 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 then the props passed to `GridCell` and `GridRow`, the row and column should not be memoized. +You can choose whether to memoize or not a component by passing a 2nd argument to `React.memo`: + +```tsx +function shallowCompare(prevProps, nextProps) { + const aKeys = Object.keys(prevProps); + const bKeys = Object.keys(nextProps); + + if (aKeys.length !== bKeys.length) { + return false; + } + + return aKeys.every( + (key) => nextProps.hasOwnProperty(key) && nextProps[key] === prevProps[key], + ); +} + +const MemoizedCell = React.memo(GridCell, (prevProps, nextProps) => { + // Prevent memoizing the cells from the "total" column + return nextProps.field !== 'total' && shallowCompare(prevProps, nextProps); +}); +``` + +::: + +## API + +- [DataGrid](/x/api/data-grid/data-grid/) +- [DataGridPro](/x/api/data-grid/data-grid-pro/) +- [DataGridPremium](/x/api/data-grid/data-grid-premium/) diff --git a/docs/data/migration/migration-data-grid-v5/migration-data-grid-v5.md b/docs/data/migration/migration-data-grid-v5/migration-data-grid-v5.md index 44ca9bb05e27d..2b33735ea856c 100644 --- a/docs/data/migration/migration-data-grid-v5/migration-data-grid-v5.md +++ b/docs/data/migration/migration-data-grid-v5/migration-data-grid-v5.md @@ -390,13 +390,13 @@ Most of this breaking change is handled by `preset-safe` codemod but some furthe | `.MuiDataGrid-withBorder` | `.MuiDataGrid-withBorderColor` | The class only sets `border-color` CSS property | | `.MuiDataGrid-filterFormLinkOperatorInput` | `.MuiDataGrid-filterFormLogicOperatorInput` | | - - ### Removals from the public API - The `getGridSingleSelectQuickFilterFn` function was removed. You can copy the old function and pass it to the `getApplyQuickFilterFn` property of the `singleSelect` column definition. + +### Misc + +- The `cellFocus`, `cellTabIndex` and `editRowsState` props are not passed to the component used in the row slot. + You can use the new `focusedCell` and `tabbableCell` props instead. + For the editing state, use the API methods. diff --git a/docs/data/pages.ts b/docs/data/pages.ts index 59b1a04a992b1..b5dedfeaa5c6d 100644 --- a/docs/data/pages.ts +++ b/docs/data/pages.ts @@ -71,6 +71,7 @@ const pages: MuiPage[] = [ { pathname: '/x/react-data-grid/scrolling' }, { pathname: '/x/react-data-grid/virtualization' }, { pathname: '/x/react-data-grid/accessibility' }, + { pathname: '/x/react-data-grid/performance' }, { pathname: '/x/react-data-grid-group-pivot', title: 'Group & Pivot', diff --git a/docs/pages/x/api/data-grid/data-grid-premium.json b/docs/pages/x/api/data-grid/data-grid-premium.json index 85e81dc20cc4f..34bfe006fd5e4 100644 --- a/docs/pages/x/api/data-grid/data-grid-premium.json +++ b/docs/pages/x/api/data-grid/data-grid-premium.json @@ -263,6 +263,7 @@ "default": "GridColumnHeaderFilterIconButton", "type": { "name": "elementType" } }, + "ColumnHeaders": { "default": "DataGridColumnHeaders", "type": { "name": "elementType" } }, "ColumnMenu": { "default": "GridColumnMenu", "type": { "name": "elementType" } }, "ColumnMenuAggregationIcon": { "default": "GridFunctionsIcon", diff --git a/docs/pages/x/api/data-grid/data-grid-pro.json b/docs/pages/x/api/data-grid/data-grid-pro.json index bef02fced4916..8a9fc4c3f954b 100644 --- a/docs/pages/x/api/data-grid/data-grid-pro.json +++ b/docs/pages/x/api/data-grid/data-grid-pro.json @@ -238,6 +238,7 @@ "default": "GridColumnHeaderFilterIconButton", "type": { "name": "elementType" } }, + "ColumnHeaders": { "default": "DataGridColumnHeaders", "type": { "name": "elementType" } }, "ColumnMenu": { "default": "GridColumnMenu", "type": { "name": "elementType" } }, "ColumnMenuClearIcon": { "default": "GridClearIcon", "type": { "name": "elementType" } }, "ColumnMenuFilterIcon": { "default": "GridFilterAltIcon", "type": { "name": "elementType" } }, diff --git a/docs/pages/x/api/data-grid/data-grid.json b/docs/pages/x/api/data-grid/data-grid.json index fa208607e28bf..9373b9e544789 100644 --- a/docs/pages/x/api/data-grid/data-grid.json +++ b/docs/pages/x/api/data-grid/data-grid.json @@ -183,6 +183,7 @@ "default": "GridColumnHeaderFilterIconButton", "type": { "name": "elementType" } }, + "ColumnHeaders": { "default": "DataGridColumnHeaders", "type": { "name": "elementType" } }, "ColumnMenu": { "default": "GridColumnMenu", "type": { "name": "elementType" } }, "ColumnMenuClearIcon": { "default": "GridClearIcon", "type": { "name": "elementType" } }, "ColumnMenuFilterIcon": { "default": "GridFilterAltIcon", "type": { "name": "elementType" } }, diff --git a/docs/pages/x/react-data-grid/performance.js b/docs/pages/x/react-data-grid/performance.js new file mode 100644 index 0000000000000..c3600d16c9d56 --- /dev/null +++ b/docs/pages/x/react-data-grid/performance.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import * as pageProps from 'docsx/data/data-grid/performance/performance.md?@mui/markdown'; + +export default function Page() { + return ; +} diff --git a/docs/translations/api-docs/data-grid/data-grid-premium.json b/docs/translations/api-docs/data-grid/data-grid-premium.json index 93cf6903b8a33..acef575975fd6 100644 --- a/docs/translations/api-docs/data-grid/data-grid-premium.json +++ b/docs/translations/api-docs/data-grid/data-grid-premium.json @@ -698,6 +698,7 @@ "Cell": "Component rendered for each cell.", "ColumnFilteredIcon": "Icon displayed on the column header menu to show that a filter has been applied to the column.", "ColumnHeaderFilterIconButton": "Filter icon component rendered in each column header.", + "ColumnHeaders": "Component responsible for rendering the column headers.", "ColumnMenu": "Column menu component rendered by clicking on the 3 dots "kebab" icon in column headers.", "ColumnMenuAggregationIcon": "Icon displayed in column menu for aggregation", "ColumnMenuClearIcon": "Icon displayed in column menu for clearing values", diff --git a/docs/translations/api-docs/data-grid/data-grid-pro.json b/docs/translations/api-docs/data-grid/data-grid-pro.json index e399fead55a2d..70816ec7089d1 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pro.json +++ b/docs/translations/api-docs/data-grid/data-grid-pro.json @@ -685,6 +685,7 @@ "Cell": "Component rendered for each cell.", "ColumnFilteredIcon": "Icon displayed on the column header menu to show that a filter has been applied to the column.", "ColumnHeaderFilterIconButton": "Filter icon component rendered in each column header.", + "ColumnHeaders": "Component responsible for rendering the column headers.", "ColumnMenu": "Column menu component rendered by clicking on the 3 dots "kebab" icon in column headers.", "ColumnMenuClearIcon": "Icon displayed in column menu for clearing values", "ColumnMenuFilterIcon": "Icon displayed in column menu for filter", diff --git a/docs/translations/api-docs/data-grid/data-grid.json b/docs/translations/api-docs/data-grid/data-grid.json index a2f316a5c29f2..0ba90cb4be1f9 100644 --- a/docs/translations/api-docs/data-grid/data-grid.json +++ b/docs/translations/api-docs/data-grid/data-grid.json @@ -653,6 +653,7 @@ "Cell": "Component rendered for each cell.", "ColumnFilteredIcon": "Icon displayed on the column header menu to show that a filter has been applied to the column.", "ColumnHeaderFilterIconButton": "Filter icon component rendered in each column header.", + "ColumnHeaders": "Component responsible for rendering the column headers.", "ColumnMenu": "Column menu component rendered by clicking on the 3 dots "kebab" icon in column headers.", "ColumnMenuClearIcon": "Icon displayed in column menu for clearing values", "ColumnMenuFilterIcon": "Icon displayed in column menu for filter", diff --git a/packages/grid/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/grid/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx index 31a19d9f48d40..a2eff9ef588b3 100644 --- a/packages/grid/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx +++ b/packages/grid/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx @@ -9,11 +9,10 @@ import { GridRoot, GridContextProvider, GridValidRowModel, + useGridSelector, + gridPinnedColumnsSelector, } from '@mui/x-data-grid-pro'; -import { - DataGridProVirtualScroller, - DataGridProColumnHeaders, -} from '@mui/x-data-grid-pro/internals'; +import { DataGridProVirtualScroller } from '@mui/x-data-grid-pro/internals'; import { useDataGridPremiumComponent } from './useDataGridPremiumComponent'; import { DataGridPremiumProps } from '../models/dataGridPremiumProps'; import { useDataGridPremiumProps } from './useDataGridPremiumProps'; @@ -30,13 +29,15 @@ const DataGridPremiumRaw = React.forwardRef(function DataGridPremium diff --git a/packages/grid/x-data-grid-premium/src/index.ts b/packages/grid/x-data-grid-premium/src/index.ts index d134ceb28bd6d..74a9a8c898150 100644 --- a/packages/grid/x-data-grid-premium/src/index.ts +++ b/packages/grid/x-data-grid-premium/src/index.ts @@ -32,3 +32,5 @@ export { GRID_COLUMN_MENU_COMPONENTS, GRID_COLUMN_MENU_COMPONENTS_PROPS, } from './components/reexports'; + +export { DataGridProColumnHeaders as DataGridPremiumColumnHeaders } from '@mui/x-data-grid-pro'; diff --git a/packages/grid/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx b/packages/grid/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx index b2ef3012fde6a..1e99d40612079 100644 --- a/packages/grid/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx +++ b/packages/grid/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx @@ -9,13 +9,14 @@ import { GridRoot, GridContextProvider, GridValidRowModel, + useGridSelector, } from '@mui/x-data-grid'; import { useDataGridProComponent } from './useDataGridProComponent'; import { DataGridProProps } from '../models/dataGridProProps'; import { useDataGridProProps } from './useDataGridProProps'; import { DataGridProVirtualScroller } from '../components/DataGridProVirtualScroller'; -import { DataGridProColumnHeaders } from '../components/DataGridProColumnHeaders'; import { getReleaseInfo } from '../utils/releaseInfo'; +import { gridPinnedColumnsSelector } from '../hooks/features/columnPinning/gridColumnPinningSelector'; const releaseInfo = getReleaseInfo(); @@ -27,13 +28,15 @@ const DataGridProRaw = React.forwardRef(function DataGridPro diff --git a/packages/grid/x-data-grid-pro/src/components/DataGridProColumnHeaders.tsx b/packages/grid/x-data-grid-pro/src/components/DataGridProColumnHeaders.tsx index 189d81aa7424a..61b184f9d5380 100644 --- a/packages/grid/x-data-grid-pro/src/components/DataGridProColumnHeaders.tsx +++ b/packages/grid/x-data-grid-pro/src/components/DataGridProColumnHeaders.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import PropTypes from 'prop-types'; import { unstable_composeClasses as composeClasses, unstable_useEventCallback as useEventCallback, @@ -7,24 +8,19 @@ import { styled, alpha, useTheme } from '@mui/material/styles'; import { getDataGridUtilityClass, gridClasses, - useGridSelector, useGridApiEventHandler, - gridVisibleColumnFieldsSelector, GridColumnHeaderSeparatorSides, } from '@mui/x-data-grid'; import { GridColumnHeaders, GridColumnHeadersInner, useGridColumnHeaders, + UseGridColumnHeadersProps, } from '@mui/x-data-grid/internals'; import { useGridRootProps } from '../hooks/utils/useGridRootProps'; import { useGridApiContext } from '../hooks/utils/useGridApiContext'; import { DataGridProProcessedProps } from '../models/dataGridProProps'; -import { - gridPinnedColumnsSelector, - GridPinnedPosition, - GridPinnedColumns, -} from '../hooks/features/columnPinning'; +import { GridPinnedPosition, GridPinnedColumns } from '../hooks/features/columnPinning'; import { filterColumns } from './DataGridProVirtualScroller'; type OwnerState = { @@ -105,148 +101,253 @@ const GridColumnHeadersPinnedColumnHeaders = styled('div', { }), })); -interface DataGridProColumnHeadersProps extends React.HTMLAttributes { +GridColumnHeadersPinnedColumnHeaders.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "yarn proptypes" | + // ---------------------------------------------------------------------- + ownerState: PropTypes.shape({ + showCellVerticalBorder: PropTypes.bool.isRequired, + side: PropTypes.oneOf(['left', 'right']).isRequired, + }).isRequired, +} as any; + +interface DataGridProColumnHeadersProps + extends React.HTMLAttributes, + Omit { innerRef?: React.Ref; + pinnedColumns: GridPinnedColumns; } -export const DataGridProColumnHeaders = React.forwardRef< - HTMLDivElement, - DataGridProColumnHeadersProps ->(function DataGridProColumnHeaders(props, ref) { - const { style, className, innerRef, ...other } = props; - const rootProps = useGridRootProps(); - const apiRef = useGridApiContext(); - const visibleColumnFields = useGridSelector(apiRef, gridVisibleColumnFieldsSelector); - const [scrollbarSize, setScrollbarSize] = React.useState(0); - const theme = useTheme(); - - const handleContentSizeChange = useEventCallback(() => { - const rootDimensions = apiRef.current.getRootDimensions(); - if (!rootDimensions) { - return; - } - - const newScrollbarSize = rootDimensions.hasScrollY ? rootDimensions.scrollBarSize : 0; - if (scrollbarSize !== newScrollbarSize) { - setScrollbarSize(newScrollbarSize); - } - }); - - useGridApiEventHandler(apiRef, 'virtualScrollerContentSizeChange', handleContentSizeChange); - - const pinnedColumns = useGridSelector(apiRef, gridPinnedColumnsSelector); - const [leftPinnedColumns, rightPinnedColumns] = filterColumns( - pinnedColumns, - visibleColumnFields, - theme.direction === 'rtl', - ); - - const { - isDragging, - renderContext, - getRootProps, - getInnerProps, - getColumnHeaders, - getColumnGroupHeaders, - } = useGridColumnHeaders({ - innerRef, - minColumnIndex: leftPinnedColumns.length, - }); - - const ownerState = { - leftPinnedColumns, - rightPinnedColumns, - classes: rootProps.classes, - }; - const classes = useUtilityClasses(ownerState); - - const leftRenderContext = - renderContext && leftPinnedColumns.length - ? { - ...renderContext, - firstColumnIndex: 0, - lastColumnIndex: leftPinnedColumns.length, - } - : null; - - const rightRenderContext = - renderContext && rightPinnedColumns.length - ? { - ...renderContext, - firstColumnIndex: visibleColumnFields.length - rightPinnedColumns.length, - lastColumnIndex: visibleColumnFields.length, - } - : null; - - const innerProps = getInnerProps(); - - const pinnedColumnHeadersProps = { - role: innerProps.role, - }; +const DataGridProColumnHeaders = React.forwardRef( + function DataGridProColumnHeaders(props, ref) { + const { + style, + className, + innerRef, + visibleColumns, + sortColumnLookup, + filterColumnLookup, + columnPositions, + columnHeaderTabIndexState, + columnGroupHeaderTabIndexState, + columnHeaderFocus, + columnGroupHeaderFocus, + densityFactor, + headerGroupingMaxDepth, + columnMenuState, + columnVisibility, + columnGroupsHeaderStructure, + hasOtherElementInTabSequence, + pinnedColumns, + ...other + } = props; + const rootProps = useGridRootProps(); + const apiRef = useGridApiContext(); + const [scrollbarSize, setScrollbarSize] = React.useState(0); + const theme = useTheme(); - return ( - - {leftRenderContext && ( - - {getColumnGroupHeaders({ - renderContext: leftRenderContext, - minFirstColumn: leftRenderContext.firstColumnIndex, - maxLastColumn: leftRenderContext.lastColumnIndex, - })} - {getColumnHeaders( - { + const handleContentSizeChange = useEventCallback(() => { + const rootDimensions = apiRef.current.getRootDimensions(); + if (!rootDimensions) { + return; + } + + const newScrollbarSize = rootDimensions.hasScrollY ? rootDimensions.scrollBarSize : 0; + if (scrollbarSize !== newScrollbarSize) { + setScrollbarSize(newScrollbarSize); + } + }); + + useGridApiEventHandler(apiRef, 'virtualScrollerContentSizeChange', handleContentSizeChange); + + const visibleColumnFields = React.useMemo( + () => visibleColumns.map(({ field }) => field), + [visibleColumns], + ); + + const [leftPinnedColumns, rightPinnedColumns] = filterColumns( + pinnedColumns, + visibleColumnFields, + theme.direction === 'rtl', + ); + + const { + isDragging, + renderContext, + getRootProps, + getInnerProps, + getColumnHeaders, + getColumnGroupHeaders, + } = useGridColumnHeaders({ + innerRef, + visibleColumns, + sortColumnLookup, + filterColumnLookup, + columnPositions, + columnHeaderTabIndexState, + hasOtherElementInTabSequence, + columnGroupHeaderTabIndexState, + columnHeaderFocus, + columnGroupHeaderFocus, + densityFactor, + headerGroupingMaxDepth, + columnMenuState, + columnVisibility, + columnGroupsHeaderStructure, + minColumnIndex: leftPinnedColumns.length, + }); + + const ownerState = { + leftPinnedColumns, + rightPinnedColumns, + classes: rootProps.classes, + }; + const classes = useUtilityClasses(ownerState); + + const leftRenderContext = + renderContext && leftPinnedColumns.length + ? { + ...renderContext, + firstColumnIndex: 0, + lastColumnIndex: leftPinnedColumns.length, + } + : null; + + const rightRenderContext = + renderContext && rightPinnedColumns.length + ? { + ...renderContext, + firstColumnIndex: visibleColumns.length - rightPinnedColumns.length, + lastColumnIndex: visibleColumns.length, + } + : null; + + const innerProps = getInnerProps(); + + const pinnedColumnHeadersProps = { + role: innerProps.role, + }; + + return ( + + {leftRenderContext && ( + + {getColumnGroupHeaders({ renderContext: leftRenderContext, minFirstColumn: leftRenderContext.firstColumnIndex, maxLastColumn: leftRenderContext.lastColumnIndex, - }, - { disableReorder: true }, - )} - - )} - - {getColumnGroupHeaders({ - renderContext, - minFirstColumn: leftPinnedColumns.length, - maxLastColumn: visibleColumnFields.length - rightPinnedColumns.length, - })} - {getColumnHeaders({ - renderContext, - minFirstColumn: leftPinnedColumns.length, - maxLastColumn: visibleColumnFields.length - rightPinnedColumns.length, - })} - - {rightRenderContext && ( - + })} + {getColumnHeaders( + { + renderContext: leftRenderContext, + minFirstColumn: leftRenderContext.firstColumnIndex, + maxLastColumn: leftRenderContext.lastColumnIndex, + }, + { disableReorder: true }, + )} + + )} + + {getColumnGroupHeaders({ - renderContext: rightRenderContext, - minFirstColumn: rightRenderContext.firstColumnIndex, - maxLastColumn: rightRenderContext.lastColumnIndex, + renderContext, + minFirstColumn: leftPinnedColumns.length, + maxLastColumn: visibleColumns.length - rightPinnedColumns.length, + })} + {getColumnHeaders({ + renderContext, + minFirstColumn: leftPinnedColumns.length, + maxLastColumn: visibleColumns.length - rightPinnedColumns.length, })} - {getColumnHeaders( - { + + {rightRenderContext && ( + + {getColumnGroupHeaders({ renderContext: rightRenderContext, minFirstColumn: rightRenderContext.firstColumnIndex, maxLastColumn: rightRenderContext.lastColumnIndex, - }, - { disableReorder: true, separatorSide: GridColumnHeaderSeparatorSides.Left }, - )} - - )} - - ); -}); + })} + {getColumnHeaders( + { + renderContext: rightRenderContext, + minFirstColumn: rightRenderContext.firstColumnIndex, + maxLastColumn: rightRenderContext.lastColumnIndex, + }, + { disableReorder: true, separatorSide: GridColumnHeaderSeparatorSides.Left }, + )} + + )} + + ); + }, +); + +DataGridProColumnHeaders.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "yarn proptypes" | + // ---------------------------------------------------------------------- + columnGroupHeaderFocus: PropTypes.shape({ + depth: PropTypes.number.isRequired, + field: PropTypes.string.isRequired, + }), + columnGroupHeaderTabIndexState: PropTypes.shape({ + depth: PropTypes.number.isRequired, + field: PropTypes.string.isRequired, + }), + columnGroupsHeaderStructure: PropTypes.arrayOf( + PropTypes.arrayOf( + PropTypes.shape({ + columnFields: PropTypes.arrayOf(PropTypes.string).isRequired, + groupId: PropTypes.string, + }), + ), + ).isRequired, + columnHeaderFocus: PropTypes.shape({ + field: PropTypes.string.isRequired, + }), + columnHeaderTabIndexState: PropTypes.shape({ + field: PropTypes.string.isRequired, + }), + columnMenuState: PropTypes.shape({ + field: PropTypes.string, + open: PropTypes.bool.isRequired, + }).isRequired, + columnPositions: PropTypes.arrayOf(PropTypes.number).isRequired, + columnVisibility: PropTypes.object.isRequired, + densityFactor: PropTypes.number.isRequired, + filterColumnLookup: PropTypes.object.isRequired, + hasOtherElementInTabSequence: PropTypes.bool.isRequired, + headerGroupingMaxDepth: PropTypes.number.isRequired, + innerRef: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.shape({ + current: PropTypes.object, + }), + ]), + minColumnIndex: PropTypes.number, + pinnedColumns: PropTypes.shape({ + left: PropTypes.arrayOf(PropTypes.string), + right: PropTypes.arrayOf(PropTypes.string), + }).isRequired, + sortColumnLookup: PropTypes.object.isRequired, + visibleColumns: PropTypes.arrayOf(PropTypes.object).isRequired, +} as any; + +export { DataGridProColumnHeaders }; diff --git a/packages/grid/x-data-grid-pro/src/components/DataGridProVirtualScroller.tsx b/packages/grid/x-data-grid-pro/src/components/DataGridProVirtualScroller.tsx index b55ddf6283a26..18959da5b8666 100644 --- a/packages/grid/x-data-grid-pro/src/components/DataGridProVirtualScroller.tsx +++ b/packages/grid/x-data-grid-pro/src/components/DataGridProVirtualScroller.tsx @@ -233,13 +233,16 @@ const DataGridProVirtualScroller = React.forwardRef< [], ); - const getRowProps = (id: GridRowId) => { - if (!expandedRowIds.includes(id)) { - return null; - } - const height = detailPanelsHeights[id]; - return { style: { marginBottom: height } }; - }; + const getRowProps = React.useCallback( + (id: GridRowId) => { + if (!expandedRowIds.includes(id)) { + return null; + } + const height = detailPanelsHeights[id]; + return { style: { marginBottom: height } }; + }, + [detailPanelsHeights, expandedRowIds], + ); const pinnedColumns = useGridSelector(apiRef, gridPinnedColumnsSelector); const [leftPinnedColumns, rightPinnedColumns] = filterColumns( diff --git a/packages/grid/x-data-grid-pro/src/constants/dataGridProDefaultSlotsComponents.ts b/packages/grid/x-data-grid-pro/src/constants/dataGridProDefaultSlotsComponents.ts index 9f48894e56bad..04239bf74b20b 100644 --- a/packages/grid/x-data-grid-pro/src/constants/dataGridProDefaultSlotsComponents.ts +++ b/packages/grid/x-data-grid-pro/src/constants/dataGridProDefaultSlotsComponents.ts @@ -2,6 +2,7 @@ import { DATA_GRID_DEFAULT_SLOTS_COMPONENTS } from '@mui/x-data-grid/internals'; import { GridProSlotsComponent, GridProIconSlotsComponent } from '../models'; import { GridPushPinRightIcon, GridPushPinLeftIcon } from '../components'; import { GridProColumnMenu } from '../components/GridProColumnMenu'; +import { DataGridProColumnHeaders } from '../components/DataGridProColumnHeaders'; export const DEFAULT_GRID_PRO_ICON_SLOTS_COMPONENTS: GridProIconSlotsComponent = { ColumnMenuPinRightIcon: GridPushPinRightIcon, @@ -12,4 +13,5 @@ export const DATA_GRID_PRO_DEFAULT_SLOTS_COMPONENTS: GridProSlotsComponent = { ...DATA_GRID_DEFAULT_SLOTS_COMPONENTS, ...DEFAULT_GRID_PRO_ICON_SLOTS_COMPONENTS, ColumnMenu: GridProColumnMenu, + ColumnHeaders: DataGridProColumnHeaders, }; diff --git a/packages/grid/x-data-grid-pro/src/index.ts b/packages/grid/x-data-grid-pro/src/index.ts index 4e445d67ce86a..312ddaeb9fbed 100644 --- a/packages/grid/x-data-grid-pro/src/index.ts +++ b/packages/grid/x-data-grid-pro/src/index.ts @@ -31,3 +31,5 @@ export { GRID_COLUMN_MENU_COMPONENTS, GRID_COLUMN_MENU_COMPONENTS_PROPS, } from './components/reexports'; + +export { DataGridProColumnHeaders } from './components/DataGridProColumnHeaders'; diff --git a/packages/grid/x-data-grid/src/components/DataGridColumnHeaders.tsx b/packages/grid/x-data-grid/src/components/DataGridColumnHeaders.tsx index 0888cb7a3d165..ade7550d29335 100644 --- a/packages/grid/x-data-grid/src/components/DataGridColumnHeaders.tsx +++ b/packages/grid/x-data-grid/src/components/DataGridColumnHeaders.tsx @@ -1,20 +1,58 @@ import * as React from 'react'; -import { useGridColumnHeaders } from '../hooks/features/columnHeaders/useGridColumnHeaders'; +import PropTypes from 'prop-types'; +import { + useGridColumnHeaders, + UseGridColumnHeadersProps, +} from '../hooks/features/columnHeaders/useGridColumnHeaders'; import { GridScrollArea } from './GridScrollArea'; import { GridColumnHeaders } from './columnHeaders/GridColumnHeaders'; import { GridColumnHeadersInner } from './columnHeaders/GridColumnHeadersInner'; -interface DataGridColumnHeadersProps extends React.HTMLAttributes { +interface DataGridColumnHeadersProps + extends React.HTMLAttributes, + Omit { innerRef?: React.Ref; } -export const DataGridColumnHeaders = React.forwardRef( +const DataGridColumnHeaders = React.forwardRef( function GridColumnsHeader(props, ref) { - const { innerRef, className, ...other } = props; + const { + innerRef, + className, + visibleColumns, + sortColumnLookup, + filterColumnLookup, + columnPositions, + columnHeaderTabIndexState, + columnGroupHeaderTabIndexState, + columnHeaderFocus, + columnGroupHeaderFocus, + densityFactor, + headerGroupingMaxDepth, + columnMenuState, + columnVisibility, + columnGroupsHeaderStructure, + hasOtherElementInTabSequence, + ...other + } = props; const { isDragging, getRootProps, getInnerProps, getColumnHeaders, getColumnGroupHeaders } = useGridColumnHeaders({ innerRef, + visibleColumns, + sortColumnLookup, + filterColumnLookup, + columnPositions, + columnHeaderTabIndexState, + columnGroupHeaderTabIndexState, + columnHeaderFocus, + columnGroupHeaderFocus, + densityFactor, + headerGroupingMaxDepth, + columnMenuState, + columnVisibility, + columnGroupsHeaderStructure, + hasOtherElementInTabSequence, }); return ( @@ -29,3 +67,53 @@ export const DataGridColumnHeaders = React.forwardRef; @@ -112,10 +114,9 @@ const GridRow = React.forwardRef< containerWidth, firstColumnToRender, lastColumnToRender, - cellFocus, - cellTabIndex, - editRowsState, isLastVisible = false, + focusedCell, + tabbableCell, onClick, onDoubleClick, onMouseEnter, @@ -130,6 +131,7 @@ const GridRow = React.forwardRef< const sortModel = useGridSelector(apiRef, gridSortModelSelector); const treeDepth = useGridSelector(apiRef, gridRowMaximumTreeDepthSelector); const headerGroupingMaxDepth = useGridSelector(apiRef, gridColumnGroupsHeaderMaxDepthSelector); + const editRowsState = useGridSelector(apiRef, gridEditRowsStateSelector); const handleRef = useForkRef(ref, refProp); const ariaRowIndex = index + headerGroupingMaxDepth + 2; // 1 for the header row and 1 as it's 1-based @@ -256,6 +258,15 @@ const GridRow = React.forwardRef< [apiRef, onClick, publish, rowId], ); + const { + components, + componentsProps, + classes: rootClasses, + disableColumnReorder, + getCellClassName, + rowReordering, + } = rootProps as any; + const getCell = React.useCallback( ( column: GridStateColDef, @@ -275,8 +286,8 @@ const GridRow = React.forwardRef< }); const disableDragEvents = - (rootProps.disableColumnReorder && column.disableReorder) || - (!(rootProps as any).rowReordering && + (disableColumnReorder && column.disableReorder) || + (!rowReordering && !!sortModel.length && treeDepth > 1 && Object.keys(editRowsState).length > 0); @@ -298,7 +309,7 @@ const GridRow = React.forwardRef< content = column.renderCell({ ...cellParams, api: apiRef.current }); // TODO move to GridCell classNames.push( - clsx(gridClasses['cell--withRenderer'], rootProps.classes?.['cell--withRenderer']), + clsx(gridClasses['cell--withRenderer'], rootClasses?.['cell--withRenderer']), ); } @@ -316,24 +327,16 @@ const GridRow = React.forwardRef< content = column.renderEditCell(params); // TODO move to GridCell - classNames.push(clsx(gridClasses['cell--editing'], rootProps.classes?.['cell--editing'])); + classNames.push(clsx(gridClasses['cell--editing'], rootClasses?.['cell--editing'])); } - if (rootProps.getCellClassName) { + if (getCellClassName) { // TODO move to GridCell - classNames.push(rootProps.getCellClassName(cellParams)); + classNames.push(getCellClassName(cellParams)); } - const hasFocus = - cellFocus !== null && cellFocus.id === rowId && cellFocus.field === column.field; - - const tabIndex = - cellTabIndex !== null && - cellTabIndex.id === rowId && - cellTabIndex.field === column.field && - cellParams.cellMode === 'view' - ? 0 - : -1; + const hasFocus = focusedCell === column.field; + const tabIndex = tabbableCell === column.field ? 0 : -1; const isSelected = apiRef.current.unstable_applyPipeProcessors('isCellSelected', false, { id: rowId, @@ -341,7 +344,7 @@ const GridRow = React.forwardRef< }); return ( - {content} - + ); }, [ apiRef, - cellTabIndex, - editRowsState, - cellFocus, - rootProps, - rowHeight, rowId, - treeDepth, + disableColumnReorder, + rowReordering, sortModel.length, + treeDepth, + editRowsState, + getCellClassName, + focusedCell, + tabbableCell, + components, + rowHeight, + componentsProps?.cell, + rootClasses, ], ); @@ -512,11 +520,13 @@ GridRow.propTypes = { // | These PropTypes are generated from the TypeScript type definitions | // | To update them edit the TypeScript types and run "yarn proptypes" | // ---------------------------------------------------------------------- - cellFocus: PropTypes.object, - cellTabIndex: PropTypes.object, containerWidth: PropTypes.number.isRequired, - editRowsState: PropTypes.object.isRequired, firstColumnToRender: PropTypes.number.isRequired, + /** + * Determines which cell has focus. + * If `null`, no cell in this row has focus. + */ + focusedCell: PropTypes.string, /** * Index of the row in the whole sorted and filtered dataset. * If some rows above have expanded children, this index also take those children into account. @@ -530,6 +540,11 @@ GridRow.propTypes = { rowHeight: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number]).isRequired, rowId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, selected: PropTypes.bool.isRequired, + /** + * Determines which cell should be tabbable by having tabIndex=0. + * If `null`, no cell in this row is in the tab sequence. + */ + tabbableCell: PropTypes.string, visibleColumns: PropTypes.arrayOf(PropTypes.object).isRequired, } as any; diff --git a/packages/grid/x-data-grid/src/components/base/GridBody.tsx b/packages/grid/x-data-grid/src/components/base/GridBody.tsx index 67c2cb9470eb7..8d275df310cb3 100644 --- a/packages/grid/x-data-grid/src/components/base/GridBody.tsx +++ b/packages/grid/x-data-grid/src/components/base/GridBody.tsx @@ -1,32 +1,81 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { useGridPrivateApiContext } from '../../hooks/utils/useGridPrivateApiContext'; +import { useGridSelector } from '../../hooks/utils/useGridSelector'; import { ElementSize } from '../../models/elementSize'; import { GridMainContainer } from '../containers/GridMainContainer'; import { GridAutoSizer } from '../GridAutoSizer'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; +import { + gridColumnPositionsSelector, + gridColumnVisibilityModelSelector, + gridVisibleColumnDefinitionsSelector, +} from '../../hooks/features/columns/gridColumnsSelector'; +import { gridFilterActiveItemsLookupSelector } from '../../hooks/features/filter/gridFilterSelector'; +import { gridSortColumnLookupSelector } from '../../hooks/features/sorting/gridSortingSelector'; +import { + gridTabIndexColumnHeaderSelector, + gridTabIndexCellSelector, + gridFocusColumnHeaderSelector, + unstable_gridTabIndexColumnGroupHeaderSelector, + unstable_gridFocusColumnGroupHeaderSelector, +} from '../../hooks/features/focus/gridFocusStateSelector'; +import { gridDensityFactorSelector } from '../../hooks/features/density/densitySelector'; +import { + gridColumnGroupsHeaderMaxDepthSelector, + gridColumnGroupsHeaderStructureSelector, +} from '../../hooks/features/columnGrouping/gridColumnGroupsSelector'; +import { gridColumnMenuSelector } from '../../hooks/features/columnMenu/columnMenuSelector'; interface GridBodyProps { children?: React.ReactNode; + ColumnHeadersProps?: Record; VirtualScrollerComponent: React.JSXElementConstructor< React.HTMLAttributes & { ref: React.Ref; disableVirtualization: boolean; } >; - - ColumnHeadersComponent: React.JSXElementConstructor< - React.HTMLAttributes & { - ref: React.Ref; - innerRef: React.Ref; - } - >; } function GridBody(props: GridBodyProps) { - const { children, VirtualScrollerComponent, ColumnHeadersComponent } = props; + const { children, VirtualScrollerComponent, ColumnHeadersProps } = props; const apiRef = useGridPrivateApiContext(); const rootProps = useGridRootProps(); + + const visibleColumns = useGridSelector(apiRef, gridVisibleColumnDefinitionsSelector); + const filterColumnLookup = useGridSelector(apiRef, gridFilterActiveItemsLookupSelector); + const sortColumnLookup = useGridSelector(apiRef, gridSortColumnLookupSelector); + const columnPositions = useGridSelector(apiRef, gridColumnPositionsSelector); + const columnHeaderTabIndexState = useGridSelector(apiRef, gridTabIndexColumnHeaderSelector); + const cellTabIndexState = useGridSelector(apiRef, gridTabIndexCellSelector); + const columnGroupHeaderTabIndexState = useGridSelector( + apiRef, + unstable_gridTabIndexColumnGroupHeaderSelector, + ); + + const columnHeaderFocus = useGridSelector(apiRef, gridFocusColumnHeaderSelector); + const columnGroupHeaderFocus = useGridSelector( + apiRef, + unstable_gridFocusColumnGroupHeaderSelector, + ); + + const densityFactor = useGridSelector(apiRef, gridDensityFactorSelector); + const headerGroupingMaxDepth = useGridSelector(apiRef, gridColumnGroupsHeaderMaxDepthSelector); + + const columnMenuState = useGridSelector(apiRef, gridColumnMenuSelector); + const columnVisibility = useGridSelector(apiRef, gridColumnVisibilityModelSelector); + const columnGroupsHeaderStructure = useGridSelector( + apiRef, + gridColumnGroupsHeaderStructureSelector, + ); + + const hasOtherElementInTabSequence = !( + columnGroupHeaderTabIndexState === null && + columnHeaderTabIndexState === null && + cellTabIndexState === null + ); + const [isVirtualizationDisabled, setIsVirtualizationDisabled] = React.useState( rootProps.disableVirtualization, ); @@ -71,7 +120,25 @@ function GridBody(props: GridBodyProps) { return ( - + { let warnedOnce = false; -function GridCell(props: GridCellProps) { +const GridCell = React.forwardRef((props, ref) => { const { align, children, @@ -126,6 +127,7 @@ function GridCell(props: GridCellProps) { const valueToRender = formattedValue == null ? value : formattedValue; const cellRef = React.useRef(null); + const handleRef = useForkRef(ref, cellRef); const focusElementRef = React.useRef(null); const apiRef = useGridApiContext(); @@ -262,7 +264,7 @@ function GridCell(props: GridCellProps) { return (
); -} +}); GridCell.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']).isRequired, + align: PropTypes.oneOf(['center', 'left', 'right']), cellMode: PropTypes.oneOf(['edit', 'view']), children: PropTypes.node, className: PropTypes.string, - colIndex: PropTypes.number.isRequired, + colIndex: PropTypes.number, colSpan: PropTypes.number, disableDragEvents: PropTypes.bool, - field: PropTypes.string.isRequired, + field: PropTypes.string, formattedValue: PropTypes.any, hasFocus: PropTypes.bool, - height: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number]).isRequired, + height: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number]), isEditable: PropTypes.bool, isSelected: PropTypes.bool, onClick: PropTypes.func, @@ -312,11 +314,11 @@ GridCell.propTypes = { onKeyDown: PropTypes.func, onMouseDown: PropTypes.func, onMouseUp: PropTypes.func, - rowId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, + rowId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), showRightBorder: PropTypes.bool, - tabIndex: PropTypes.oneOf([-1, 0]).isRequired, + tabIndex: PropTypes.oneOf([-1, 0]), value: PropTypes.any, - width: PropTypes.number.isRequired, + width: PropTypes.number, } as any; export { GridCell }; diff --git a/packages/grid/x-data-grid/src/constants/defaultGridSlotsComponents.ts b/packages/grid/x-data-grid/src/constants/defaultGridSlotsComponents.ts index e491d706589af..17bd443ace271 100644 --- a/packages/grid/x-data-grid/src/constants/defaultGridSlotsComponents.ts +++ b/packages/grid/x-data-grid/src/constants/defaultGridSlotsComponents.ts @@ -45,6 +45,7 @@ import { GridViewColumnIcon, GridClearIcon, } from '../components'; +import { DataGridColumnHeaders } from '../components/DataGridColumnHeaders'; import { GridColumnMenu } from '../components/menu/columnMenu/GridColumnMenu'; import { GridColumnUnsortedIcon } from '../components/columnHeaders/GridColumnUnsortedIcon'; import { GridNoResultsOverlay } from '../components/GridNoResultsOverlay'; @@ -98,6 +99,7 @@ export const DATA_GRID_DEFAULT_SLOTS_COMPONENTS: GridSlotsComponent = { SkeletonCell: GridSkeletonCell, ColumnHeaderFilterIconButton: GridColumnHeaderFilterIconButton, ColumnMenu: GridColumnMenu, + ColumnHeaders: DataGridColumnHeaders, Footer: GridFooter, Toolbar: null, PreferencesPanel: GridPreferencesPanel, 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 80f25d15f913b..cc4a8655b3d5b 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 @@ -4,23 +4,6 @@ import { unstable_useForkRef as useForkRef } from '@mui/utils'; import { styled, useTheme } from '@mui/material/styles'; import { defaultMemoize } from 'reselect'; import { useGridPrivateApiContext } from '../../utils/useGridPrivateApiContext'; -import { useGridSelector } from '../../utils/useGridSelector'; -import { - gridVisibleColumnDefinitionsSelector, - gridColumnPositionsSelector, - gridColumnVisibilityModelSelector, -} from '../columns/gridColumnsSelector'; -import { - gridTabIndexColumnHeaderSelector, - gridTabIndexCellSelector, - gridFocusColumnHeaderSelector, - unstable_gridFocusColumnGroupHeaderSelector, - unstable_gridTabIndexColumnGroupHeaderSelector, -} from '../focus/gridFocusStateSelector'; -import { gridDensityFactorSelector } from '../density/densitySelector'; -import { gridFilterActiveItemsLookupSelector } from '../filter/gridFilterSelector'; -import { gridSortColumnLookupSelector } from '../sorting/gridSortingSelector'; -import { gridColumnMenuSelector } from '../columnMenu/columnMenuSelector'; import { useGridRootProps } from '../../utils/useGridRootProps'; import { GridRenderContext } from '../../../models/params/gridScrollParams'; import { useGridApiEventHandler } from '../../utils/useGridApiEventHandler'; @@ -31,10 +14,13 @@ import { useGridVisibleRows } from '../../utils/useGridVisibleRows'; import { getRenderableIndexes } from '../virtualization/useGridVirtualScroller'; import { GridColumnGroupHeader } from '../../../components/columnHeaders/GridColumnGroupHeader'; import { GridColumnGroup } from '../../../models/gridColumnGrouping'; -import { - gridColumnGroupsHeaderMaxDepthSelector, - gridColumnGroupsHeaderStructureSelector, -} from '../columnGrouping/gridColumnGroupsSelector'; +import { GridStateColDef } from '../../../models/colDef/gridColDef'; +import { GridSortColumnLookup } from '../sorting'; +import { GridFilterActiveItemsLookup } from '../filter'; +import { GridColumnGroupIdentifier, GridColumnIdentifier } from '../focus'; +import { GridColumnMenuState } from '../columnMenu'; +import { GridColumnVisibilityModel } from '../columns'; +import { GridGroupingStructure } from '../columnGrouping/gridColumnGroupsInterfaces'; const GridColumnHeaderRow = styled('div', { name: 'MuiDataGrid', @@ -54,9 +40,23 @@ interface HeaderInfo { description?: string; } -interface UseGridColumnHeadersProps { +export interface UseGridColumnHeadersProps { innerRef?: React.Ref; minColumnIndex?: number; + visibleColumns: GridStateColDef[]; + sortColumnLookup: GridSortColumnLookup; + filterColumnLookup: GridFilterActiveItemsLookup; + columnPositions: number[]; + columnHeaderTabIndexState: GridColumnIdentifier | null; + columnGroupHeaderTabIndexState: GridColumnGroupIdentifier | null; + columnHeaderFocus: GridColumnIdentifier | null; + columnGroupHeaderFocus: GridColumnGroupIdentifier | null; + densityFactor: number; + headerGroupingMaxDepth: number; + columnMenuState: GridColumnMenuState; + columnVisibility: GridColumnVisibilityModel; + columnGroupsHeaderStructure: GridGroupingStructure[][]; + hasOtherElementInTabSequence: boolean; } interface GetHeadersParams { @@ -70,36 +70,31 @@ function isUIEvent(event: any): event is React.UIEvent { } export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { - const { innerRef: innerRefProp, minColumnIndex = 0 } = props; + const { + innerRef: innerRefProp, + minColumnIndex = 0, + visibleColumns, + sortColumnLookup, + filterColumnLookup, + columnPositions, + columnHeaderTabIndexState, + columnGroupHeaderTabIndexState, + columnHeaderFocus, + columnGroupHeaderFocus, + densityFactor, + headerGroupingMaxDepth, + columnMenuState, + columnVisibility, + columnGroupsHeaderStructure, + hasOtherElementInTabSequence, + } = props; const theme = useTheme(); const [dragCol, setDragCol] = React.useState(''); const [resizeCol, setResizeCol] = React.useState(''); const apiRef = useGridPrivateApiContext(); - const visibleColumns = useGridSelector(apiRef, gridVisibleColumnDefinitionsSelector); - const columnPositions = useGridSelector(apiRef, gridColumnPositionsSelector); - const columnHeaderTabIndexState = useGridSelector(apiRef, gridTabIndexColumnHeaderSelector); - const cellTabIndexState = useGridSelector(apiRef, gridTabIndexCellSelector); - const columnGroupHeaderTabIndexState = useGridSelector( - apiRef, - unstable_gridTabIndexColumnGroupHeaderSelector, - ); - const columnHeaderFocus = useGridSelector(apiRef, gridFocusColumnHeaderSelector); - const columnGroupHeaderFocus = useGridSelector( - apiRef, - unstable_gridFocusColumnGroupHeaderSelector, - ); - const densityFactor = useGridSelector(apiRef, gridDensityFactorSelector); - const headerGroupingMaxDepth = useGridSelector(apiRef, gridColumnGroupsHeaderMaxDepthSelector); - const filterColumnLookup = useGridSelector(apiRef, gridFilterActiveItemsLookupSelector); - const sortColumnLookup = useGridSelector(apiRef, gridSortColumnLookupSelector); - const columnMenuState = useGridSelector(apiRef, gridColumnMenuSelector); - const columnVisibility = useGridSelector(apiRef, gridColumnVisibilityModelSelector); - const columnGroupsHeaderStructure = useGridSelector( - apiRef, - gridColumnGroupsHeaderStructureSelector, - ); + const rootProps = useGridRootProps(); const innerRef = React.useRef(null); const handleInnerRef = useForkRef(innerRefProp, innerRef); @@ -302,14 +297,9 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { const columnIndex = firstColumnToRender + i; const isFirstColumn = columnIndex === 0; - const hasTabbableElement = !( - columnGroupHeaderTabIndexState === null && - columnHeaderTabIndexState === null && - cellTabIndexState === null - ); const tabIndex = (columnHeaderTabIndexState !== null && columnHeaderTabIndexState.field === colDef.field) || - (isFirstColumn && !hasTabbableElement) + (isFirstColumn && !hasOtherElementInTabSequence) ? 0 : -1; const hasFocus = columnHeaderFocus !== null && columnHeaderFocus.field === colDef.field; diff --git a/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx b/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx index 7ad5032c2ff3e..684d4428179ff 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 @@ -14,17 +14,19 @@ import { gridColumnPositionsSelector, } from '../columns/gridColumnsSelector'; import { gridFocusCellSelector, gridTabIndexCellSelector } from '../focus/gridFocusStateSelector'; -import { gridEditRowsStateSelector } from '../editing/gridEditingSelectors'; import { useGridVisibleRows } from '../../utils/useGridVisibleRows'; import { GridEventListener } from '../../../models/events'; +import { GridSlotsComponentsProps } from '../../../models/gridSlotsComponentsProps'; import { useGridApiEventHandler } from '../../utils/useGridApiEventHandler'; import { clamp } from '../../../utils/utils'; import { GridRenderContext, GridRowEntry } from '../../../models'; import { selectedIdsLookupSelector } from '../rowSelection/gridRowSelectionSelector'; import { gridRowsMetaSelector } from '../rows/gridRowsMetaSelector'; import { GridRowId, GridRowModel } from '../../../models/gridRows'; +import { GridStateColDef } from '../../../models/colDef/gridColDef'; import { getFirstNonSpannedColumnToRender } from '../columns/gridColumnsUtils'; import { getMinimalContentHeight } from '../rows/gridRowsUtils'; +import { GridRowProps } from '../../../components/GridRow'; // Uses binary search to avoid looping through all possible positions export function binarySearch( @@ -124,7 +126,6 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { const cellFocus = useGridSelector(apiRef, gridFocusCellSelector); const cellTabIndex = useGridSelector(apiRef, gridTabIndexCellSelector); const rowsMeta = useGridSelector(apiRef, gridRowsMetaSelector); - const editRowsState = useGridSelector(apiRef, gridEditRowsStateSelector); const selectedRowsLookup = useGridSelector(apiRef, selectedIdsLookupSelector); const currentPage = useGridVisibleRows(apiRef, rootProps); const renderZoneRef = React.useRef(null); @@ -139,6 +140,15 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { }); const prevTotalWidth = React.useRef(columnsTotalWidth); + const rowStyleCache = React.useRef>({}); + const prevGetRowProps = React.useRef(); + const prevRootRowStyle = React.useRef(); + + const cachedRenderedColumns = React.useRef(); + const prevFirstColumnToRender = React.useRef(); + const prevLastColumnToRender = React.useRef(); + const prevVisibleColumns = React.useRef(); + const getNearestIndexToRender = React.useCallback( (offset: number) => { const lastMeasuredIndexRelativeToAllRows = apiRef.current.getLastMeasuredRowIndex(); @@ -506,7 +516,28 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { visibleRows: currentPage.rows, }); - const renderedColumns = visibleColumns.slice(firstColumnToRender, lastColumnToRender); + const invalidatesCachedRenderedColumns = + firstColumnToRender !== prevFirstColumnToRender.current || + lastColumnToRender !== prevLastColumnToRender.current || + visibleColumns !== prevVisibleColumns.current; + + if (invalidatesCachedRenderedColumns) { + cachedRenderedColumns.current = visibleColumns.slice(firstColumnToRender, lastColumnToRender); + prevFirstColumnToRender.current = firstColumnToRender; + prevLastColumnToRender.current = lastColumnToRender; + prevVisibleColumns.current = visibleColumns; + } + + const renderedColumns = cachedRenderedColumns.current; + + const { style: rootRowStyle, ...rootRowProps } = rootProps.componentsProps?.row || {}; + + const invalidatesCachedRowStyle = + prevGetRowProps.current !== getRowProps || prevRootRowStyle.current !== rootRowStyle; + + if (invalidatesCachedRowStyle) { + rowStyleCache.current = {}; + } const rows: JSX.Element[] = []; @@ -524,19 +555,33 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { isSelected = apiRef.current.isRowSelectable(id); } - const { style: rootRowStyle, ...rootRowProps } = rootProps.componentsProps?.row || {}; + const focusedCell = cellFocus !== null && cellFocus.id === id ? cellFocus.field : null; + + let tabbableCell: GridRowProps['tabbableCell'] = null; + if (cellTabIndex !== null && cellTabIndex.id === id) { + const cellParams = apiRef.current.getCellParams(id, cellTabIndex.field); + tabbableCell = cellParams.cellMode === 'view' ? cellTabIndex.field : null; + } + const { style: rowStyle, ...rowProps } = (typeof getRowProps === 'function' && getRowProps(id, model)) || {}; + if (!rowStyleCache.current[id]) { + const style = { + ...rowStyle, + ...rootRowStyle, + }; + rowStyleCache.current[id] = style; + } + rows.push( { position={position} {...rowProps} {...rootRowProps} - style={{ - ...rowStyle, - ...rootRowStyle, - }} + style={rowStyleCache.current[id]} />, ); } + prevGetRowProps.current = getRowProps; + prevRootRowStyle.current = rootRowStyle; + return rows; }; diff --git a/packages/grid/x-data-grid/src/index.ts b/packages/grid/x-data-grid/src/index.ts index a27b060807942..ffbd4e21644f0 100644 --- a/packages/grid/x-data-grid/src/index.ts +++ b/packages/grid/x-data-grid/src/index.ts @@ -20,6 +20,8 @@ export type { DataGridProps, GridExperimentalFeatures } from './models/props/Dat export type { GridToolbarExportProps } from './components/toolbar/GridToolbarExport'; export type { GridExportFormat, GridExportExtension } from './models/gridExport'; +export { DataGridColumnHeaders } from './components/DataGridColumnHeaders'; + /** * Reexportable components. */ diff --git a/packages/grid/x-data-grid/src/internals/index.ts b/packages/grid/x-data-grid/src/internals/index.ts index 7649c7b6eb452..07e9c407fa3aa 100644 --- a/packages/grid/x-data-grid/src/internals/index.ts +++ b/packages/grid/x-data-grid/src/internals/index.ts @@ -21,6 +21,7 @@ export { useGridInitialization } from '../hooks/core/useGridInitialization'; export { useGridClipboard } from '../hooks/features/clipboard/useGridClipboard'; export { useGridColumnHeaders } from '../hooks/features/columnHeaders/useGridColumnHeaders'; +export type { UseGridColumnHeadersProps } from '../hooks/features/columnHeaders/useGridColumnHeaders'; export { useGridColumnMenu, columnMenuStateInitializer, diff --git a/packages/grid/x-data-grid/src/models/gridSlotsComponent.ts b/packages/grid/x-data-grid/src/models/gridSlotsComponent.ts index 5888537b3abbe..fc518d4b033fc 100644 --- a/packages/grid/x-data-grid/src/models/gridSlotsComponent.ts +++ b/packages/grid/x-data-grid/src/models/gridSlotsComponent.ts @@ -71,6 +71,11 @@ export interface GridSlotsComponent extends GridIconSlotsComponent { * @default GridColumnMenu */ ColumnMenu: React.JSXElementConstructor; + /** + * Component responsible for rendering the column headers. + * @default DataGridColumnHeaders + */ + ColumnHeaders: React.JSXElementConstructor; /** * Footer component rendered at the bottom of the grid viewport. * @default GridFooter diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 9f12dc3f59501..32c994a676a2f 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -14,6 +14,7 @@ { "name": "DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES", "kind": "Variable" }, { "name": "DataGrid", "kind": "Function" }, { "name": "DataGridPremium", "kind": "Variable" }, + { "name": "DataGridPremiumColumnHeaders", "kind": "Variable" }, { "name": "DataGridPremiumProps", "kind": "Interface" }, { "name": "DataGridPro", "kind": "Function" }, { "name": "deDE", "kind": "Variable" }, @@ -97,7 +98,7 @@ { "name": "GridBody", "kind": "Function" }, { "name": "GridBooleanCell", "kind": "Variable" }, { "name": "GridCallbackDetails", "kind": "Interface" }, - { "name": "GridCell", "kind": "Function" }, + { "name": "GridCell", "kind": "Variable" }, { "name": "GridCellCheckboxForwardRef", "kind": "Variable" }, { "name": "GridCellCheckboxRenderer", "kind": "Variable" }, { "name": "GridCellClassFn", "kind": "TypeAlias" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index 4c4af4bd92786..330b92f119e2d 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -14,6 +14,7 @@ { "name": "DataGrid", "kind": "Function" }, { "name": "DataGridPremium", "kind": "Function" }, { "name": "DataGridPro", "kind": "Variable" }, + { "name": "DataGridProColumnHeaders", "kind": "Variable" }, { "name": "DataGridProProps", "kind": "Interface" }, { "name": "deDE", "kind": "Variable" }, { "name": "DEFAULT_GRID_COL_TYPE_KEY", "kind": "Variable" }, @@ -75,7 +76,7 @@ { "name": "GridBody", "kind": "Function" }, { "name": "GridBooleanCell", "kind": "Variable" }, { "name": "GridCallbackDetails", "kind": "Interface" }, - { "name": "GridCell", "kind": "Function" }, + { "name": "GridCell", "kind": "Variable" }, { "name": "GridCellCheckboxForwardRef", "kind": "Variable" }, { "name": "GridCellCheckboxRenderer", "kind": "Variable" }, { "name": "GridCellClassFn", "kind": "TypeAlias" }, diff --git a/scripts/x-data-grid.exports.json b/scripts/x-data-grid.exports.json index 1fa3dfac2a4fb..670bd6d0fa134 100644 --- a/scripts/x-data-grid.exports.json +++ b/scripts/x-data-grid.exports.json @@ -12,6 +12,7 @@ { "name": "daDK", "kind": "Variable" }, { "name": "DATA_GRID_PROPS_DEFAULT_VALUES", "kind": "Variable" }, { "name": "DataGrid", "kind": "Variable" }, + { "name": "DataGridColumnHeaders", "kind": "Variable" }, { "name": "DataGridProps", "kind": "TypeAlias" }, { "name": "deDE", "kind": "Variable" }, { "name": "DEFAULT_GRID_COL_TYPE_KEY", "kind": "Variable" }, @@ -68,7 +69,7 @@ { "name": "GridBody", "kind": "Function" }, { "name": "GridBooleanCell", "kind": "Variable" }, { "name": "GridCallbackDetails", "kind": "Interface" }, - { "name": "GridCell", "kind": "Function" }, + { "name": "GridCell", "kind": "Variable" }, { "name": "GridCellCheckboxForwardRef", "kind": "Variable" }, { "name": "GridCellCheckboxRenderer", "kind": "Variable" }, { "name": "GridCellClassFn", "kind": "TypeAlias" }, From 3065d0ff958ad51915facc4c5265319fd5b52aff Mon Sep 17 00:00:00 2001 From: Matheus Wichman Date: Mon, 20 Feb 2023 12:46:51 -0300 Subject: [PATCH 02/15] Memoize cell by default --- .../data/data-grid/performance/performance.md | 29 ++----------------- .../src/components/cell/GridCell.tsx | 4 ++- 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/docs/data/data-grid/performance/performance.md b/docs/data/data-grid/performance/performance.md index 06b1b07affc23..76357a03ecd94 100644 --- a/docs/data/data-grid/performance/performance.md +++ b/docs/data/data-grid/performance/performance.md @@ -15,19 +15,16 @@ To start using memoization, import the inner components, then pass their memoize ```tsx import { GridRow, - GridCell, DataGrid, // or DataGridPro, DataGridPremium DataGridColumnHeaders, // or DataGridProColumnHeaders, DataGridPremiumColumnHeaders } from '@mui/x-data-grid'; const MemoizedRow = React.memo(GridRow); -const MemoizedCell = React.memo(GridCell); const MemoizedColumnHeaders = React.memo(DataGridColumnHeaders); ; @@ -39,30 +36,8 @@ It also contains additional logic to highlight the components when they re-rende {{"demo": "GridWithReactMemo.js", "bg": "inline", "defaultCodeOpen": false}} :::warning -We do not ship the components above already wrapped with `React.memo` because if you have cells that display custom content whose source is not the received props, these cells may display outdated information. -For instance, 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 then the props passed to `GridCell` and `GridRow`, the row and column should not be memoized. -You can choose whether to memoize or not a component by passing a 2nd argument to `React.memo`: - -```tsx -function shallowCompare(prevProps, nextProps) { - const aKeys = Object.keys(prevProps); - const bKeys = Object.keys(nextProps); - - if (aKeys.length !== bKeys.length) { - return false; - } - - return aKeys.every( - (key) => nextProps.hasOwnProperty(key) && nextProps[key] === prevProps[key], - ); -} - -const MemoizedCell = React.memo(GridCell, (prevProps, nextProps) => { - // Prevent memoizing the cells from the "total" column - return nextProps.field !== 'total' && shallowCompare(prevProps, nextProps); -}); -``` - +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 then the props passed to `GridRow`, the row component should not be memoized. ::: ## 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 26954025accb3..bca8187b6e8c5 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridCell.tsx @@ -289,6 +289,8 @@ const GridCell = React.forwardRef((props, ref) => ); }); +const MemoizedCell = React.memo(GridCell); + GridCell.propTypes = { // ----------------------------- Warning -------------------------------- // | These PropTypes are generated from the TypeScript type definitions | @@ -321,4 +323,4 @@ GridCell.propTypes = { width: PropTypes.number, } as any; -export { GridCell }; +export { MemoizedCell as GridCell }; From b0fdc7e7130ebf065d7c9721b4d7955914aee6a0 Mon Sep 17 00:00:00 2001 From: Matheus Wichman Date: Mon, 20 Feb 2023 14:32:50 -0300 Subject: [PATCH 03/15] Update colors for dark mode --- .../data-grid/performance/GridWithReactMemo.js | 14 +++++++------- .../data-grid/performance/GridWithReactMemo.tsx | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/data/data-grid/performance/GridWithReactMemo.js b/docs/data/data-grid/performance/GridWithReactMemo.js index 92d4eb4216aac..78cffb597ca3c 100644 --- a/docs/data/data-grid/performance/GridWithReactMemo.js +++ b/docs/data/data-grid/performance/GridWithReactMemo.js @@ -1,5 +1,6 @@ import * as React from 'react'; import Box from '@mui/material/Box'; +import { teal } from '@mui/material/colors'; import { unstable_useForkRef as useForkRef } from '@mui/utils'; import { DataGridPro, @@ -50,13 +51,12 @@ export default function GridWithReactMemo() { sx={{ height: 400, width: '100%', - '& .updating': { - background: '#b2dfdb', - transition: (theme) => - theme.transitions.create('background', { - duration: theme.transitions.duration.standard, - }), - }, + '&& .updating': (theme) => ({ + background: teal[theme.palette.mode === 'dark' ? 900 : 100], + transition: theme.transitions.create('background', { + duration: theme.transitions.duration.standard, + }), + }), }} > - theme.transitions.create('background', { - duration: theme.transitions.duration.standard, - }), - }, + '&& .updating': (theme) => ({ + background: teal[theme.palette.mode === 'dark' ? 900 : 100], + transition: theme.transitions.create('background', { + duration: theme.transitions.duration.standard, + }), + }), }} > Date: Mon, 20 Feb 2023 18:44:10 -0300 Subject: [PATCH 04/15] Increase specificity --- docs/data/data-grid/performance/GridWithReactMemo.js | 2 +- docs/data/data-grid/performance/GridWithReactMemo.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/data/data-grid/performance/GridWithReactMemo.js b/docs/data/data-grid/performance/GridWithReactMemo.js index 78cffb597ca3c..864c22f7a80f3 100644 --- a/docs/data/data-grid/performance/GridWithReactMemo.js +++ b/docs/data/data-grid/performance/GridWithReactMemo.js @@ -51,7 +51,7 @@ export default function GridWithReactMemo() { sx={{ height: 400, width: '100%', - '&& .updating': (theme) => ({ + '&&& .updating': (theme) => ({ background: teal[theme.palette.mode === 'dark' ? 900 : 100], transition: theme.transitions.create('background', { duration: theme.transitions.duration.standard, diff --git a/docs/data/data-grid/performance/GridWithReactMemo.tsx b/docs/data/data-grid/performance/GridWithReactMemo.tsx index ff476531ce845..c320cdca90347 100644 --- a/docs/data/data-grid/performance/GridWithReactMemo.tsx +++ b/docs/data/data-grid/performance/GridWithReactMemo.tsx @@ -51,7 +51,7 @@ export default function GridWithReactMemo() { sx={{ height: 400, width: '100%', - '&& .updating': (theme) => ({ + '&&& .updating': (theme) => ({ background: teal[theme.palette.mode === 'dark' ? 900 : 100], transition: theme.transitions.create('background', { duration: theme.transitions.duration.standard, From 437879ef89286608cd07c0a3188736e933590917 Mon Sep 17 00:00:00 2001 From: Matheus Wichman Date: Fri, 24 Feb 2023 20:25:05 -0300 Subject: [PATCH 05/15] Rename column headeres component --- docs/data/data-grid/overview/DataGridProDemo.js | 4 ++-- docs/data/data-grid/overview/DataGridProDemo.tsx | 4 ++-- .../data-grid/performance/GridWithReactMemo.js | 8 ++------ .../data-grid/performance/GridWithReactMemo.tsx | 8 ++------ docs/data/data-grid/performance/performance.md | 4 ++-- packages/grid/x-data-grid-premium/src/index.ts | 4 ++-- ...roColumnHeaders.tsx => GridColumnHeaders.tsx} | 14 +++++++------- .../dataGridProDefaultSlotsComponents.ts | 4 ++-- packages/grid/x-data-grid-pro/src/index.ts | 2 +- .../grid/x-data-grid-pro/src/internals/index.ts | 2 +- .../grid/x-data-grid/src/DataGrid/DataGrid.tsx | 4 ++-- ...idColumnHeaders.tsx => GridColumnHeaders.tsx} | 16 ++++++++-------- ...lumnHeaders.tsx => GridBaseColumnHeaders.tsx} | 4 ++-- .../src/constants/defaultGridSlotsComponents.ts | 4 ++-- packages/grid/x-data-grid/src/index.ts | 2 +- packages/grid/x-data-grid/src/internals/index.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 +- 19 files changed, 42 insertions(+), 50 deletions(-) rename packages/grid/x-data-grid-pro/src/components/{DataGridProColumnHeaders.tsx => GridColumnHeaders.tsx} (97%) rename packages/grid/x-data-grid/src/components/{DataGridColumnHeaders.tsx => GridColumnHeaders.tsx} (89%) rename packages/grid/x-data-grid/src/components/columnHeaders/{GridColumnHeaders.tsx => GridBaseColumnHeaders.tsx} (89%) diff --git a/docs/data/data-grid/overview/DataGridProDemo.js b/docs/data/data-grid/overview/DataGridProDemo.js index ee79a1a6c6267..a1da4fde507b9 100644 --- a/docs/data/data-grid/overview/DataGridProDemo.js +++ b/docs/data/data-grid/overview/DataGridProDemo.js @@ -4,7 +4,7 @@ import { DataGridPro, GridRow, GridCell, - DataGridProColumnHeaders, + GridColumnHeaders, } from '@mui/x-data-grid-pro'; import { useDemoData } from '@mui/x-data-grid-generator'; @@ -12,7 +12,7 @@ const MemoizedRow = React.memo(GridRow); const MemoizedCell = React.memo(GridCell); -const MemoizedColumnHeaders = React.memo(DataGridProColumnHeaders); +const MemoizedColumnHeaders = React.memo(GridColumnHeaders); export default function DataGridProDemo() { const { data } = useDemoData({ diff --git a/docs/data/data-grid/overview/DataGridProDemo.tsx b/docs/data/data-grid/overview/DataGridProDemo.tsx index ee79a1a6c6267..a1da4fde507b9 100644 --- a/docs/data/data-grid/overview/DataGridProDemo.tsx +++ b/docs/data/data-grid/overview/DataGridProDemo.tsx @@ -4,7 +4,7 @@ import { DataGridPro, GridRow, GridCell, - DataGridProColumnHeaders, + GridColumnHeaders, } from '@mui/x-data-grid-pro'; import { useDemoData } from '@mui/x-data-grid-generator'; @@ -12,7 +12,7 @@ const MemoizedRow = React.memo(GridRow); const MemoizedCell = React.memo(GridCell); -const MemoizedColumnHeaders = React.memo(DataGridProColumnHeaders); +const MemoizedColumnHeaders = React.memo(GridColumnHeaders); export default function DataGridProDemo() { const { data } = useDemoData({ diff --git a/docs/data/data-grid/performance/GridWithReactMemo.js b/docs/data/data-grid/performance/GridWithReactMemo.js index 864c22f7a80f3..a99fbe9953eed 100644 --- a/docs/data/data-grid/performance/GridWithReactMemo.js +++ b/docs/data/data-grid/performance/GridWithReactMemo.js @@ -2,11 +2,7 @@ import * as React from 'react'; import Box from '@mui/material/Box'; import { teal } from '@mui/material/colors'; import { unstable_useForkRef as useForkRef } from '@mui/utils'; -import { - DataGridPro, - GridRow, - DataGridProColumnHeaders, -} from '@mui/x-data-grid-pro'; +import { DataGridPro, GridRow, GridColumnHeaders } from '@mui/x-data-grid-pro'; import { useDemoData } from '@mui/x-data-grid-generator'; const TraceUpdates = React.forwardRef((props, ref) => { @@ -32,7 +28,7 @@ const RowWithTracer = React.forwardRef((props, ref) => { }); const ColumnHeadersWithTracer = React.forwardRef((props, ref) => { - return ; + return ; }); const MemoizedRow = React.memo(RowWithTracer); diff --git a/docs/data/data-grid/performance/GridWithReactMemo.tsx b/docs/data/data-grid/performance/GridWithReactMemo.tsx index c320cdca90347..f933dd3749024 100644 --- a/docs/data/data-grid/performance/GridWithReactMemo.tsx +++ b/docs/data/data-grid/performance/GridWithReactMemo.tsx @@ -2,11 +2,7 @@ import * as React from 'react'; import Box from '@mui/material/Box'; import { teal } from '@mui/material/colors'; import { unstable_useForkRef as useForkRef } from '@mui/utils'; -import { - DataGridPro, - GridRow, - DataGridProColumnHeaders, -} from '@mui/x-data-grid-pro'; +import { DataGridPro, GridRow, GridColumnHeaders } from '@mui/x-data-grid-pro'; import { useDemoData } from '@mui/x-data-grid-generator'; const TraceUpdates = React.forwardRef((props, ref) => { @@ -32,7 +28,7 @@ const RowWithTracer = React.forwardRef((props, ref) => { }); const ColumnHeadersWithTracer = React.forwardRef((props, ref) => { - return ; + return ; }); const MemoizedRow = React.memo(RowWithTracer); diff --git a/docs/data/data-grid/performance/performance.md b/docs/data/data-grid/performance/performance.md index 9b50cd54331ac..37f3689d72faa 100644 --- a/docs/data/data-grid/performance/performance.md +++ b/docs/data/data-grid/performance/performance.md @@ -15,12 +15,12 @@ To start using memoization, import the inner components, then pass their memoize ```tsx import { GridRow, + GridColumnHeaders, DataGrid, // or DataGridPro, DataGridPremium - DataGridColumnHeaders, // or DataGridProColumnHeaders, DataGridPremiumColumnHeaders } from '@mui/x-data-grid'; const MemoizedRow = React.memo(GridRow); -const MemoizedColumnHeaders = React.memo(DataGridColumnHeaders); +const MemoizedColumnHeaders = React.memo(GridColumnHeaders); ( - function DataGridProColumnHeaders(props, ref) { +const GridColumnHeaders = React.forwardRef( + function GridColumnHeaders(props, ref) { const { style, className, @@ -229,7 +229,7 @@ const DataGridProColumnHeaders = React.forwardRef + {leftRenderContext && ( )} - + ); }, ); -DataGridProColumnHeaders.propTypes = { +GridColumnHeaders.propTypes = { // ----------------------------- Warning -------------------------------- // | These PropTypes are generated from the TypeScript type definitions | // | To update them edit the TypeScript types and run "yarn proptypes" | @@ -351,4 +351,4 @@ DataGridProColumnHeaders.propTypes = { visibleColumns: PropTypes.arrayOf(PropTypes.object).isRequired, } as any; -export { DataGridProColumnHeaders }; +export { GridColumnHeaders }; diff --git a/packages/grid/x-data-grid-pro/src/constants/dataGridProDefaultSlotsComponents.ts b/packages/grid/x-data-grid-pro/src/constants/dataGridProDefaultSlotsComponents.ts index 04239bf74b20b..322635ffffaa8 100644 --- a/packages/grid/x-data-grid-pro/src/constants/dataGridProDefaultSlotsComponents.ts +++ b/packages/grid/x-data-grid-pro/src/constants/dataGridProDefaultSlotsComponents.ts @@ -2,7 +2,7 @@ import { DATA_GRID_DEFAULT_SLOTS_COMPONENTS } from '@mui/x-data-grid/internals'; import { GridProSlotsComponent, GridProIconSlotsComponent } from '../models'; import { GridPushPinRightIcon, GridPushPinLeftIcon } from '../components'; import { GridProColumnMenu } from '../components/GridProColumnMenu'; -import { DataGridProColumnHeaders } from '../components/DataGridProColumnHeaders'; +import { GridColumnHeaders } from '../components/GridColumnHeaders'; export const DEFAULT_GRID_PRO_ICON_SLOTS_COMPONENTS: GridProIconSlotsComponent = { ColumnMenuPinRightIcon: GridPushPinRightIcon, @@ -13,5 +13,5 @@ export const DATA_GRID_PRO_DEFAULT_SLOTS_COMPONENTS: GridProSlotsComponent = { ...DATA_GRID_DEFAULT_SLOTS_COMPONENTS, ...DEFAULT_GRID_PRO_ICON_SLOTS_COMPONENTS, ColumnMenu: GridProColumnMenu, - ColumnHeaders: DataGridProColumnHeaders, + ColumnHeaders: GridColumnHeaders, }; diff --git a/packages/grid/x-data-grid-pro/src/index.ts b/packages/grid/x-data-grid-pro/src/index.ts index 312ddaeb9fbed..851dd8d5ebcfb 100644 --- a/packages/grid/x-data-grid-pro/src/index.ts +++ b/packages/grid/x-data-grid-pro/src/index.ts @@ -32,4 +32,4 @@ export { GRID_COLUMN_MENU_COMPONENTS_PROPS, } from './components/reexports'; -export { DataGridProColumnHeaders } from './components/DataGridProColumnHeaders'; +export { GridColumnHeaders } from './components/GridColumnHeaders'; diff --git a/packages/grid/x-data-grid-pro/src/internals/index.ts b/packages/grid/x-data-grid-pro/src/internals/index.ts index 1a4ff446992cd..7c62bd5352239 100644 --- a/packages/grid/x-data-grid-pro/src/internals/index.ts +++ b/packages/grid/x-data-grid-pro/src/internals/index.ts @@ -1,7 +1,7 @@ export * from '@mui/x-data-grid/internals'; export { DataGridProVirtualScroller } from '../components/DataGridProVirtualScroller'; -export { DataGridProColumnHeaders } from '../components/DataGridProColumnHeaders'; +export { GridColumnHeaders } from '../components/GridColumnHeaders'; export { DATA_GRID_PRO_DEFAULT_SLOTS_COMPONENTS } from '../constants/dataGridProDefaultSlotsComponents'; export { diff --git a/packages/grid/x-data-grid/src/DataGrid/DataGrid.tsx b/packages/grid/x-data-grid/src/DataGrid/DataGrid.tsx index 5d1a40faed9c4..505b4af6f4f03 100644 --- a/packages/grid/x-data-grid/src/DataGrid/DataGrid.tsx +++ b/packages/grid/x-data-grid/src/DataGrid/DataGrid.tsx @@ -7,7 +7,7 @@ import { GridContextProvider } from '../context/GridContextProvider'; import { useDataGridComponent } from './useDataGridComponent'; import { useDataGridProps } from './useDataGridProps'; import { DataGridVirtualScroller } from '../components/DataGridVirtualScroller'; -import { DataGridColumnHeaders } from '../components/DataGridColumnHeaders'; +import { GridColumnHeaders } from '../components/GridColumnHeaders'; import { GridValidRowModel } from '../models/gridRows'; const DataGridRaw = React.forwardRef(function DataGrid( @@ -22,7 +22,7 @@ const DataGridRaw = React.forwardRef(function DataGrid diff --git a/packages/grid/x-data-grid/src/components/DataGridColumnHeaders.tsx b/packages/grid/x-data-grid/src/components/GridColumnHeaders.tsx similarity index 89% rename from packages/grid/x-data-grid/src/components/DataGridColumnHeaders.tsx rename to packages/grid/x-data-grid/src/components/GridColumnHeaders.tsx index ade7550d29335..c2df1d6c2b83f 100644 --- a/packages/grid/x-data-grid/src/components/DataGridColumnHeaders.tsx +++ b/packages/grid/x-data-grid/src/components/GridColumnHeaders.tsx @@ -5,17 +5,17 @@ import { UseGridColumnHeadersProps, } from '../hooks/features/columnHeaders/useGridColumnHeaders'; import { GridScrollArea } from './GridScrollArea'; -import { GridColumnHeaders } from './columnHeaders/GridColumnHeaders'; +import { GridBaseColumnHeaders } from './columnHeaders/GridBaseColumnHeaders'; import { GridColumnHeadersInner } from './columnHeaders/GridColumnHeadersInner'; -interface DataGridColumnHeadersProps +interface GridColumnHeadersProps extends React.HTMLAttributes, Omit { innerRef?: React.Ref; } -const DataGridColumnHeaders = React.forwardRef( - function GridColumnsHeader(props, ref) { +const GridColumnHeaders = React.forwardRef( + function GridColumnsHeaders(props, ref) { const { innerRef, className, @@ -56,19 +56,19 @@ const DataGridColumnHeaders = React.forwardRef + {getColumnGroupHeaders()} {getColumnHeaders()} - + ); }, ); -DataGridColumnHeaders.propTypes = { +GridColumnHeaders.propTypes = { // ----------------------------- Warning -------------------------------- // | These PropTypes are generated from the TypeScript type definitions | // | To update them edit the TypeScript types and run "yarn proptypes" | @@ -116,4 +116,4 @@ DataGridColumnHeaders.propTypes = { visibleColumns: PropTypes.arrayOf(PropTypes.object).isRequired, } as any; -export { DataGridColumnHeaders }; +export { GridColumnHeaders }; diff --git a/packages/grid/x-data-grid/src/components/columnHeaders/GridColumnHeaders.tsx b/packages/grid/x-data-grid/src/components/columnHeaders/GridBaseColumnHeaders.tsx similarity index 89% rename from packages/grid/x-data-grid/src/components/columnHeaders/GridColumnHeaders.tsx rename to packages/grid/x-data-grid/src/components/columnHeaders/GridBaseColumnHeaders.tsx index 40d2de736e755..105fd7c8f3447 100644 --- a/packages/grid/x-data-grid/src/components/columnHeaders/GridColumnHeaders.tsx +++ b/packages/grid/x-data-grid/src/components/columnHeaders/GridBaseColumnHeaders.tsx @@ -35,11 +35,11 @@ const GridColumnHeadersRoot = styled('div', { }; }); -interface GridColumnHeadersProps extends React.HTMLAttributes { +interface GridBaseColumnHeadersProps extends React.HTMLAttributes { sx?: SxProps; } -export const GridColumnHeaders = React.forwardRef( +export const GridBaseColumnHeaders = React.forwardRef( function GridColumnHeaders(props, ref) { const { className, ...other } = props; const rootProps = useGridRootProps(); diff --git a/packages/grid/x-data-grid/src/constants/defaultGridSlotsComponents.ts b/packages/grid/x-data-grid/src/constants/defaultGridSlotsComponents.ts index 17bd443ace271..c8f46eb672423 100644 --- a/packages/grid/x-data-grid/src/constants/defaultGridSlotsComponents.ts +++ b/packages/grid/x-data-grid/src/constants/defaultGridSlotsComponents.ts @@ -45,7 +45,7 @@ import { GridViewColumnIcon, GridClearIcon, } from '../components'; -import { DataGridColumnHeaders } from '../components/DataGridColumnHeaders'; +import { GridColumnHeaders } from '../components/GridColumnHeaders'; import { GridColumnMenu } from '../components/menu/columnMenu/GridColumnMenu'; import { GridColumnUnsortedIcon } from '../components/columnHeaders/GridColumnUnsortedIcon'; import { GridNoResultsOverlay } from '../components/GridNoResultsOverlay'; @@ -99,7 +99,7 @@ export const DATA_GRID_DEFAULT_SLOTS_COMPONENTS: GridSlotsComponent = { SkeletonCell: GridSkeletonCell, ColumnHeaderFilterIconButton: GridColumnHeaderFilterIconButton, ColumnMenu: GridColumnMenu, - ColumnHeaders: DataGridColumnHeaders, + ColumnHeaders: GridColumnHeaders, Footer: GridFooter, Toolbar: null, PreferencesPanel: GridPreferencesPanel, diff --git a/packages/grid/x-data-grid/src/index.ts b/packages/grid/x-data-grid/src/index.ts index ffbd4e21644f0..c1f297c323d23 100644 --- a/packages/grid/x-data-grid/src/index.ts +++ b/packages/grid/x-data-grid/src/index.ts @@ -20,7 +20,7 @@ export type { DataGridProps, GridExperimentalFeatures } from './models/props/Dat export type { GridToolbarExportProps } from './components/toolbar/GridToolbarExport'; export type { GridExportFormat, GridExportExtension } from './models/gridExport'; -export { DataGridColumnHeaders } from './components/DataGridColumnHeaders'; +export { GridColumnHeaders } from './components/GridColumnHeaders'; /** * Reexportable components. diff --git a/packages/grid/x-data-grid/src/internals/index.ts b/packages/grid/x-data-grid/src/internals/index.ts index e0c46190a3eec..201ac99215d23 100644 --- a/packages/grid/x-data-grid/src/internals/index.ts +++ b/packages/grid/x-data-grid/src/internals/index.ts @@ -6,7 +6,7 @@ export type { export { GridVirtualScroller } from '../components/virtualization/GridVirtualScroller'; export { GridVirtualScrollerContent } from '../components/virtualization/GridVirtualScrollerContent'; export { GridVirtualScrollerRenderZone } from '../components/virtualization/GridVirtualScrollerRenderZone'; -export { GridColumnHeaders } from '../components/columnHeaders/GridColumnHeaders'; +export { GridBaseColumnHeaders } from '../components/columnHeaders/GridBaseColumnHeaders'; export { GridColumnHeadersInner } from '../components/columnHeaders/GridColumnHeadersInner'; export { DATA_GRID_DEFAULT_SLOTS_COMPONENTS } from '../constants/defaultGridSlotsComponents'; diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 7c63bed768845..3021cbb4d159e 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -14,7 +14,6 @@ { "name": "DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES", "kind": "Variable" }, { "name": "DataGrid", "kind": "Function" }, { "name": "DataGridPremium", "kind": "Variable" }, - { "name": "DataGridPremiumColumnHeaders", "kind": "Variable" }, { "name": "DataGridPremiumProps", "kind": "Interface" }, { "name": "DataGridPro", "kind": "Function" }, { "name": "deDE", "kind": "Variable" }, @@ -153,6 +152,7 @@ { "name": "GridColumnHeaderMenu", "kind": "Function" }, { "name": "GridColumnHeaderMenuProps", "kind": "Interface" }, { "name": "GridColumnHeaderParams", "kind": "Interface" }, + { "name": "GridColumnHeaders", "kind": "Variable" }, { "name": "GridColumnHeaderSeparator", "kind": "Variable" }, { "name": "GridColumnHeaderSeparatorProps", "kind": "Interface" }, { "name": "GridColumnHeaderSeparatorSides", "kind": "Enum" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index 6ef314f399c71..d21bb1981b130 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -14,7 +14,6 @@ { "name": "DataGrid", "kind": "Function" }, { "name": "DataGridPremium", "kind": "Function" }, { "name": "DataGridPro", "kind": "Variable" }, - { "name": "DataGridProColumnHeaders", "kind": "Variable" }, { "name": "DataGridProProps", "kind": "Interface" }, { "name": "deDE", "kind": "Variable" }, { "name": "DEFAULT_GRID_COL_TYPE_KEY", "kind": "Variable" }, @@ -129,6 +128,7 @@ { "name": "GridColumnHeaderMenu", "kind": "Function" }, { "name": "GridColumnHeaderMenuProps", "kind": "Interface" }, { "name": "GridColumnHeaderParams", "kind": "Interface" }, + { "name": "GridColumnHeaders", "kind": "Variable" }, { "name": "GridColumnHeaderSeparator", "kind": "Variable" }, { "name": "GridColumnHeaderSeparatorProps", "kind": "Interface" }, { "name": "GridColumnHeaderSeparatorSides", "kind": "Enum" }, diff --git a/scripts/x-data-grid.exports.json b/scripts/x-data-grid.exports.json index 1e1614708e2f4..9ac77dc79048f 100644 --- a/scripts/x-data-grid.exports.json +++ b/scripts/x-data-grid.exports.json @@ -12,7 +12,6 @@ { "name": "daDK", "kind": "Variable" }, { "name": "DATA_GRID_PROPS_DEFAULT_VALUES", "kind": "Variable" }, { "name": "DataGrid", "kind": "Variable" }, - { "name": "DataGridColumnHeaders", "kind": "Variable" }, { "name": "DataGridProps", "kind": "TypeAlias" }, { "name": "deDE", "kind": "Variable" }, { "name": "DEFAULT_GRID_COL_TYPE_KEY", "kind": "Variable" }, @@ -122,6 +121,7 @@ { "name": "GridColumnHeaderMenu", "kind": "Function" }, { "name": "GridColumnHeaderMenuProps", "kind": "Interface" }, { "name": "GridColumnHeaderParams", "kind": "Interface" }, + { "name": "GridColumnHeaders", "kind": "Variable" }, { "name": "GridColumnHeaderSeparator", "kind": "Variable" }, { "name": "GridColumnHeaderSeparatorProps", "kind": "Interface" }, { "name": "GridColumnHeaderSeparatorSides", "kind": "Enum" }, From 02acd1f4a7fa1e01b2b83310b2ae38f82edffe41 Mon Sep 17 00:00:00 2001 From: Matheus Wichman Date: Fri, 24 Feb 2023 20:27:37 -0300 Subject: [PATCH 06/15] Remove cell memoization --- docs/data/data-grid/overview/DataGridProDemo.js | 10 +--------- docs/data/data-grid/overview/DataGridProDemo.tsx | 10 +--------- .../data-grid/overview/DataGridProDemo.tsx.preview | 1 - 3 files changed, 2 insertions(+), 19 deletions(-) diff --git a/docs/data/data-grid/overview/DataGridProDemo.js b/docs/data/data-grid/overview/DataGridProDemo.js index a1da4fde507b9..3e0a22b660475 100644 --- a/docs/data/data-grid/overview/DataGridProDemo.js +++ b/docs/data/data-grid/overview/DataGridProDemo.js @@ -1,17 +1,10 @@ import * as React from 'react'; import Box from '@mui/material/Box'; -import { - DataGridPro, - GridRow, - GridCell, - GridColumnHeaders, -} from '@mui/x-data-grid-pro'; +import { DataGridPro, GridRow, GridColumnHeaders } from '@mui/x-data-grid-pro'; import { useDemoData } from '@mui/x-data-grid-generator'; const MemoizedRow = React.memo(GridRow); -const MemoizedCell = React.memo(GridCell); - const MemoizedColumnHeaders = React.memo(GridColumnHeaders); export default function DataGridProDemo() { @@ -31,7 +24,6 @@ export default function DataGridProDemo() { disableRowSelectionOnClick components={{ Row: MemoizedRow, - Cell: MemoizedCell, ColumnHeaders: MemoizedColumnHeaders, }} /> diff --git a/docs/data/data-grid/overview/DataGridProDemo.tsx b/docs/data/data-grid/overview/DataGridProDemo.tsx index a1da4fde507b9..3e0a22b660475 100644 --- a/docs/data/data-grid/overview/DataGridProDemo.tsx +++ b/docs/data/data-grid/overview/DataGridProDemo.tsx @@ -1,17 +1,10 @@ import * as React from 'react'; import Box from '@mui/material/Box'; -import { - DataGridPro, - GridRow, - GridCell, - GridColumnHeaders, -} from '@mui/x-data-grid-pro'; +import { DataGridPro, GridRow, GridColumnHeaders } from '@mui/x-data-grid-pro'; import { useDemoData } from '@mui/x-data-grid-generator'; const MemoizedRow = React.memo(GridRow); -const MemoizedCell = React.memo(GridCell); - const MemoizedColumnHeaders = React.memo(GridColumnHeaders); export default function DataGridProDemo() { @@ -31,7 +24,6 @@ export default function DataGridProDemo() { disableRowSelectionOnClick components={{ Row: MemoizedRow, - Cell: MemoizedCell, ColumnHeaders: MemoizedColumnHeaders, }} /> diff --git a/docs/data/data-grid/overview/DataGridProDemo.tsx.preview b/docs/data/data-grid/overview/DataGridProDemo.tsx.preview index 8eb2d740cf670..272b35f8a747b 100644 --- a/docs/data/data-grid/overview/DataGridProDemo.tsx.preview +++ b/docs/data/data-grid/overview/DataGridProDemo.tsx.preview @@ -6,7 +6,6 @@ disableRowSelectionOnClick components={{ Row: MemoizedRow, - Cell: MemoizedCell, ColumnHeaders: MemoizedColumnHeaders, }} /> \ No newline at end of file From 34975ee8ee1617debcdc37f2e8ea9b8404c2b4e2 Mon Sep 17 00:00:00 2001 From: Matheus Wichman Date: Fri, 24 Feb 2023 20:30:27 -0300 Subject: [PATCH 07/15] Avoid `any` --- packages/grid/x-data-grid/src/components/GridRow.tsx | 5 +++-- 1 file changed, 3 insertions(+), 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 0464dad778fe2..c92a203c52152 100644 --- a/packages/grid/x-data-grid/src/components/GridRow.tsx +++ b/packages/grid/x-data-grid/src/components/GridRow.tsx @@ -264,8 +264,9 @@ const GridRow = React.forwardRef< classes: rootClasses, disableColumnReorder, getCellClassName, - rowReordering, - } = rootProps as any; + } = rootProps; + + const rowReordering = (rootProps as any).rowReordering as boolean; const getCell = React.useCallback( ( From 1381e47957fcf1f2b3d4786582c88b4ed683e30b Mon Sep 17 00:00:00 2001 From: Matheus Wichman Date: Fri, 24 Feb 2023 20:36:02 -0300 Subject: [PATCH 08/15] Avoid listing `slots` as dependency --- packages/grid/x-data-grid/src/components/GridRow.tsx | 8 +++++--- 1 file changed, 5 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 c92a203c52152..33ce82d08e431 100644 --- a/packages/grid/x-data-grid/src/components/GridRow.tsx +++ b/packages/grid/x-data-grid/src/components/GridRow.tsx @@ -268,6 +268,8 @@ const GridRow = React.forwardRef< const rowReordering = (rootProps as any).rowReordering as boolean; + const CellComponent = slots.cell; + const getCell = React.useCallback( ( column: GridStateColDef, @@ -345,7 +347,7 @@ const GridRow = React.forwardRef< }); return ( - {content} - + ); }, [ @@ -381,7 +383,7 @@ const GridRow = React.forwardRef< getCellClassName, focusedCell, tabbableCell, - slots, + CellComponent, rowHeight, slotProps?.cell, rootClasses, From c5d16257c47e3af7a1e47879407c2888fc4105c2 Mon Sep 17 00:00:00 2001 From: Matheus Wichman Date: Mon, 27 Feb 2023 13:45:13 -0300 Subject: [PATCH 09/15] Use defaultMemoize to cache rendered columns --- .../virtualization/useGridVirtualScroller.tsx | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 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 9155ba1049634..3c10cee5bc940 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 @@ -27,6 +27,7 @@ import { GridStateColDef } from '../../../models/colDef/gridColDef'; import { getFirstNonSpannedColumnToRender } from '../columns/gridColumnsUtils'; import { getMinimalContentHeight } from '../rows/gridRowsUtils'; import { GridRowProps } from '../../../components/GridRow'; +import { defaultMemoize } from 'reselect'; // Uses binary search to avoid looping through all possible positions export function binarySearch( @@ -144,10 +145,13 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { const prevGetRowProps = React.useRef(); const prevRootRowStyle = React.useRef(); - const cachedRenderedColumns = React.useRef(); - const prevFirstColumnToRender = React.useRef(); - const prevLastColumnToRender = React.useRef(); - const prevVisibleColumns = React.useRef(); + const getRenderedColumnsRef = React.useRef( + defaultMemoize( + (columns: GridStateColDef[], firstColumnToRender: number, lastColumnToRender: number) => { + return columns.slice(firstColumnToRender, lastColumnToRender); + }, + ), + ); const getNearestIndexToRender = React.useCallback( (offset: number) => { @@ -517,19 +521,11 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { visibleRows: currentPage.rows, }); - const invalidatesCachedRenderedColumns = - firstColumnToRender !== prevFirstColumnToRender.current || - lastColumnToRender !== prevLastColumnToRender.current || - visibleColumns !== prevVisibleColumns.current; - - if (invalidatesCachedRenderedColumns) { - cachedRenderedColumns.current = visibleColumns.slice(firstColumnToRender, lastColumnToRender); - prevFirstColumnToRender.current = firstColumnToRender; - prevLastColumnToRender.current = lastColumnToRender; - prevVisibleColumns.current = visibleColumns; - } - - const renderedColumns = cachedRenderedColumns.current; + const renderedColumns = getRenderedColumnsRef.current( + visibleColumns, + firstColumnToRender, + lastColumnToRender, + ); const { style: rootRowStyle, ...rootRowProps } = rootProps.slotProps?.row || {}; From c342db69529e8ea078c8d3afcf2b87637ec531c9 Mon Sep 17 00:00:00 2001 From: Matheus Wichman Date: Mon, 27 Feb 2023 13:48:10 -0300 Subject: [PATCH 10/15] Improve migration guide --- .../migration-data-grid-v5.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/data/migration/migration-data-grid-v5/migration-data-grid-v5.md b/docs/data/migration/migration-data-grid-v5/migration-data-grid-v5.md index 5cd900c176faf..e4f2489efc9f4 100644 --- a/docs/data/migration/migration-data-grid-v5/migration-data-grid-v5.md +++ b/docs/data/migration/migration-data-grid-v5/migration-data-grid-v5.md @@ -270,6 +270,17 @@ Most of this breaking change is handled by `preset-safe` codemod but some furthe - The `GridActionsCellProps['api']` property was removed. Use `useGridApiContext` hook instead to get `apiRef`. - The `GridActionsCellProps['getValue']` property was removed. Use `params.row` instead. - The `GridFooterCellProps['getValue']` property was removed. Use `params.row` instead. +- The `cellFocus`, `cellTabIndex` and `editRowsState` props are not passed to the component used in the row slot. + You can use the new `focusedCell` and `tabbableCell` props instead. + For the editing state, use the API methods. + ```diff + const CustomRow = (props) => { + - const focusedField = props.cellFocus.field; + + const focusedField = props.focusedCell; + - const tabIndex = props.cellTabIndex.field && cellMode === 'view' ? 0 : 1; + + const tabIndex = props.tabbableCell === column.field ? 0 : 1; + } + ``` ### Pagination @@ -458,9 +469,3 @@ npx @mui/x-codemod v6.0.0/data-grid/rename-components-to-slots ``` Take a look at [the RFC](https://github.com/mui/material-ui/issues/33416) for more information. - -### Misc - -- The `cellFocus`, `cellTabIndex` and `editRowsState` props are not passed to the component used in the row slot. - You can use the new `focusedCell` and `tabbableCell` props instead. - For the editing state, use the API methods. From 3fea0a0ec4b8f37355c336b7e7a8c3d5767c04f2 Mon Sep 17 00:00:00 2001 From: Matheus Wichman Date: Mon, 27 Feb 2023 13:50:39 -0300 Subject: [PATCH 11/15] No need to pass `ColumnHeadersComponent` prop --- packages/grid/x-data-grid/src/DataGrid/DataGrid.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/grid/x-data-grid/src/DataGrid/DataGrid.tsx b/packages/grid/x-data-grid/src/DataGrid/DataGrid.tsx index 505b4af6f4f03..af8c7ae8945b8 100644 --- a/packages/grid/x-data-grid/src/DataGrid/DataGrid.tsx +++ b/packages/grid/x-data-grid/src/DataGrid/DataGrid.tsx @@ -7,7 +7,6 @@ import { GridContextProvider } from '../context/GridContextProvider'; import { useDataGridComponent } from './useDataGridComponent'; import { useDataGridProps } from './useDataGridProps'; import { DataGridVirtualScroller } from '../components/DataGridVirtualScroller'; -import { GridColumnHeaders } from '../components/GridColumnHeaders'; import { GridValidRowModel } from '../models/gridRows'; const DataGridRaw = React.forwardRef(function DataGrid( @@ -21,10 +20,7 @@ const DataGridRaw = React.forwardRef(function DataGrid - + From 221be7d21b857263c8ce9e1916b004a9b79ed4e3 Mon Sep 17 00:00:00 2001 From: Matheus Wichman Date: Mon, 27 Feb 2023 13:51:37 -0300 Subject: [PATCH 12/15] Organize imports --- .../hooks/features/virtualization/useGridVirtualScroller.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3c10cee5bc940..07de08ae95e59 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 @@ -5,6 +5,7 @@ import { unstable_useEnhancedEffect as useEnhancedEffect, } from '@mui/utils'; import { useTheme } from '@mui/material/styles'; +import { defaultMemoize } from 'reselect'; import { useGridPrivateApiContext } from '../../utils/useGridPrivateApiContext'; import { useGridRootProps } from '../../utils/useGridRootProps'; import { useGridSelector } from '../../utils/useGridSelector'; @@ -27,7 +28,6 @@ import { GridStateColDef } from '../../../models/colDef/gridColDef'; import { getFirstNonSpannedColumnToRender } from '../columns/gridColumnsUtils'; import { getMinimalContentHeight } from '../rows/gridRowsUtils'; import { GridRowProps } from '../../../components/GridRow'; -import { defaultMemoize } from 'reselect'; // Uses binary search to avoid looping through all possible positions export function binarySearch( From 7b899d15a3f221e9be0dd74f02353e5633b88962 Mon Sep 17 00:00:00 2001 From: Matheus Wichman Date: Mon, 27 Feb 2023 13:52:15 -0300 Subject: [PATCH 13/15] Fix markdown --- .../migration-data-grid-v5/migration-data-grid-v5.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/data/migration/migration-data-grid-v5/migration-data-grid-v5.md b/docs/data/migration/migration-data-grid-v5/migration-data-grid-v5.md index e4f2489efc9f4..33bb5e6b987e9 100644 --- a/docs/data/migration/migration-data-grid-v5/migration-data-grid-v5.md +++ b/docs/data/migration/migration-data-grid-v5/migration-data-grid-v5.md @@ -274,12 +274,12 @@ Most of this breaking change is handled by `preset-safe` codemod but some furthe You can use the new `focusedCell` and `tabbableCell` props instead. For the editing state, use the API methods. ```diff - const CustomRow = (props) => { + const CustomRow = (props) => { - const focusedField = props.cellFocus.field; + const focusedField = props.focusedCell; - const tabIndex = props.cellTabIndex.field && cellMode === 'view' ? 0 : 1; + const tabIndex = props.tabbableCell === column.field ? 0 : 1; - } + } ``` ### Pagination From c083ab1f7302af7e42b07fbe23ff62c1182eaae4 Mon Sep 17 00:00:00 2001 From: Matheus Wichman Date: Mon, 27 Feb 2023 18:58:31 -0300 Subject: [PATCH 14/15] Andrew's suggestions Co-authored-by: Andrew Cherniavskii Signed-off-by: Matheus Wichman --- docs/data/data-grid/performance/performance.md | 8 ++++---- .../migration-data-grid-v5/migration-data-grid-v5.md | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/data/data-grid/performance/performance.md b/docs/data/data-grid/performance/performance.md index 37f3689d72faa..a26c29d8d89d4 100644 --- a/docs/data/data-grid/performance/performance.md +++ b/docs/data/data-grid/performance/performance.md @@ -7,9 +7,9 @@ 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`, it means that the `DataGrid` component will re-render as well as its children, which includes 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 gets increased and, specially, 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 children components when their props have changed. +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: ```tsx @@ -37,7 +37,7 @@ It also contains additional logic to highlight the components when they re-rende :::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 then the props passed to `GridRow`, the row component should not be memoized. +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 diff --git a/docs/data/migration/migration-data-grid-v5/migration-data-grid-v5.md b/docs/data/migration/migration-data-grid-v5/migration-data-grid-v5.md index 33bb5e6b987e9..f5e7562ebe3aa 100644 --- a/docs/data/migration/migration-data-grid-v5/migration-data-grid-v5.md +++ b/docs/data/migration/migration-data-grid-v5/migration-data-grid-v5.md @@ -270,8 +270,8 @@ Most of this breaking change is handled by `preset-safe` codemod but some furthe - The `GridActionsCellProps['api']` property was removed. Use `useGridApiContext` hook instead to get `apiRef`. - The `GridActionsCellProps['getValue']` property was removed. Use `params.row` instead. - The `GridFooterCellProps['getValue']` property was removed. Use `params.row` instead. -- The `cellFocus`, `cellTabIndex` and `editRowsState` props are not passed to the component used in the row slot. - You can use the new `focusedCell` and `tabbableCell` props instead. +- The `cellFocus`, `cellTabIndex` and `editRowsState` props are not passed to the `Row` slot anymore. + Use the `focusedCell` and `tabbableCell` props instead. For the editing state, use the API methods. ```diff const CustomRow = (props) => { From 723a823a5f4f121ce022123c9341b4d1d42bfcdb Mon Sep 17 00:00:00 2001 From: Matheus Wichman Date: Mon, 27 Feb 2023 19:20:39 -0300 Subject: [PATCH 15/15] Rerun CI