From c9386b58ef584921ef42b5fa84621bd0a55668bd Mon Sep 17 00:00:00 2001 From: lauri865 Date: Mon, 23 Dec 2024 13:07:55 +0100 Subject: [PATCH 01/13] avoid `renderContext` selector hooks in root + better isolation for `rowSpanning` --- .../features/dataSource/useGridDataSource.ts | 3 +- .../useGridDataSourceLazyLoader.ts | 10 +-- .../hooks/features/rows/useGridRowSpanning.ts | 78 +++++++++++-------- packages/x-data-grid/src/utils/utils.ts | 6 ++ 4 files changed, 57 insertions(+), 40 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index 243af1a9e34ce..a83c200e3325c 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -16,7 +16,7 @@ import { GridStrategyGroup, GridStrategyProcessor, useGridRegisterStrategyProcessor, -} from '@mui/x-data-grid/internals'; + runIf } from '@mui/x-data-grid/internals'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; import { gridGetRowsParamsSelector, gridDataSourceErrorsSelector } from './gridDataSourceSelector'; @@ -26,7 +26,6 @@ import { DataSourceRowsUpdateStrategy, NestedDataManager, RequestStatus, - runIf, } from './utils'; import { GridDataSourceCache } from '../../../models'; import { GridDataSourceCacheDefault, GridDataSourceCacheDefaultConfig } from './cache'; diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts index 2b940e816308c..bf60484c59083 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts @@ -20,12 +20,12 @@ import { GridStrategyGroup, GridStrategyProcessor, useGridRegisterStrategyProcessor, -} from '@mui/x-data-grid/internals'; + runIf } from '@mui/x-data-grid/internals'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; import { findSkeletonRowsSection } from '../lazyLoader/utils'; import { GRID_SKELETON_ROW_ROOT_ID } from '../lazyLoader/useGridLazyLoaderPreProcessors'; -import { DataSourceRowsUpdateStrategy, runIf } from '../dataSource/utils'; +import { DataSourceRowsUpdateStrategy } from '../dataSource/utils'; enum LoadingTrigger { VIEWPORT, @@ -72,7 +72,6 @@ export const useGridDataSourceLazyLoader = ( const paginationModel = useGridSelector(privateApiRef, gridPaginationModelSelector); const filteredSortedRowIds = useGridSelector(privateApiRef, gridFilteredSortedRowIdsSelector); const dimensions = useGridSelector(privateApiRef, gridDimensionsSelector); - const renderContext = useGridSelector(privateApiRef, gridRenderContextSelector); const renderedRowsIntervalCache = React.useRef(INTERVAL_CACHE_INITIAL_STATE); const previousLastRowIndex = React.useRef(0); const loadingTrigger = React.useRef(null); @@ -308,6 +307,7 @@ export const useGridDataSourceLazyLoader = ( const handleScrolling: GridEventListener<'scrollPositionChange'> = React.useCallback( (newScrollPosition) => { + const renderContext = gridRenderContextSelector(privateApiRef); if ( loadingTrigger.current !== LoadingTrigger.SCROLL_END || previousLastRowIndex.current >= renderContext.lastRowIndex @@ -342,7 +342,6 @@ export const useGridDataSourceLazyLoader = ( filterModel, dimensions, paginationModel.pageSize, - renderContext.lastRowIndex, adjustRowParams, ], ); @@ -419,6 +418,7 @@ export const useGridDataSourceLazyLoader = ( (newSortModel) => { rowsStale.current = true; previousLastRowIndex.current = 0; + const renderContext = gridRenderContextSelector(privateApiRef); const rangeParams = loadingTrigger.current === LoadingTrigger.VIEWPORT ? { @@ -442,7 +442,7 @@ export const useGridDataSourceLazyLoader = ( adjustRowParams(getRowsParams), ); }, - [privateApiRef, filterModel, paginationModel.pageSize, renderContext, adjustRowParams], + [privateApiRef, filterModel, paginationModel.pageSize, adjustRowParams], ); const handleGridFilterModelChange = React.useCallback>( diff --git a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts index 8ee70db58ad24..d52e020b1d820 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts @@ -4,6 +4,7 @@ import { GRID_DETAIL_PANEL_TOGGLE_FIELD } from '../../../internals/constants'; import { gridVisibleColumnDefinitionsSelector } from '../columns/gridColumnsSelector'; import { useGridVisibleRows } from '../../utils/useGridVisibleRows'; import { gridRenderContextSelector } from '../virtualization/gridVirtualizationSelectors'; +import { GridRenderContext } from '../../../models'; import { useGridSelector } from '../../utils/useGridSelector'; import { gridRowTreeSelector } from './gridRowsSelector'; import type { GridColDef } from '../../../models/colDef'; @@ -18,6 +19,8 @@ import { getCellValue, } from './gridRowSpanningUtils'; import { GRID_CHECKBOX_SELECTION_FIELD } from '../../../colDef/gridCheckboxSelectionColDef'; +import { useGridApiEventHandler } from '../../utils/useGridApiEventHandler'; +import { runIf } from '../../../utils/utils'; export interface GridRowSpanningState { spannedCells: Record>; @@ -225,7 +228,6 @@ export const useGridRowSpanning = ( props: Pick, ): void => { const { range, rows: visibleRows } = useGridVisibleRows(apiRef, props); - const renderContext = useGridSelector(apiRef, gridRenderContextSelector); const colDefs = useGridSelector(apiRef, gridVisibleColumnDefinitionsSelector); const tree = useGridSelector(apiRef, gridRowTreeSelector); const processedRange = useLazyRef(() => { @@ -248,14 +250,7 @@ export const useGridRowSpanning = ( // - The sorting is applied // - The `paginationModel` is updated // - The rows are updated - (resetState: boolean = true) => { - if (!props.rowSpanning) { - if (apiRef.current.state.rowSpanning !== EMPTY_STATE) { - apiRef.current.setState((state) => ({ ...state, rowSpanning: EMPTY_STATE })); - } - return; - } - + (renderContext: GridRenderContext, resetState: boolean = true) => { if (range === null || !isRowContextInitialized(renderContext)) { return; } @@ -322,35 +317,52 @@ export const useGridRowSpanning = ( }; }); }, - [apiRef, props.rowSpanning, range, renderContext, visibleRows, colDefs, processedRange], + [apiRef, range, visibleRows, colDefs, processedRange], ); - const prevRenderContext = React.useRef(renderContext); - const isFirstRender = React.useRef(true); + const prevRenderContext = React.useRef(gridRenderContextSelector(apiRef)); const shouldResetState = React.useRef(false); const previousTree = React.useRef(tree); + const onRenderContextChange = React.useCallback( + (renderContext: GridRenderContext) => { + if (tree !== previousTree.current) { + previousTree.current = tree; + updateRowSpanningState(renderContext, true); + return; + } + if (range && lastRange.current && isRowRangeUpdated(range, lastRange.current)) { + lastRange.current = range; + shouldResetState.current = true; + } + if (prevRenderContext.current !== renderContext) { + if (isRowRangeUpdated(prevRenderContext.current, renderContext)) { + updateRowSpanningState(renderContext, shouldResetState.current); + shouldResetState.current = false; + } + prevRenderContext.current = renderContext; + return; + } + updateRowSpanningState(renderContext); + }, + [updateRowSpanningState, range, lastRange, tree], + ); + + const onRowsUpdate = React.useCallback(() => { + onRenderContextChange(gridRenderContextSelector(apiRef)); + }, [apiRef, onRenderContextChange]); + + useGridApiEventHandler( + apiRef, + 'renderedRowsIntervalChange', + runIf(props.rowSpanning, onRenderContextChange), + ); + useGridApiEventHandler(apiRef, 'rowsSet', runIf(props.rowSpanning, onRowsUpdate)); + React.useEffect(() => { - const firstRender = isFirstRender.current; - if (isFirstRender.current) { - isFirstRender.current = false; - } - if (tree !== previousTree.current) { - previousTree.current = tree; - updateRowSpanningState(true); - return; - } - if (range && lastRange.current && isRowRangeUpdated(range, lastRange.current)) { - lastRange.current = range; - shouldResetState.current = true; - } - if (!firstRender && prevRenderContext.current !== renderContext) { - if (isRowRangeUpdated(prevRenderContext.current, renderContext)) { - updateRowSpanningState(shouldResetState.current); - shouldResetState.current = false; + if (!props.rowSpanning) { + if (apiRef.current.state.rowSpanning !== EMPTY_STATE) { + apiRef.current.setState((state) => ({ ...state, rowSpanning: EMPTY_STATE })); } - prevRenderContext.current = renderContext; - return; } - updateRowSpanningState(); - }, [updateRowSpanningState, renderContext, range, lastRange, tree]); + }, [apiRef, props.rowSpanning]); }; diff --git a/packages/x-data-grid/src/utils/utils.ts b/packages/x-data-grid/src/utils/utils.ts index 623213bd3b495..9edeb5ecd8eda 100644 --- a/packages/x-data-grid/src/utils/utils.ts +++ b/packages/x-data-grid/src/utils/utils.ts @@ -215,3 +215,9 @@ export function deepClone(obj: Record) { * that hint disables checks on all values instead of just one. */ export function eslintUseValue(_: any) {} + +export const runIf = (condition: boolean, fn: Function) => (params: unknown) => { + if (condition) { + fn(params); + } +}; From baea9b1017b2aeed83010e984b37e6687645e450 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Mon, 23 Dec 2024 13:25:20 +0100 Subject: [PATCH 02/13] prettier --- .../src/hooks/features/dataSource/useGridDataSource.ts | 3 ++- .../serverSideLazyLoader/useGridDataSourceLazyLoader.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index a83c200e3325c..d13d64e1895d7 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -16,7 +16,8 @@ import { GridStrategyGroup, GridStrategyProcessor, useGridRegisterStrategyProcessor, - runIf } from '@mui/x-data-grid/internals'; + runIf, +} from '@mui/x-data-grid/internals'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; import { gridGetRowsParamsSelector, gridDataSourceErrorsSelector } from './gridDataSourceSelector'; diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts index bf60484c59083..c36a874f5ef82 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts @@ -20,7 +20,8 @@ import { GridStrategyGroup, GridStrategyProcessor, useGridRegisterStrategyProcessor, - runIf } from '@mui/x-data-grid/internals'; + runIf, +} from '@mui/x-data-grid/internals'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; import { findSkeletonRowsSection } from '../lazyLoader/utils'; From 1137212732d3c7c64e24f4d4ea8094a6cb6eb78c Mon Sep 17 00:00:00 2001 From: lauri865 Date: Mon, 23 Dec 2024 13:41:05 +0100 Subject: [PATCH 03/13] fix row spanning with pagination --- .../x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts index d52e020b1d820..2315364204314 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts @@ -357,6 +357,7 @@ export const useGridRowSpanning = ( runIf(props.rowSpanning, onRenderContextChange), ); useGridApiEventHandler(apiRef, 'rowsSet', runIf(props.rowSpanning, onRowsUpdate)); + useGridApiEventHandler(apiRef, 'paginationModelChange', runIf(props.rowSpanning, onRowsUpdate)); React.useEffect(() => { if (!props.rowSpanning) { From 703a7e6083693a03a1ef37482c674775a341f592 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Mon, 23 Dec 2024 13:47:04 +0100 Subject: [PATCH 04/13] add events for sorting and filtering for rowSpanning --- .../x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts index 2315364204314..efdd3a2ce81ee 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts @@ -358,6 +358,8 @@ export const useGridRowSpanning = ( ); useGridApiEventHandler(apiRef, 'rowsSet', runIf(props.rowSpanning, onRowsUpdate)); useGridApiEventHandler(apiRef, 'paginationModelChange', runIf(props.rowSpanning, onRowsUpdate)); + useGridApiEventHandler(apiRef, 'sortModelChange', runIf(props.rowSpanning, onRowsUpdate)); + useGridApiEventHandler(apiRef, 'filterModelChange', runIf(props.rowSpanning, onRowsUpdate)); React.useEffect(() => { if (!props.rowSpanning) { From 8b09405b08bef59a967e814fb9ef11be23892d4a Mon Sep 17 00:00:00 2001 From: lauri865 Date: Mon, 23 Dec 2024 14:42:30 +0100 Subject: [PATCH 05/13] fix reactivity --- .../hooks/features/rows/useGridRowSpanning.ts | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts index efdd3a2ce81ee..5f6ea5738bc33 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts @@ -2,10 +2,9 @@ import * as React from 'react'; import useLazyRef from '@mui/utils/useLazyRef'; import { GRID_DETAIL_PANEL_TOGGLE_FIELD } from '../../../internals/constants'; import { gridVisibleColumnDefinitionsSelector } from '../columns/gridColumnsSelector'; -import { useGridVisibleRows } from '../../utils/useGridVisibleRows'; +import { getVisibleRows } from '../../utils/useGridVisibleRows'; import { gridRenderContextSelector } from '../virtualization/gridVirtualizationSelectors'; import { GridRenderContext } from '../../../models'; -import { useGridSelector } from '../../utils/useGridSelector'; import { gridRowTreeSelector } from './gridRowsSelector'; import type { GridColDef } from '../../../models/colDef'; import type { GridRowId, GridValidRowModel, GridRowEntry } from '../../../models/gridRows'; @@ -227,9 +226,6 @@ export const useGridRowSpanning = ( apiRef: React.MutableRefObject, props: Pick, ): void => { - const { range, rows: visibleRows } = useGridVisibleRows(apiRef, props); - const colDefs = useGridSelector(apiRef, gridVisibleColumnDefinitionsSelector); - const tree = useGridSelector(apiRef, gridRowTreeSelector); const processedRange = useLazyRef(() => { return Object.keys(apiRef.current.state.rowSpanning.spannedCells).length > 0 ? { @@ -251,6 +247,10 @@ export const useGridRowSpanning = ( // - The `paginationModel` is updated // - The rows are updated (renderContext: GridRenderContext, resetState: boolean = true) => { + const { range, rows: visibleRows } = getVisibleRows(apiRef, { + pagination: props.pagination, + paginationMode: props.paginationMode, + }); if (range === null || !isRowContextInitialized(renderContext)) { return; } @@ -271,6 +271,7 @@ export const useGridRowSpanning = ( return; } + const colDefs = gridVisibleColumnDefinitionsSelector(apiRef); const { spannedCells, hiddenCells, @@ -316,20 +317,26 @@ export const useGridRowSpanning = ( }, }; }); + apiRef.current.updateRenderContext?.(); }, - [apiRef, range, visibleRows, colDefs, processedRange], + [apiRef, processedRange, props.pagination, props.paginationMode], ); const prevRenderContext = React.useRef(gridRenderContextSelector(apiRef)); const shouldResetState = React.useRef(false); - const previousTree = React.useRef(tree); + const previousTree = React.useRef(gridRowTreeSelector(apiRef)); const onRenderContextChange = React.useCallback( (renderContext: GridRenderContext) => { + const tree = gridRowTreeSelector(apiRef); if (tree !== previousTree.current) { previousTree.current = tree; updateRowSpanningState(renderContext, true); return; } + const { range } = getVisibleRows(apiRef, { + pagination: props.pagination, + paginationMode: props.paginationMode, + }); if (range && lastRange.current && isRowRangeUpdated(range, lastRange.current)) { lastRange.current = range; shouldResetState.current = true; @@ -344,7 +351,7 @@ export const useGridRowSpanning = ( } updateRowSpanningState(renderContext); }, - [updateRowSpanningState, range, lastRange, tree], + [apiRef, updateRowSpanningState, lastRange, props.pagination, props.paginationMode], ); const onRowsUpdate = React.useCallback(() => { @@ -356,10 +363,8 @@ export const useGridRowSpanning = ( 'renderedRowsIntervalChange', runIf(props.rowSpanning, onRenderContextChange), ); - useGridApiEventHandler(apiRef, 'rowsSet', runIf(props.rowSpanning, onRowsUpdate)); - useGridApiEventHandler(apiRef, 'paginationModelChange', runIf(props.rowSpanning, onRowsUpdate)); - useGridApiEventHandler(apiRef, 'sortModelChange', runIf(props.rowSpanning, onRowsUpdate)); - useGridApiEventHandler(apiRef, 'filterModelChange', runIf(props.rowSpanning, onRowsUpdate)); + + useGridApiEventHandler(apiRef, 'sortedRowsSet', runIf(props.rowSpanning, onRowsUpdate)); React.useEffect(() => { if (!props.rowSpanning) { From e91ffbcff18c59c7e19f548a19aeea483d6c8e0c Mon Sep 17 00:00:00 2001 From: lauri865 Date: Mon, 23 Dec 2024 15:38:33 +0100 Subject: [PATCH 06/13] fix prop --- .../x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts index 5f6ea5738bc33..c965d84b5fbc4 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts @@ -371,6 +371,8 @@ export const useGridRowSpanning = ( if (apiRef.current.state.rowSpanning !== EMPTY_STATE) { apiRef.current.setState((state) => ({ ...state, rowSpanning: EMPTY_STATE })); } + } else if (apiRef.current.state.rowSpanning === EMPTY_STATE) { + onRowsUpdate(); } }, [apiRef, props.rowSpanning]); }; From 6fa1476374f2f2e6757bf54179f7f52e4024767d Mon Sep 17 00:00:00 2001 From: lauri865 Date: Mon, 23 Dec 2024 15:55:58 +0100 Subject: [PATCH 07/13] missing dep --- .../x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts index c965d84b5fbc4..daf1f68ac875c 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts @@ -374,5 +374,5 @@ export const useGridRowSpanning = ( } else if (apiRef.current.state.rowSpanning === EMPTY_STATE) { onRowsUpdate(); } - }, [apiRef, props.rowSpanning]); + }, [apiRef, onRowsUpdate, props.rowSpanning]); }; From 27b8270b48a60e178b79573f1d4f5836e3ab071c Mon Sep 17 00:00:00 2001 From: lauri865 Date: Mon, 23 Dec 2024 16:11:33 +0100 Subject: [PATCH 08/13] cleanup --- .../x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts index daf1f68ac875c..4871d6a1086f0 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts @@ -317,7 +317,6 @@ export const useGridRowSpanning = ( }, }; }); - apiRef.current.updateRenderContext?.(); }, [apiRef, processedRange, props.pagination, props.paginationMode], ); From cb3cc3d161d4bca4b711813ffa5d6a336dd184d2 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Tue, 24 Dec 2024 01:08:48 +0100 Subject: [PATCH 09/13] refactor rowspanning logic --- .../hooks/features/rows/useGridRowSpanning.ts | 74 +++++++------------ 1 file changed, 26 insertions(+), 48 deletions(-) diff --git a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts index 4871d6a1086f0..5db53f3efe451 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts @@ -5,7 +5,6 @@ import { gridVisibleColumnDefinitionsSelector } from '../columns/gridColumnsSele import { getVisibleRows } from '../../utils/useGridVisibleRows'; import { gridRenderContextSelector } from '../virtualization/gridVirtualizationSelectors'; import { GridRenderContext } from '../../../models'; -import { gridRowTreeSelector } from './gridRowsSelector'; import type { GridColDef } from '../../../models/colDef'; import type { GridRowId, GridValidRowModel, GridRowEntry } from '../../../models/gridRows'; import type { DataGridProcessedProps } from '../../../models/props/DataGridProps'; @@ -13,9 +12,9 @@ import type { GridPrivateApiCommunity } from '../../../models/api/gridApiCommuni import type { GridStateInitializer } from '../../utils/useGridInitializeState'; import { getUnprocessedRange, - isRowRangeUpdated, isRowContextInitialized, getCellValue, + isRowRangeUpdated, } from './gridRowSpanningUtils'; import { GRID_CHECKBOX_SELECTION_FIELD } from '../../../colDef/gridCheckboxSelectionColDef'; import { useGridApiEventHandler } from '../../utils/useGridApiEventHandler'; @@ -96,9 +95,10 @@ const computeRowSpanningState = ( const backwardsHiddenCells: number[] = []; if (index === rangeToProcess.firstRowIndex) { let prevIndex = index - 1; - const prevRowEntry = visibleRows[prevIndex]; + let prevRowEntry = visibleRows[prevIndex]; while ( prevIndex >= range.firstRowIndex && + prevRowEntry && getCellValue(prevRowEntry.model, colDef, apiRef) === cellValue ) { const currentRow = visibleRows[prevIndex + 1]; @@ -112,6 +112,8 @@ const computeRowSpanningState = ( spannedRowId = prevRowEntry.id; spannedRowIndex = prevIndex; prevIndex -= 1; + + prevRowEntry = visibleRows[prevIndex]; } } @@ -181,7 +183,8 @@ export const rowSpanningStateInitializer: GridStateInitializer = (state, props, !orderedFields.length || !dataRowIdToModelLookup || !columnsLookup || - isFilteringPending + isFilteringPending || + !props.rowSpanning ) { return { ...state, @@ -237,16 +240,9 @@ export const useGridRowSpanning = ( } : EMPTY_RANGE; }); - const lastRange = React.useRef(EMPTY_RANGE); const updateRowSpanningState = React.useCallback( - // A reset needs to occur when: - // - The `unstable_rowSpanning` prop is updated (feature flag) - // - The filtering is applied - // - The sorting is applied - // - The `paginationModel` is updated - // - The rows are updated - (renderContext: GridRenderContext, resetState: boolean = true) => { + (renderContext: GridRenderContext, resetState: boolean = false) => { const { range, rows: visibleRows } = getVisibleRows(apiRef, { pagination: props.pagination, paginationMode: props.paginationMode, @@ -302,8 +298,9 @@ export const useGridRowSpanning = ( resetState || newSpannedCellsCount !== currentSpannedCellsCount || newHiddenCellsCount !== currentHiddenCellsCount; + const hasNoSpannedCells = newSpannedCellsCount === 0 && currentSpannedCellsCount === 0; - if (!shouldUpdateState) { + if (!shouldUpdateState || hasNoSpannedCells) { return; } @@ -321,49 +318,30 @@ export const useGridRowSpanning = ( [apiRef, processedRange, props.pagination, props.paginationMode], ); - const prevRenderContext = React.useRef(gridRenderContextSelector(apiRef)); - const shouldResetState = React.useRef(false); - const previousTree = React.useRef(gridRowTreeSelector(apiRef)); - const onRenderContextChange = React.useCallback( - (renderContext: GridRenderContext) => { - const tree = gridRowTreeSelector(apiRef); - if (tree !== previousTree.current) { - previousTree.current = tree; - updateRowSpanningState(renderContext, true); - return; - } - const { range } = getVisibleRows(apiRef, { - pagination: props.pagination, - paginationMode: props.paginationMode, - }); - if (range && lastRange.current && isRowRangeUpdated(range, lastRange.current)) { - lastRange.current = range; - shouldResetState.current = true; - } - if (prevRenderContext.current !== renderContext) { - if (isRowRangeUpdated(prevRenderContext.current, renderContext)) { - updateRowSpanningState(renderContext, shouldResetState.current); - shouldResetState.current = false; - } - prevRenderContext.current = renderContext; - return; - } - updateRowSpanningState(renderContext); - }, - [apiRef, updateRowSpanningState, lastRange, props.pagination, props.paginationMode], - ); - + // Reset events trigger a full re-computation of the row spanning state: + // - The `unstable_rowSpanning` prop is updated (feature flag) + // - The filtering is applied + // - The sorting is applied + // - The `paginationModel` is updated + // - The rows are updated const onRowsUpdate = React.useCallback(() => { - onRenderContextChange(gridRenderContextSelector(apiRef)); - }, [apiRef, onRenderContextChange]); + const renderContext = gridRenderContextSelector(apiRef); + if (!isRowContextInitialized(renderContext)) { + return; + } + updateRowSpanningState(renderContext, true); + }, [apiRef, updateRowSpanningState]); useGridApiEventHandler( apiRef, 'renderedRowsIntervalChange', - runIf(props.rowSpanning, onRenderContextChange), + runIf(props.rowSpanning, updateRowSpanningState), ); useGridApiEventHandler(apiRef, 'sortedRowsSet', runIf(props.rowSpanning, onRowsUpdate)); + useGridApiEventHandler(apiRef, 'paginationModelChange', runIf(props.rowSpanning, onRowsUpdate)); + useGridApiEventHandler(apiRef, 'filteredRowsSet', runIf(props.rowSpanning, onRowsUpdate)); + useGridApiEventHandler(apiRef, 'columnsChange', runIf(props.rowSpanning, onRowsUpdate)); React.useEffect(() => { if (!props.rowSpanning) { From ec537170ad6e81098a29b5d1dfce90d2eb5307b4 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Tue, 24 Dec 2024 01:10:32 +0100 Subject: [PATCH 10/13] remove unused import --- .../src/hooks/features/rows/useGridRowSpanning.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts index 5db53f3efe451..d978af8094355 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts @@ -10,12 +10,7 @@ import type { GridRowId, GridValidRowModel, GridRowEntry } from '../../../models import type { DataGridProcessedProps } from '../../../models/props/DataGridProps'; import type { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity'; import type { GridStateInitializer } from '../../utils/useGridInitializeState'; -import { - getUnprocessedRange, - isRowContextInitialized, - getCellValue, - isRowRangeUpdated, -} from './gridRowSpanningUtils'; +import { getUnprocessedRange, isRowContextInitialized, getCellValue } from './gridRowSpanningUtils'; import { GRID_CHECKBOX_SELECTION_FIELD } from '../../../colDef/gridCheckboxSelectionColDef'; import { useGridApiEventHandler } from '../../utils/useGridApiEventHandler'; import { runIf } from '../../../utils/utils'; From ee331f9409b50eef78dc186da99ac257c9999157 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Tue, 24 Dec 2024 01:12:55 +0100 Subject: [PATCH 11/13] fix test --- .../x-data-grid/src/tests/rowSpanning.DataGrid.test.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/x-data-grid/src/tests/rowSpanning.DataGrid.test.tsx b/packages/x-data-grid/src/tests/rowSpanning.DataGrid.test.tsx index f89153e6c0b4e..868a5c28b2f10 100644 --- a/packages/x-data-grid/src/tests/rowSpanning.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/rowSpanning.DataGrid.test.tsx @@ -250,7 +250,7 @@ describe(' - Row spanning', () => { describe('rows update', () => { it('should update the row spanning state when the rows are updated', () => { - const rowSpanValueGetter = spy(); + const rowSpanValueGetter = spy((value) => value); let rowSpanningStateUpdates = 0; let spannedCells = {}; render( @@ -271,7 +271,10 @@ describe(' - Row spanning', () => { expect(rowSpanningStateUpdates).to.equal(1); act(() => { - apiRef.current.setRows([{ id: 1, code: 'A101' }]); + apiRef.current.setRows([ + { id: 1, code: 'A101' }, + { id: 2, code: 'A101' }, + ]); }); // Second update on row update From f164dd4842ec350301af9608cbfb043e01b6df32 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Tue, 24 Dec 2024 01:20:08 +0100 Subject: [PATCH 12/13] ci From 401f0fa393fe7e3d98121032d24bfd1e5892616c Mon Sep 17 00:00:00 2001 From: lauri865 Date: Thu, 26 Dec 2024 09:06:01 +0100 Subject: [PATCH 13/13] fixes --- .../hooks/features/rows/useGridRowSpanning.ts | 112 ++++++++++-------- 1 file changed, 60 insertions(+), 52 deletions(-) diff --git a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts index d978af8094355..b70494fd17c01 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts @@ -164,59 +164,59 @@ const computeRowSpanningState = ( * @requires filterStateInitializer (method) - should be initialized before */ export const rowSpanningStateInitializer: GridStateInitializer = (state, props, apiRef) => { - if (props.rowSpanning) { - const rowIds = state.rows!.dataRowIds || []; - const orderedFields = state.columns!.orderedFields || []; - const dataRowIdToModelLookup = state.rows!.dataRowIdToModelLookup; - const columnsLookup = state.columns!.lookup; - const isFilteringPending = - Boolean(state.filter!.filterModel!.items!.length) || - Boolean(state.filter!.filterModel!.quickFilterValues?.length); - - if ( - !rowIds.length || - !orderedFields.length || - !dataRowIdToModelLookup || - !columnsLookup || - isFilteringPending || - !props.rowSpanning - ) { - return { - ...state, - rowSpanning: EMPTY_STATE, - }; - } - const rangeToProcess = { - firstRowIndex: 0, - lastRowIndex: Math.min(DEFAULT_ROWS_TO_PROCESS, Math.max(rowIds.length, 0)), + if (!props.rowSpanning) { + return { + ...state, + rowSpanning: EMPTY_STATE, }; - const rows = rowIds.map((id) => ({ - id, - model: dataRowIdToModelLookup[id!], - })) as GridRowEntry[]; - const colDefs = orderedFields.map((field) => columnsLookup[field!]) as GridColDef[]; - const { spannedCells, hiddenCells, hiddenCellOriginMap } = computeRowSpanningState( - apiRef, - colDefs, - rows, - rangeToProcess, - rangeToProcess, - true, - EMPTY_RANGE, - ); + } + const rowIds = state.rows!.dataRowIds || []; + const orderedFields = state.columns!.orderedFields || []; + const dataRowIdToModelLookup = state.rows!.dataRowIdToModelLookup; + const columnsLookup = state.columns!.lookup; + const isFilteringPending = + Boolean(state.filter!.filterModel!.items!.length) || + Boolean(state.filter!.filterModel!.quickFilterValues?.length); + + if ( + !rowIds.length || + !orderedFields.length || + !dataRowIdToModelLookup || + !columnsLookup || + isFilteringPending + ) { return { ...state, - rowSpanning: { - spannedCells, - hiddenCells, - hiddenCellOriginMap, - }, + rowSpanning: EMPTY_STATE, }; } + const rangeToProcess = { + firstRowIndex: 0, + lastRowIndex: Math.min(DEFAULT_ROWS_TO_PROCESS, Math.max(rowIds.length, 0)), + }; + const rows = rowIds.map((id) => ({ + id, + model: dataRowIdToModelLookup[id!], + })) as GridRowEntry[]; + const colDefs = orderedFields.map((field) => columnsLookup[field!]) as GridColDef[]; + const { spannedCells, hiddenCells, hiddenCellOriginMap } = computeRowSpanningState( + apiRef, + colDefs, + rows, + rangeToProcess, + rangeToProcess, + true, + EMPTY_RANGE, + ); + return { ...state, - rowSpanning: EMPTY_STATE, + rowSpanning: { + spannedCells, + hiddenCells, + hiddenCellOriginMap, + }, }; }; @@ -319,7 +319,7 @@ export const useGridRowSpanning = ( // - The sorting is applied // - The `paginationModel` is updated // - The rows are updated - const onRowsUpdate = React.useCallback(() => { + const resetRowSpanningState = React.useCallback(() => { const renderContext = gridRenderContextSelector(apiRef); if (!isRowContextInitialized(renderContext)) { return; @@ -333,10 +333,18 @@ export const useGridRowSpanning = ( runIf(props.rowSpanning, updateRowSpanningState), ); - useGridApiEventHandler(apiRef, 'sortedRowsSet', runIf(props.rowSpanning, onRowsUpdate)); - useGridApiEventHandler(apiRef, 'paginationModelChange', runIf(props.rowSpanning, onRowsUpdate)); - useGridApiEventHandler(apiRef, 'filteredRowsSet', runIf(props.rowSpanning, onRowsUpdate)); - useGridApiEventHandler(apiRef, 'columnsChange', runIf(props.rowSpanning, onRowsUpdate)); + useGridApiEventHandler(apiRef, 'sortedRowsSet', runIf(props.rowSpanning, resetRowSpanningState)); + useGridApiEventHandler( + apiRef, + 'paginationModelChange', + runIf(props.rowSpanning, resetRowSpanningState), + ); + useGridApiEventHandler( + apiRef, + 'filteredRowsSet', + runIf(props.rowSpanning, resetRowSpanningState), + ); + useGridApiEventHandler(apiRef, 'columnsChange', runIf(props.rowSpanning, resetRowSpanningState)); React.useEffect(() => { if (!props.rowSpanning) { @@ -344,7 +352,7 @@ export const useGridRowSpanning = ( apiRef.current.setState((state) => ({ ...state, rowSpanning: EMPTY_STATE })); } } else if (apiRef.current.state.rowSpanning === EMPTY_STATE) { - onRowsUpdate(); + resetRowSpanningState(); } - }, [apiRef, onRowsUpdate, props.rowSpanning]); + }, [apiRef, resetRowSpanningState, props.rowSpanning]); };