From 8797b6cbffced091c0e602f05215a6d48d7de2eb Mon Sep 17 00:00:00 2001 From: lauri865 Date: Fri, 10 Oct 2025 14:27:33 +0200 Subject: [PATCH 01/60] fix aggregation with sorting --- .../aggregation/useGridAggregation.ts | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts index d52c5d00045ea..b9cdfea867d77 100644 --- a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts +++ b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts @@ -15,7 +15,6 @@ import { useGridRegisterPipeProcessor, GridStateInitializer, GridPipeProcessor, - gridPivotActiveSelector, } from '@mui/x-data-grid-pro/internals'; import { DataGridPremiumProcessedProps } from '../../../models/dataGridPremiumProps'; import { GridPrivateApiPremium } from '../../../models/gridApiPremium'; @@ -91,13 +90,6 @@ export const useGridAggregation = ( const abortControllerRef = React.useRef(null); const applyAggregation = React.useCallback( (reason?: 'filter' | 'sort') => { - // Abort previous if any - if (abortControllerRef.current) { - abortControllerRef.current.abort(); - } - const abortController = new AbortController(); - abortControllerRef.current = abortController; - const aggregationRules = getAggregationRules( gridColumnLookupSelector(apiRef), gridAggregationModelSelector(apiRef), @@ -112,18 +104,28 @@ export const useGridAggregation = ( return; } + // Abort previous if we're proceeding + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + const abortController = new AbortController(); + abortControllerRef.current = abortController; + const renderContext = gridRenderContextSelector(apiRef); const visibleColumns = gridVisibleColumnFieldsSelector(apiRef); const chunks: string[][] = []; + const sortFields = gridSortModelSelector(apiRef).map((s) => s.field); const visibleAggregatedFields = visibleColumns .slice(renderContext.firstColumnIndex, renderContext.lastColumnIndex + 1) .filter((field) => aggregatedFields.includes(field)); + const visibleAggregatedFieldsWithSort = visibleAggregatedFields.concat(sortFields); + const hasAggregatedSortedField = sortFields.some((field) => aggregationRules[field]); if (visibleAggregatedFields.length > 0) { - chunks.push(visibleAggregatedFields); + chunks.push(visibleAggregatedFieldsWithSort); } const otherAggregatedFields = aggregatedFields.filter( - (field) => !visibleAggregatedFields.includes(field), + (field) => !visibleAggregatedFieldsWithSort.includes(field), ); const chunkSize = 20; // columns per chunk @@ -143,11 +145,6 @@ export const useGridAggregation = ( const currentChunk = chunks[chunkIndex]; if (!currentChunk) { - const sortModel = gridSortModelSelector(apiRef).map((s) => s.field); - const hasAggregatedSorting = sortModel.some((field) => aggregationRules[field]); - if (hasAggregatedSorting) { - apiRef.current.applySorting(); - } abortControllerRef.current = null; return; } @@ -177,6 +174,10 @@ export const useGridAggregation = ( aggregation: { ...state.aggregation, lookup: { ...aggregationLookup } }, })); + if (chunkIndex === 0 && hasAggregatedSortedField) { + apiRef.current.applySorting(); + } + chunkIndex += 1; if (performance.now() - chunkStartTime < timeLimit) { @@ -248,7 +249,6 @@ export const useGridAggregation = ( * EVENTS */ const checkAggregationRulesDiff = React.useCallback(() => { - const pivotingActive = gridPivotActiveSelector(apiRef); const { rulesOnLastRowHydration, rulesOnLastColumnHydration } = apiRef.current.caches.aggregation; @@ -262,10 +262,7 @@ export const useGridAggregation = ( ); // Re-apply the row hydration to add / remove the aggregation footers - if ( - (!props.dataSource || pivotingActive) && - !areAggregationRulesEqual(rulesOnLastRowHydration, aggregationRules) - ) { + if (!props.dataSource && !areAggregationRulesEqual(rulesOnLastRowHydration, aggregationRules)) { apiRef.current.requestPipeProcessorsApplication('hydrateRows'); deferredApplyAggregation(); } From ff426d68f09674415e20343fc4891d686757608a Mon Sep 17 00:00:00 2001 From: lauri865 Date: Fri, 10 Oct 2025 15:24:12 +0200 Subject: [PATCH 02/60] fix aggregation reason --- .../aggregation/useGridAggregation.ts | 228 +++++++++--------- 1 file changed, 118 insertions(+), 110 deletions(-) diff --git a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts index b9cdfea867d77..741ad6681e314 100644 --- a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts +++ b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts @@ -88,128 +88,130 @@ export const useGridAggregation = ( ); const abortControllerRef = React.useRef(null); - const applyAggregation = React.useCallback( - (reason?: 'filter' | 'sort') => { - const aggregationRules = getAggregationRules( - gridColumnLookupSelector(apiRef), - gridAggregationModelSelector(apiRef), - props.aggregationFunctions, - !!props.dataSource, - ); - const aggregatedFields = Object.keys(aggregationRules); - const currentAggregationLookup = gridAggregationLookupSelector(apiRef); - const needsSorting = shouldApplySorting(aggregationRules, aggregatedFields); - if (reason === 'sort' && !needsSorting) { - // no need to re-apply aggregation on `sortedRowsSet` if sorting is not needed - return; - } + const applyAggregationReasonRef = React.useRef<'filter' | 'sort' | null>(null); + const applyAggregation = React.useCallback(() => { + const reason = applyAggregationReasonRef.current; + applyAggregationReasonRef.current = null; + const aggregationRules = getAggregationRules( + gridColumnLookupSelector(apiRef), + gridAggregationModelSelector(apiRef), + props.aggregationFunctions, + !!props.dataSource, + ); + const aggregatedFields = Object.keys(aggregationRules); + const currentAggregationLookup = gridAggregationLookupSelector(apiRef); + const needsSorting = shouldApplySorting(aggregationRules, aggregatedFields); + if (reason === 'sort' && !needsSorting) { + // no need to re-apply aggregation on `sortedRowsSet` if sorting is not needed + return; + } - // Abort previous if we're proceeding - if (abortControllerRef.current) { - abortControllerRef.current.abort(); - } - const abortController = new AbortController(); - abortControllerRef.current = abortController; - - const renderContext = gridRenderContextSelector(apiRef); - const visibleColumns = gridVisibleColumnFieldsSelector(apiRef); - - const chunks: string[][] = []; - const sortFields = gridSortModelSelector(apiRef).map((s) => s.field); - const visibleAggregatedFields = visibleColumns - .slice(renderContext.firstColumnIndex, renderContext.lastColumnIndex + 1) - .filter((field) => aggregatedFields.includes(field)); - const visibleAggregatedFieldsWithSort = visibleAggregatedFields.concat(sortFields); - const hasAggregatedSortedField = sortFields.some((field) => aggregationRules[field]); - if (visibleAggregatedFields.length > 0) { - chunks.push(visibleAggregatedFieldsWithSort); - } - const otherAggregatedFields = aggregatedFields.filter( - (field) => !visibleAggregatedFieldsWithSort.includes(field), - ); + // Abort previous if we're proceeding + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + const abortController = new AbortController(); + abortControllerRef.current = abortController; + + const renderContext = gridRenderContextSelector(apiRef); + const visibleColumns = gridVisibleColumnFieldsSelector(apiRef); + + const chunks: string[][] = []; + const sortFields = gridSortModelSelector(apiRef).map((s) => s.field); + const visibleAggregatedFields = visibleColumns + .slice(renderContext.firstColumnIndex, renderContext.lastColumnIndex + 1) + .filter((field) => aggregatedFields.includes(field)); + const visibleAggregatedFieldsWithSort = visibleAggregatedFields.concat( + sortFields.filter((field) => !visibleAggregatedFields.includes(field)), + ); + const hasAggregatedSortedField = sortFields.some((field) => aggregationRules[field]); + if (visibleAggregatedFields.length > 0) { + chunks.push(visibleAggregatedFieldsWithSort); + } + const otherAggregatedFields = aggregatedFields.filter( + (field) => !visibleAggregatedFieldsWithSort.includes(field), + ); - const chunkSize = 20; // columns per chunk - for (let i = 0; i < otherAggregatedFields.length; i += chunkSize) { - chunks.push(otherAggregatedFields.slice(i, i + chunkSize)); - } + const chunkSize = 20; // columns per chunk + for (let i = 0; i < otherAggregatedFields.length; i += chunkSize) { + chunks.push(otherAggregatedFields.slice(i, i + chunkSize)); + } - let chunkIndex = 0; - const aggregationLookup: GridAggregationLookup = {}; - let chunkStartTime = performance.now(); - const timeLimit = 1000 / 120; + let chunkIndex = 0; + const aggregationLookup: GridAggregationLookup = {}; + let chunkStartTime = performance.now(); + const timeLimit = 1000 / 120; - const processChunk = () => { - if (abortController.signal.aborted) { - return; - } + const processChunk = () => { + if (abortController.signal.aborted) { + return; + } - const currentChunk = chunks[chunkIndex]; - if (!currentChunk) { - abortControllerRef.current = null; - return; - } + const currentChunk = chunks[chunkIndex]; + if (!currentChunk) { + abortControllerRef.current = null; + return; + } - const applySorting = shouldApplySorting(aggregationRules, currentChunk); - - // createAggregationLookup now RETURNS new partial lookup - const partialLookup = createAggregationLookup({ - apiRef, - getAggregationPosition: props.getAggregationPosition, - aggregatedFields: currentChunk, - aggregationRules, - aggregationRowsScope: props.aggregationRowsScope, - isDataSource: !!props.dataSource, - applySorting, - }); - - for (const key of Object.keys(partialLookup)) { - for (const field of Object.keys(partialLookup[key])) { - aggregationLookup[key] ??= {}; - aggregationLookup[key][field] = partialLookup[key][field]; - } + const applySorting = shouldApplySorting(aggregationRules, currentChunk); + + // createAggregationLookup now RETURNS new partial lookup + const partialLookup = createAggregationLookup({ + apiRef, + getAggregationPosition: props.getAggregationPosition, + aggregatedFields: currentChunk, + aggregationRules, + aggregationRowsScope: props.aggregationRowsScope, + isDataSource: !!props.dataSource, + applySorting, + }); + + for (const key of Object.keys(partialLookup)) { + for (const field of Object.keys(partialLookup[key])) { + aggregationLookup[key] ??= {}; + aggregationLookup[key][field] = partialLookup[key][field]; } + } - apiRef.current.setState((state) => ({ - ...state, - aggregation: { ...state.aggregation, lookup: { ...aggregationLookup } }, - })); + apiRef.current.setState((state) => ({ + ...state, + aggregation: { ...state.aggregation, lookup: { ...aggregationLookup } }, + })); - if (chunkIndex === 0 && hasAggregatedSortedField) { - apiRef.current.applySorting(); - } + if (chunkIndex === 0 && hasAggregatedSortedField) { + apiRef.current.applySorting(); + } - chunkIndex += 1; + chunkIndex += 1; - if (performance.now() - chunkStartTime < timeLimit) { - processChunk(); - return; - } + if (performance.now() - chunkStartTime < timeLimit) { + processChunk(); + return; + } - setTimeout(() => { - chunkStartTime = performance.now(); - processChunk(); - }, 0); - }; + setTimeout(() => { + chunkStartTime = performance.now(); + processChunk(); + }, 0); + }; - processChunk(); + processChunk(); - // processChunk() does nothing if there are no aggregated fields - // make sure that the lookup is empty in this case - if (aggregatedFields.length === 0 && !isObjectEmpty(currentAggregationLookup)) { - apiRef.current.setState((state) => ({ - ...state, - aggregation: { ...state.aggregation, lookup: {} }, - })); - } - }, - [ - apiRef, - props.getAggregationPosition, - props.aggregationFunctions, - props.aggregationRowsScope, - props.dataSource, - ], - ); + // processChunk() does nothing if there are no aggregated fields + // make sure that the lookup is empty in this case + if (aggregatedFields.length === 0 && !isObjectEmpty(currentAggregationLookup)) { + apiRef.current.setState((state) => ({ + ...state, + aggregation: { ...state.aggregation, lookup: {} }, + })); + } + }, [ + apiRef, + props.getAggregationPosition, + props.aggregationFunctions, + props.aggregationRowsScope, + props.dataSource, + ]); React.useEffect(() => { return () => { @@ -281,8 +283,14 @@ export const useGridAggregation = ( useGridEvent(apiRef, 'aggregationModelChange', checkAggregationRulesDiff); useGridEvent(apiRef, 'columnsChange', checkAggregationRulesDiff); - useGridEvent(apiRef, 'filteredRowsSet', deferredApplyAggregation); - useGridEvent(apiRef, 'sortedRowsSet', () => deferredApplyAggregation('sort')); + useGridEvent(apiRef, 'filteredRowsSet', () => { + applyAggregationReasonRef.current = 'filter'; + deferredApplyAggregation(); + }); + useGridEvent(apiRef, 'sortedRowsSet', () => { + applyAggregationReasonRef.current ??= 'sort'; + deferredApplyAggregation(); + }); /** * EFFECTS From 8851650bd2e589d397ff18db7aa110939064584c Mon Sep 17 00:00:00 2001 From: lauri865 Date: Fri, 10 Oct 2025 16:20:54 +0200 Subject: [PATCH 03/60] tentative changes --- .../aggregation/useGridAggregation.ts | 2 ++ .../hooks/features/filter/useGridFilter.tsx | 14 +++++++++++- .../hooks/features/sorting/useGridSorting.ts | 22 ++++++++++++++----- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts index 741ad6681e314..3f41e336d5bf6 100644 --- a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts +++ b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts @@ -91,6 +91,7 @@ export const useGridAggregation = ( const applyAggregationReasonRef = React.useRef<'filter' | 'sort' | null>(null); const applyAggregation = React.useCallback(() => { const reason = applyAggregationReasonRef.current; + console.log('reason', reason); applyAggregationReasonRef.current = null; const aggregationRules = getAggregationRules( gridColumnLookupSelector(apiRef), @@ -124,6 +125,7 @@ export const useGridAggregation = ( const visibleAggregatedFieldsWithSort = visibleAggregatedFields.concat( sortFields.filter((field) => !visibleAggregatedFields.includes(field)), ); + const hasAggregatedSortedField = sortFields.some((field) => aggregationRules[field]); if (visibleAggregatedFields.length > 0) { chunks.push(visibleAggregatedFieldsWithSort); diff --git a/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx b/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx index 4d774bfcbf924..eee9dd3ddd18e 100644 --- a/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx +++ b/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx @@ -37,6 +37,7 @@ import { import { GridStateInitializer } from '../../utils/useGridInitializeState'; import type { ItemPlusTag } from '../../../components/panel/filterPanel/GridFilterInputValue'; import type { GridConfiguration } from '../../../models/configuration/gridConfiguration'; +import { isObjectEmpty } from '@mui/x-internals/isObjectEmpty'; export const filterStateInitializer: GridStateInitializer< Pick @@ -108,6 +109,7 @@ export const useGridFilter = ( }); const updateFilteredRows = React.useCallback(() => { + let didChange = false; apiRef.current.setState((state) => { const filterModel = gridFilterModelSelector(apiRef); const filterState = apiRef.current.getFilterState(filterModel); @@ -122,12 +124,22 @@ export const useGridFilter = ( const visibleRowsLookupState = getVisibleRowsLookupState(apiRef, newState); + if ( + visibleRowsLookupState !== state.visibleRowsLookup && + !(isObjectEmpty(visibleRowsLookupState) && !isObjectEmpty(state.visibleRowsLookup)) + ) { + didChange = true; + newState.visibleRowsLookup = visibleRowsLookupState; + } + return { ...newState, visibleRowsLookup: visibleRowsLookupState, }; }); - apiRef.current.publishEvent('filteredRowsSet'); + if (didChange) { + apiRef.current.publishEvent('filteredRowsSet'); + } }, [apiRef]); const addColumnMenuItem = React.useCallback>( diff --git a/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts b/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts index a40c01f7bd4b1..bb61646698532 100644 --- a/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts +++ b/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts @@ -33,6 +33,7 @@ import { import { GridPipeProcessor, useGridRegisterPipeProcessor } from '../../core/pipeProcessing'; import { GridStateInitializer } from '../../utils/useGridInitializeState'; import { getTreeNodeDescendants } from '../rows/gridRowsUtils'; +import { isObjectEmpty } from '@mui/x-internals/isObjectEmpty'; export const sortingStateInitializer: GridStateInitializer< Pick @@ -140,6 +141,7 @@ export const useGridSorting = ( * API METHODS */ const applySorting = React.useCallback(() => { + let didChange = false; apiRef.current.setState((state) => { if (props.sortingMode === 'server') { logger.debug('Skipping sorting rows as sortingMode = server'); @@ -162,13 +164,23 @@ export const useGridSorting = ( sortRowList, }); - return { - ...state, - sorting: { ...state.sorting, sortedRows }, - }; + if ( + sortedRows !== state.sorting.sortedRows && + !(isObjectEmpty(sortedRows) && isObjectEmpty(state.sorting.sortedRows)) + ) { + didChange = true; + return { + ...state, + sorting: { ...state.sorting, sortedRows }, + }; + } + + return state; }); - apiRef.current.publishEvent('sortedRowsSet'); + if (didChange) { + apiRef.current.publishEvent('sortedRowsSet'); + } }, [apiRef, logger, props.sortingMode]); const setSortModel = React.useCallback( From 8916698a7f37f431a27f6250f586b6a50abe8a8f Mon Sep 17 00:00:00 2001 From: lauri865 Date: Fri, 10 Oct 2025 18:18:02 +0200 Subject: [PATCH 04/60] refactors --- .../aggregation/useGridAggregation.ts | 45 ++++++++++++------- .../hooks/features/filter/useGridFilter.tsx | 2 +- .../hooks/features/sorting/useGridSorting.ts | 2 +- .../src/hooks/utils/useRunOncePerLoop.ts | 38 +++++----------- 4 files changed, 44 insertions(+), 43 deletions(-) diff --git a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts index 3f41e336d5bf6..4bf3e104fe520 100644 --- a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts +++ b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts @@ -10,6 +10,8 @@ import { gridRenderContextSelector, gridVisibleColumnFieldsSelector, gridSortModelSelector, + gridRowMaximumTreeDepthSelector, + gridRowCountSelector, } from '@mui/x-data-grid-pro'; import { useGridRegisterPipeProcessor, @@ -88,11 +90,11 @@ export const useGridAggregation = ( ); const abortControllerRef = React.useRef(null); - const applyAggregationReasonRef = React.useRef<'filter' | 'sort' | null>(null); const applyAggregation = React.useCallback(() => { - const reason = applyAggregationReasonRef.current; - console.log('reason', reason); - applyAggregationReasonRef.current = null; + const rowCount = gridRowCountSelector(apiRef); + if (!rowCount) { + return; + } const aggregationRules = getAggregationRules( gridColumnLookupSelector(apiRef), gridAggregationModelSelector(apiRef), @@ -101,11 +103,6 @@ export const useGridAggregation = ( ); const aggregatedFields = Object.keys(aggregationRules); const currentAggregationLookup = gridAggregationLookupSelector(apiRef); - const needsSorting = shouldApplySorting(aggregationRules, aggregatedFields); - if (reason === 'sort' && !needsSorting) { - // no need to re-apply aggregation on `sortedRowsSet` if sorting is not needed - return; - } // Abort previous if we're proceeding if (abortControllerRef.current) { @@ -126,7 +123,9 @@ export const useGridAggregation = ( sortFields.filter((field) => !visibleAggregatedFields.includes(field)), ); - const hasAggregatedSortedField = sortFields.some((field) => aggregationRules[field]); + const hasAggregatedSortedField = + gridRowMaximumTreeDepthSelector(apiRef) > 1 && + sortFields.some((field) => aggregationRules[field]); if (visibleAggregatedFields.length > 0) { chunks.push(visibleAggregatedFieldsWithSort); } @@ -285,12 +284,28 @@ export const useGridAggregation = ( useGridEvent(apiRef, 'aggregationModelChange', checkAggregationRulesDiff); useGridEvent(apiRef, 'columnsChange', checkAggregationRulesDiff); - useGridEvent(apiRef, 'filteredRowsSet', () => { - applyAggregationReasonRef.current = 'filter'; - deferredApplyAggregation(); - }); + useGridEvent(apiRef, 'filteredRowsSet', deferredApplyAggregation); + + const lastSortModel = React.useRef(gridSortModelSelector(apiRef)); useGridEvent(apiRef, 'sortedRowsSet', () => { - applyAggregationReasonRef.current ??= 'sort'; + const sortModel = gridSortModelSelector(apiRef); + if (lastSortModel.current === sortModel) { + return; + } + lastSortModel.current = sortModel; + + const aggregationRules = getAggregationRules( + gridColumnLookupSelector(apiRef), + gridAggregationModelSelector(apiRef), + props.aggregationFunctions, + !!props.dataSource, + ); + const aggregatedFields = Object.keys(aggregationRules); + const needsSorting = shouldApplySorting(aggregationRules, aggregatedFields); + if (!needsSorting) { + return; + } + deferredApplyAggregation(); }); diff --git a/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx b/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx index eee9dd3ddd18e..dd6fb2876f6ba 100644 --- a/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx +++ b/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx @@ -3,6 +3,7 @@ import { lruMemoize } from '@mui/x-internals/lruMemoize'; import { RefObject } from '@mui/x-internals/types'; import useEnhancedEffect from '@mui/utils/useEnhancedEffect'; import { isDeepEqual } from '@mui/x-internals/isDeepEqual'; +import { isObjectEmpty } from '@mui/x-internals/isObjectEmpty'; import { GridEventListener } from '../../../models/events'; import { DataGridProcessedProps } from '../../../models/props/DataGridProps'; import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity'; @@ -37,7 +38,6 @@ import { import { GridStateInitializer } from '../../utils/useGridInitializeState'; import type { ItemPlusTag } from '../../../components/panel/filterPanel/GridFilterInputValue'; import type { GridConfiguration } from '../../../models/configuration/gridConfiguration'; -import { isObjectEmpty } from '@mui/x-internals/isObjectEmpty'; export const filterStateInitializer: GridStateInitializer< Pick diff --git a/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts b/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts index bb61646698532..92d51fc685f46 100644 --- a/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts +++ b/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts @@ -1,6 +1,7 @@ import * as React from 'react'; import { RefObject } from '@mui/x-internals/types'; import useEnhancedEffect from '@mui/utils/useEnhancedEffect'; +import { isObjectEmpty } from '@mui/x-internals/isObjectEmpty'; import { GridEventListener } from '../../../models/events'; import { DataGridProcessedProps } from '../../../models/props/DataGridProps'; import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity'; @@ -33,7 +34,6 @@ import { import { GridPipeProcessor, useGridRegisterPipeProcessor } from '../../core/pipeProcessing'; import { GridStateInitializer } from '../../utils/useGridInitializeState'; import { getTreeNodeDescendants } from '../rows/gridRowsUtils'; -import { isObjectEmpty } from '@mui/x-internals/isObjectEmpty'; export const sortingStateInitializer: GridStateInitializer< Pick diff --git a/packages/x-data-grid/src/hooks/utils/useRunOncePerLoop.ts b/packages/x-data-grid/src/hooks/utils/useRunOncePerLoop.ts index 018e009faf1d6..cd987f0676cc3 100644 --- a/packages/x-data-grid/src/hooks/utils/useRunOncePerLoop.ts +++ b/packages/x-data-grid/src/hooks/utils/useRunOncePerLoop.ts @@ -1,38 +1,24 @@ +'use client'; import * as React from 'react'; -export function useRunOncePerLoop void>( - callback: T, - nextFrame: boolean = false, -) { - const scheduledRef = React.useRef(false); +export function useRunOncePerLoop void>(callback: T) { + const scheduledCallbackRef = React.useRef<(...args: any) => void>(null); const schedule = React.useCallback( (...args: Parameters) => { - if (scheduledRef.current) { - return; - } - scheduledRef.current = true; - - const runner = () => { - scheduledRef.current = false; + scheduledCallbackRef.current = () => { + scheduledCallbackRef.current = null; callback(...args); }; - - if (nextFrame) { - if (typeof requestAnimationFrame === 'function') { - requestAnimationFrame(runner); - } - return; - } - - if (typeof queueMicrotask === 'function') { - queueMicrotask(runner); - } else { - Promise.resolve().then(runner); - } }, - [callback, nextFrame], + [callback], ); + React.useLayoutEffect(() => { + if (scheduledCallbackRef.current) { + scheduledCallbackRef.current(); + } + }); + return schedule; } From 8e542b8d954d8b14ffabf8c03c362adb5bd351eb Mon Sep 17 00:00:00 2001 From: lauri865 Date: Fri, 10 Oct 2025 18:48:51 +0200 Subject: [PATCH 05/60] use store effect equality check --- .../x-internals/src/store/useStoreEffect.ts | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/x-internals/src/store/useStoreEffect.ts b/packages/x-internals/src/store/useStoreEffect.ts index 8a298a1ef15d4..d03815243b47a 100644 --- a/packages/x-internals/src/store/useStoreEffect.ts +++ b/packages/x-internals/src/store/useStoreEffect.ts @@ -1,6 +1,7 @@ import useLazyRef from '@mui/utils/useLazyRef'; import useOnMount from '@mui/utils/useOnMount'; import type { ReadonlyStore } from './Store'; +import { fastObjectShallowCompare } from '../fastObjectShallowCompare'; const noop = () => {}; @@ -36,7 +37,9 @@ function initialize(params?: { subscribe: () => { instance.dispose ??= store.subscribe((state) => { const nextState = selector(state); - instance.effect(previousState, nextState); + if (!argsEqual(previousState, nextState)) { + instance.effect(previousState, nextState); + } previousState = nextState; }); }, @@ -53,3 +56,22 @@ function initialize(params?: { return instance; } + +const objectShallowCompare = fastObjectShallowCompare as (a: unknown, b: unknown) => boolean; +const arrayShallowCompare = (a: any[], b: any[]) => { + if (a === b) { + return true; + } + + return a.length === b.length && a.every((v, i) => v === b[i]); +}; + +const argsEqual = (prev: any, curr: any) => { + let fn = Object.is; + if (curr instanceof Array) { + fn = arrayShallowCompare; + } else if (curr instanceof Object) { + fn = objectShallowCompare; + } + return fn(prev, curr); +}; From 86d7e4974f31d80da35f593c790b82e355cc5ec2 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Sat, 11 Oct 2025 00:28:25 +0200 Subject: [PATCH 06/60] wip --- .../x-data-grid/src/hooks/features/filter/useGridFilter.tsx | 5 ++++- .../x-data-grid/src/hooks/features/sorting/useGridSorting.ts | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx b/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx index dd6fb2876f6ba..c99860187bfd4 100644 --- a/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx +++ b/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx @@ -20,7 +20,7 @@ import { GridPreferencePanelsValue } from '../preferencesPanel/gridPreferencePan import { defaultGridFilterLookup, getDefaultGridFilterModel } from './gridFilterState'; import { gridFilterModelSelector } from './gridFilterSelector'; import { useFirstRender } from '../../utils/useFirstRender'; -import { gridRowsLookupSelector } from '../rows'; +import { gridRowCountSelector, gridRowsLookupSelector } from '../rows'; import { GridPipeProcessor, useGridRegisterPipeProcessor } from '../../core/pipeProcessing'; import { GRID_DEFAULT_STRATEGY, @@ -109,6 +109,9 @@ export const useGridFilter = ( }); const updateFilteredRows = React.useCallback(() => { + if (!gridRowCountSelector(apiRef)) { + return; + } let didChange = false; apiRef.current.setState((state) => { const filterModel = gridFilterModelSelector(apiRef); diff --git a/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts b/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts index 92d51fc685f46..e2302f8230ac0 100644 --- a/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts +++ b/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts @@ -18,7 +18,7 @@ import { gridSortedRowIdsSelector, gridSortModelSelector, } from './gridSortingSelector'; -import { GRID_ROOT_GROUP_ID, gridRowTreeSelector } from '../rows'; +import { GRID_ROOT_GROUP_ID, gridRowCountSelector, gridRowTreeSelector } from '../rows'; import { useFirstRender } from '../../utils/useFirstRender'; import { useGridRegisterStrategyProcessor, @@ -141,6 +141,9 @@ export const useGridSorting = ( * API METHODS */ const applySorting = React.useCallback(() => { + if (!gridRowCountSelector(apiRef)) { + return; + } let didChange = false; apiRef.current.setState((state) => { if (props.sortingMode === 'server') { From f2d411b4c7d7188d46d2b78159eaaba6fe688a8e Mon Sep 17 00:00:00 2001 From: lauri865 Date: Mon, 13 Oct 2025 11:28:44 +0200 Subject: [PATCH 07/60] test opts --- .../aggregation/useGridAggregation.ts | 4 --- .../detailPanel/useGridDetailPanel.ts | 5 ++-- .../src/hooks/core/useGridProps.ts | 5 ++++ .../src/hooks/core/useGridVirtualizer.tsx | 30 +++++++++++-------- .../columnGrouping/useGridColumnGrouping.ts | 3 ++ .../hooks/features/filter/useGridFilter.tsx | 18 +++++++---- .../hooks/features/sorting/useGridSorting.ts | 12 +------- .../x-internals/src/store/useStoreEffect.ts | 21 +------------ .../x-virtualizer/src/features/dimensions.ts | 3 +- 9 files changed, 43 insertions(+), 58 deletions(-) diff --git a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts index 4bf3e104fe520..be884fa3b0be3 100644 --- a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts +++ b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts @@ -91,10 +91,6 @@ export const useGridAggregation = ( const abortControllerRef = React.useRef(null); const applyAggregation = React.useCallback(() => { - const rowCount = gridRowCountSelector(apiRef); - if (!rowCount) { - return; - } const aggregationRules = getAggregationRules( gridColumnLookupSelector(apiRef), gridAggregationModelSelector(apiRef), diff --git a/packages/x-data-grid-pro/src/hooks/features/detailPanel/useGridDetailPanel.ts b/packages/x-data-grid-pro/src/hooks/features/detailPanel/useGridDetailPanel.ts index 77fa4167632da..0aa02d7b47bca 100644 --- a/packages/x-data-grid-pro/src/hooks/features/detailPanel/useGridDetailPanel.ts +++ b/packages/x-data-grid-pro/src/hooks/features/detailPanel/useGridDetailPanel.ts @@ -257,8 +257,9 @@ export const useGridDetailPanel = ( const updateCachesIfNeeded = React.useCallback(() => { if ( - props.getDetailPanelContent === previousGetDetailPanelContentProp.current && - props.getDetailPanelHeight === previousGetDetailPanelHeightProp.current + (props.getDetailPanelContent === previousGetDetailPanelContentProp.current && + props.getDetailPanelHeight === previousGetDetailPanelHeightProp.current) || + !props.getDetailPanelContent ) { return; } diff --git a/packages/x-data-grid/src/hooks/core/useGridProps.ts b/packages/x-data-grid/src/hooks/core/useGridProps.ts index dccb31b63a077..34d5a747fa0e7 100644 --- a/packages/x-data-grid/src/hooks/core/useGridProps.ts +++ b/packages/x-data-grid/src/hooks/core/useGridProps.ts @@ -22,7 +22,12 @@ export const useGridProps = ( apiRef: RefObject, props: Props, ) => { + const isFirstRender = React.useRef(true); React.useEffect(() => { + if (isFirstRender.current) { + isFirstRender.current = false; + return; + } apiRef.current.setState((state: GridStateCommunity) => ({ ...state, props: { diff --git a/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx b/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx index d9f242b88c0dd..3e7836dec72f3 100644 --- a/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx +++ b/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx @@ -6,7 +6,7 @@ import { RefObject } from '@mui/x-internals/types'; import { roundToDecimalPlaces } from '@mui/x-internals/math'; import { lruMemoize } from '@mui/x-internals/lruMemoize'; import { useStoreEffect } from '@mui/x-internals/store'; -import { useVirtualizer, Dimensions, VirtualizerParams } from '@mui/x-virtualizer'; +import { useVirtualizer, Dimensions, VirtualizerParams, Virtualization } from '@mui/x-virtualizer'; import { useFirstRender } from '../utils/useFirstRender'; import { GridPrivateApiCommunity } from '../../models/api/gridApiCommunity'; import { GridStateColDef } from '../../models/colDef/gridColDef'; @@ -38,10 +38,6 @@ import { import { getTotalHeaderHeight } from '../features/columns/gridColumnsUtils'; import { useGridOverlays } from '../features/overlays/useGridOverlays'; -function identity(x: T) { - return x; -} - type RootProps = DataGridProcessedProps; const columnsTotalWidthSelector = createSelector( @@ -351,18 +347,26 @@ export function useGridVirtualizer( })); }); - useStoreEffect(virtualizer.store, identity, (_, state) => { - if (state.rowsMeta !== apiRef.current.state.rowsMeta) { + useStoreEffect(virtualizer.store, Dimensions.selectors.rowsMeta, (_, rowsMeta) => { + if (rowsMeta !== apiRef.current.state.rowsMeta) { apiRef.current.setState((gridState) => ({ ...gridState, - rowsMeta: state.rowsMeta, + rowsMeta, })); } - if (state.virtualization !== apiRef.current.state.virtualization) { - apiRef.current.setState((gridState) => ({ - ...gridState, - virtualization: state.virtualization, - })); + }); + + useStoreEffect(virtualizer.store, Virtualization.selectors.renderContext, (_, renderContext) => { + if (renderContext !== apiRef.current.state.virtualization.renderContext) { + queueMicrotask(() => { + apiRef.current.setState((gridState) => ({ + ...gridState, + virtualization: { + ...gridState.virtualization, + renderContext, + }, + })); + }); } }); diff --git a/packages/x-data-grid/src/hooks/features/columnGrouping/useGridColumnGrouping.ts b/packages/x-data-grid/src/hooks/features/columnGrouping/useGridColumnGrouping.ts index 3c1278e25dc7e..ae170fa9d3f52 100644 --- a/packages/x-data-grid/src/hooks/features/columnGrouping/useGridColumnGrouping.ts +++ b/packages/x-data-grid/src/hooks/features/columnGrouping/useGridColumnGrouping.ts @@ -113,6 +113,9 @@ export const useGridColumnGrouping = ( const updateColumnGroupingState = React.useCallback( (columnGroupingModel: GridColumnGroupingModel | undefined) => { + if (!columnGroupingModel && !apiRef.current.caches.columnGrouping.lastColumnGroupingModel) { + return; + } apiRef.current.caches.columnGrouping.lastColumnGroupingModel = columnGroupingModel; // @ts-expect-error Move this logic to `Pro` package const pinnedColumns = apiRef.current.getPinnedColumns?.() ?? {}; diff --git a/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx b/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx index c99860187bfd4..97361068da59c 100644 --- a/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx +++ b/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx @@ -108,6 +108,7 @@ export const useGridFilter = ( changeEvent: 'filterModelChange', }); + const firstRender = React.useRef(true); const updateFilteredRows = React.useCallback(() => { if (!gridRowCountSelector(apiRef)) { return; @@ -129,16 +130,21 @@ export const useGridFilter = ( if ( visibleRowsLookupState !== state.visibleRowsLookup && - !(isObjectEmpty(visibleRowsLookupState) && !isObjectEmpty(state.visibleRowsLookup)) + !(isObjectEmpty(visibleRowsLookupState) && isObjectEmpty(state.visibleRowsLookup)) ) { didChange = true; - newState.visibleRowsLookup = visibleRowsLookupState; + return { + ...newState, + visibleRowsLookup: visibleRowsLookupState, + }; } - return { - ...newState, - visibleRowsLookup: visibleRowsLookupState, - }; + if (firstRender.current) { + firstRender.current = false; + didChange = true; + } + + return state; }); if (didChange) { apiRef.current.publishEvent('filteredRowsSet'); diff --git a/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts b/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts index e2302f8230ac0..31cbc1c5137a1 100644 --- a/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts +++ b/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts @@ -18,7 +18,7 @@ import { gridSortedRowIdsSelector, gridSortModelSelector, } from './gridSortingSelector'; -import { GRID_ROOT_GROUP_ID, gridRowCountSelector, gridRowTreeSelector } from '../rows'; +import { GRID_ROOT_GROUP_ID, gridRowTreeSelector } from '../rows'; import { useFirstRender } from '../../utils/useFirstRender'; import { useGridRegisterStrategyProcessor, @@ -141,9 +141,6 @@ export const useGridSorting = ( * API METHODS */ const applySorting = React.useCallback(() => { - if (!gridRowCountSelector(apiRef)) { - return; - } let didChange = false; apiRef.current.setState((state) => { if (props.sortingMode === 'server') { @@ -384,13 +381,6 @@ export const useGridSorting = ( useGridEvent(apiRef, 'columnsChange', handleColumnsChange); useGridEvent(apiRef, 'activeStrategyProcessorChange', handleStrategyProcessorChange); - /** - * 1ST RENDER - */ - useFirstRender(() => { - apiRef.current.applySorting(); - }); - /** * EFFECTS */ diff --git a/packages/x-internals/src/store/useStoreEffect.ts b/packages/x-internals/src/store/useStoreEffect.ts index d03815243b47a..e1cb0469e2507 100644 --- a/packages/x-internals/src/store/useStoreEffect.ts +++ b/packages/x-internals/src/store/useStoreEffect.ts @@ -37,7 +37,7 @@ function initialize(params?: { subscribe: () => { instance.dispose ??= store.subscribe((state) => { const nextState = selector(state); - if (!argsEqual(previousState, nextState)) { + if (!Object.is(previousState, nextState)) { instance.effect(previousState, nextState); } previousState = nextState; @@ -56,22 +56,3 @@ function initialize(params?: { return instance; } - -const objectShallowCompare = fastObjectShallowCompare as (a: unknown, b: unknown) => boolean; -const arrayShallowCompare = (a: any[], b: any[]) => { - if (a === b) { - return true; - } - - return a.length === b.length && a.every((v, i) => v === b[i]); -}; - -const argsEqual = (prev: any, curr: any) => { - let fn = Object.is; - if (curr instanceof Array) { - fn = arrayShallowCompare; - } else if (curr instanceof Object) { - fn = objectShallowCompare; - } - return fn(prev, curr); -}; diff --git a/packages/x-virtualizer/src/features/dimensions.ts b/packages/x-virtualizer/src/features/dimensions.ts index f608720d523b7..f67d1bbbf8897 100644 --- a/packages/x-virtualizer/src/features/dimensions.ts +++ b/packages/x-virtualizer/src/features/dimensions.ts @@ -428,9 +428,8 @@ function useRowsMeta( pinnedBottomRowsTotalHeight, }; - store.set('rowsMeta', rowsMeta); - if (didHeightsChange) { + store.set('rowsMeta', rowsMeta); updateDimensions(); } From 067a9606c4431c45889a445d6245fad69ed2329e Mon Sep 17 00:00:00 2001 From: lauri865 Date: Mon, 13 Oct 2025 12:20:58 +0200 Subject: [PATCH 08/60] fixes --- .../features/aggregation/useGridAggregation.ts | 14 +++++++------- .../src/hooks/core/useGridVirtualizer.tsx | 16 +++++++--------- .../src/hooks/features/filter/useGridFilter.tsx | 11 ++++------- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts index be884fa3b0be3..e337ee94c60ac 100644 --- a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts +++ b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts @@ -91,6 +91,13 @@ export const useGridAggregation = ( const abortControllerRef = React.useRef(null); const applyAggregation = React.useCallback(() => { + // Abort previous if we're proceeding + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + const abortController = new AbortController(); + abortControllerRef.current = abortController; + const aggregationRules = getAggregationRules( gridColumnLookupSelector(apiRef), gridAggregationModelSelector(apiRef), @@ -100,13 +107,6 @@ export const useGridAggregation = ( const aggregatedFields = Object.keys(aggregationRules); const currentAggregationLookup = gridAggregationLookupSelector(apiRef); - // Abort previous if we're proceeding - if (abortControllerRef.current) { - abortControllerRef.current.abort(); - } - const abortController = new AbortController(); - abortControllerRef.current = abortController; - const renderContext = gridRenderContextSelector(apiRef); const visibleColumns = gridVisibleColumnFieldsSelector(apiRef); diff --git a/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx b/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx index 3e7836dec72f3..153fad46597d0 100644 --- a/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx +++ b/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx @@ -358,15 +358,13 @@ export function useGridVirtualizer( useStoreEffect(virtualizer.store, Virtualization.selectors.renderContext, (_, renderContext) => { if (renderContext !== apiRef.current.state.virtualization.renderContext) { - queueMicrotask(() => { - apiRef.current.setState((gridState) => ({ - ...gridState, - virtualization: { - ...gridState.virtualization, - renderContext, - }, - })); - }); + apiRef.current.setState((gridState) => ({ + ...gridState, + virtualization: { + ...gridState.virtualization, + renderContext, + }, + })); } }); diff --git a/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx b/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx index 97361068da59c..604da105930c1 100644 --- a/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx +++ b/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx @@ -129,9 +129,11 @@ export const useGridFilter = ( const visibleRowsLookupState = getVisibleRowsLookupState(apiRef, newState); if ( - visibleRowsLookupState !== state.visibleRowsLookup && - !(isObjectEmpty(visibleRowsLookupState) && isObjectEmpty(state.visibleRowsLookup)) + (visibleRowsLookupState !== state.visibleRowsLookup && + !(isObjectEmpty(visibleRowsLookupState) && isObjectEmpty(state.visibleRowsLookup))) || + firstRender.current ) { + firstRender.current = false; didChange = true; return { ...newState, @@ -139,11 +141,6 @@ export const useGridFilter = ( }; } - if (firstRender.current) { - firstRender.current = false; - didChange = true; - } - return state; }); if (didChange) { From 2653c29312e5bf634c7da095947b9327ad94fe03 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Mon, 13 Oct 2025 12:26:45 +0200 Subject: [PATCH 09/60] wip --- .../x-data-grid/src/hooks/features/sorting/useGridSorting.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts b/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts index 31cbc1c5137a1..d2291b97e266f 100644 --- a/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts +++ b/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts @@ -19,7 +19,6 @@ import { gridSortModelSelector, } from './gridSortingSelector'; import { GRID_ROOT_GROUP_ID, gridRowTreeSelector } from '../rows'; -import { useFirstRender } from '../../utils/useFirstRender'; import { useGridRegisterStrategyProcessor, GridStrategyProcessor, From d136a1a096000d61ab333a3924718b0f92bbd702 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Mon, 13 Oct 2025 12:27:22 +0200 Subject: [PATCH 10/60] lint --- .../src/hooks/features/aggregation/useGridAggregation.ts | 1 - packages/x-internals/src/store/useStoreEffect.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts index e337ee94c60ac..41fb2764fdd3e 100644 --- a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts +++ b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts @@ -11,7 +11,6 @@ import { gridVisibleColumnFieldsSelector, gridSortModelSelector, gridRowMaximumTreeDepthSelector, - gridRowCountSelector, } from '@mui/x-data-grid-pro'; import { useGridRegisterPipeProcessor, diff --git a/packages/x-internals/src/store/useStoreEffect.ts b/packages/x-internals/src/store/useStoreEffect.ts index e1cb0469e2507..8daf7e771c002 100644 --- a/packages/x-internals/src/store/useStoreEffect.ts +++ b/packages/x-internals/src/store/useStoreEffect.ts @@ -1,7 +1,6 @@ import useLazyRef from '@mui/utils/useLazyRef'; import useOnMount from '@mui/utils/useOnMount'; import type { ReadonlyStore } from './Store'; -import { fastObjectShallowCompare } from '../fastObjectShallowCompare'; const noop = () => {}; From b568d28c9ce164c15508f8af48cbf02ca080d51a Mon Sep 17 00:00:00 2001 From: lauri865 Date: Mon, 13 Oct 2025 12:32:19 +0200 Subject: [PATCH 11/60] fix --- .../src/hooks/features/sorting/useGridSorting.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts b/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts index d2291b97e266f..92d51fc685f46 100644 --- a/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts +++ b/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts @@ -19,6 +19,7 @@ import { gridSortModelSelector, } from './gridSortingSelector'; import { GRID_ROOT_GROUP_ID, gridRowTreeSelector } from '../rows'; +import { useFirstRender } from '../../utils/useFirstRender'; import { useGridRegisterStrategyProcessor, GridStrategyProcessor, @@ -380,6 +381,13 @@ export const useGridSorting = ( useGridEvent(apiRef, 'columnsChange', handleColumnsChange); useGridEvent(apiRef, 'activeStrategyProcessorChange', handleStrategyProcessorChange); + /** + * 1ST RENDER + */ + useFirstRender(() => { + apiRef.current.applySorting(); + }); + /** * EFFECTS */ From 742b378749e83b2f8a50f4aa81456076aab2f60a Mon Sep 17 00:00:00 2001 From: lauri865 Date: Mon, 13 Oct 2025 13:35:21 +0200 Subject: [PATCH 12/60] fix --- .../src/hooks/features/aggregation/useGridAggregation.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts index 41fb2764fdd3e..d6bd93024900c 100644 --- a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts +++ b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts @@ -115,7 +115,9 @@ export const useGridAggregation = ( .slice(renderContext.firstColumnIndex, renderContext.lastColumnIndex + 1) .filter((field) => aggregatedFields.includes(field)); const visibleAggregatedFieldsWithSort = visibleAggregatedFields.concat( - sortFields.filter((field) => !visibleAggregatedFields.includes(field)), + sortFields.filter( + (field) => aggregationRules[field] && !visibleAggregatedFields.includes(field), + ), ); const hasAggregatedSortedField = @@ -296,6 +298,9 @@ export const useGridAggregation = ( !!props.dataSource, ); const aggregatedFields = Object.keys(aggregationRules); + if (!aggregatedFields.length) { + return; + } const needsSorting = shouldApplySorting(aggregationRules, aggregatedFields); if (!needsSorting) { return; From 900eeecb5d17c0d43a0e40ec0b5704ce7611995f Mon Sep 17 00:00:00 2001 From: lauri865 Date: Mon, 13 Oct 2025 17:55:27 +0200 Subject: [PATCH 13/60] refactor: virtualizer implemented in subcomponent --- .../useDataGridPremiumComponent.tsx | 7 +- .../DataGridPro/useDataGridProComponent.tsx | 4 - .../src/DataGrid/useDataGridComponent.tsx | 5 +- .../virtualization/GridVirtualScroller.tsx | 5 +- .../GridVirtualScrollerRenderZone.tsx | 2 +- .../src/hooks/core/useGridVirtualizer.tsx | 21 +- .../features/columns/useGridColumnSpanning.ts | 15 +- .../features/dimensions/useGridDimensions.ts | 9 +- .../hooks/features/rows/useGridRowSpanning.ts | 11 +- .../virtualization/useGridVirtualization.tsx | 13 +- .../x-virtualizer/src/features/dimensions.ts | 250 +++++++++--------- .../src/features/virtualization.ts | 59 +++-- 12 files changed, 211 insertions(+), 190 deletions(-) diff --git a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx index 916eedbbb3250..fe3787c2bf4b0 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx @@ -4,7 +4,6 @@ import { RefObject } from '@mui/x-internals/types'; import { useGridInitialization, useGridInitializeState, - useGridVirtualizer, useGridClipboard, useGridColumnMenu, useGridColumns, @@ -79,6 +78,7 @@ import { propsStateInitializer, rowReorderStateInitializer, type GridConfiguration, + useFirstRender, } from '@mui/x-data-grid-pro/internals'; import { useGridSelector } from '@mui/x-data-grid-pro'; import { GridPrivateApiPremium } from '../models/gridApiPremium'; @@ -202,7 +202,6 @@ export const useDataGridPremiumComponent = ( useGridInitializeState(aiAssistantStateInitializer, apiRef, props); useGridInitializeState(chartsIntegrationStateInitializer, apiRef, props); - useGridVirtualizer(apiRef, props); useGridSidebar(apiRef, props); useGridPivoting(apiRef, props, inProps.columns, inProps.rows); useGridRowGrouping(apiRef, props); @@ -231,7 +230,6 @@ export const useDataGridPremiumComponent = ( useGridColumnReorder(apiRef, props); useGridColumnResize(apiRef, props); useGridPagination(apiRef, props); - useGridRowsMeta(apiRef, props); useGridRowReorder(apiRef, props); useGridScroll(apiRef, props); useGridInfiniteLoader(apiRef, props); @@ -254,6 +252,9 @@ export const useDataGridPremiumComponent = ( useGridPivotingExportState(apiRef); // Should be the last thing to run, because all pre-processors should have been registered by now. + useFirstRender(() => { + apiRef.current.runAppliersForPendingProcessors(); + }); React.useEffect(() => { apiRef.current.runAppliersForPendingProcessors(); }); diff --git a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx index bb3dcc8f0a123..fbbe06fc1079c 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx @@ -4,7 +4,6 @@ import { RefObject } from '@mui/x-internals/types'; import { useGridInitialization, useGridInitializeState, - useGridVirtualizer, useGridClipboard, useGridColumnMenu, useGridColumns, @@ -24,7 +23,6 @@ import { useGridRows, useGridRowsPreProcessors, rowsStateInitializer, - useGridRowsMeta, useGridParamsApi, useGridRowSelection, useGridSorting, @@ -153,7 +151,6 @@ export const useDataGridProComponent = ( useGridInitializeState(rowsMetaStateInitializer, apiRef, props); useGridInitializeState(listViewStateInitializer, apiRef, props); - useGridVirtualizer(apiRef, props); useGridHeaderFiltering(apiRef, props); useGridTreeData(apiRef, props); useGridKeyboardNavigation(apiRef, props); @@ -176,7 +173,6 @@ export const useDataGridProComponent = ( useGridColumnReorder(apiRef, props); useGridColumnResize(apiRef, props); useGridPagination(apiRef, props); - useGridRowsMeta(apiRef, props); useGridRowReorder(apiRef, props); useGridScroll(apiRef, props); useGridInfiniteLoader(apiRef, props); diff --git a/packages/x-data-grid/src/DataGrid/useDataGridComponent.tsx b/packages/x-data-grid/src/DataGrid/useDataGridComponent.tsx index d799bc93ebf1f..a666bfdde0b8a 100644 --- a/packages/x-data-grid/src/DataGrid/useDataGridComponent.tsx +++ b/packages/x-data-grid/src/DataGrid/useDataGridComponent.tsx @@ -4,7 +4,6 @@ import { RefObject } from '@mui/x-internals/types'; import { DataGridProcessedProps } from '../models/props/DataGridProps'; import { GridPrivateApiCommunity } from '../models/api/gridApiCommunity'; import { useGridInitialization } from '../hooks/core/useGridInitialization'; -import { useGridVirtualizer } from '../hooks/core/useGridVirtualizer'; import { useGridInitializeState } from '../hooks/utils/useGridInitializeState'; import { useGridClipboard } from '../hooks/features/clipboard/useGridClipboard'; import { @@ -42,7 +41,7 @@ import { dimensionsStateInitializer, useGridDimensions, } from '../hooks/features/dimensions/useGridDimensions'; -import { rowsMetaStateInitializer, useGridRowsMeta } from '../hooks/features/rows/useGridRowsMeta'; +import { rowsMetaStateInitializer } from '../hooks/features/rows/useGridRowsMeta'; import { useGridStatePersistence } from '../hooks/features/statePersistence/useGridStatePersistence'; import { useGridColumnSpanning } from '../hooks/features/columns/useGridColumnSpanning'; import { @@ -105,7 +104,6 @@ export const useDataGridComponent = ( useGridInitializeState(rowsMetaStateInitializer, apiRef, props); useGridInitializeState(listViewStateInitializer, apiRef, props); - useGridVirtualizer(apiRef, props); useGridKeyboardNavigation(apiRef, props); useGridRowSelection(apiRef, props); useGridColumns(apiRef, props); @@ -122,7 +120,6 @@ export const useDataGridComponent = ( useGridDensity(apiRef, props); useGridColumnResize(apiRef, props); useGridPagination(apiRef, props); - useGridRowsMeta(apiRef, props); useGridScroll(apiRef, props); useGridColumnMenu(apiRef); useGridCsvExport(apiRef, props); diff --git a/packages/x-data-grid/src/components/virtualization/GridVirtualScroller.tsx b/packages/x-data-grid/src/components/virtualization/GridVirtualScroller.tsx index 7e7cbac3c6bc5..8e5670e038a42 100644 --- a/packages/x-data-grid/src/components/virtualization/GridVirtualScroller.tsx +++ b/packages/x-data-grid/src/components/virtualization/GridVirtualScroller.tsx @@ -29,6 +29,7 @@ import type { GridLoadingOverlayVariant, } from '../../hooks/features/overlays/gridOverlaysInterfaces'; import { GridApiCommunity } from '../../models/api/gridApiCommunity'; +import { useGridVirtualizer } from '../../hooks/core/useGridVirtualizer'; type OwnerState = Pick & { hasScrollX: boolean; @@ -100,7 +101,7 @@ function GridVirtualScroller(props: GridVirtualScrollerProps) { }; const classes = useUtilityClasses(ownerState); - const virtualScroller = apiRef.current.virtualizer.api.useVirtualization().getters; + const virtualScroller = useGridVirtualizer().api.getters; const { getContainerProps, @@ -112,7 +113,7 @@ function GridVirtualScroller(props: GridVirtualScrollerProps) { getScrollAreaProps, } = virtualScroller; - const rows = getRows(undefined, gridRowTreeSelector(apiRef)); + const rows = virtualScroller.getRows(undefined, gridRowTreeSelector(apiRef)); return ( diff --git a/packages/x-data-grid/src/components/virtualization/GridVirtualScrollerRenderZone.tsx b/packages/x-data-grid/src/components/virtualization/GridVirtualScrollerRenderZone.tsx index b5591f5200974..b49cefa299748 100644 --- a/packages/x-data-grid/src/components/virtualization/GridVirtualScrollerRenderZone.tsx +++ b/packages/x-data-grid/src/components/virtualization/GridVirtualScrollerRenderZone.tsx @@ -37,7 +37,7 @@ const GridVirtualScrollerRenderZone = forwardRef< const apiRef = useGridPrivateApiContext(); const rootProps = useGridRootProps(); const classes = useUtilityClasses(rootProps); - const offsetTop = apiRef.current.virtualizer.api.useVirtualization().getters.getOffsetTop(); + const offsetTop = apiRef.current.virtualizer.api.getters.getOffsetTop(); return ( /** * Virtualizer setup */ -export function useGridVirtualizer( - apiRef: RefObject, - rootProps: RootProps, -): void { +export function useGridVirtualizer() { const isRtl = useRtl(); + const rootProps = useGridRootProps(); + const apiRef = useGridPrivateApiContext(); const { listView } = rootProps; const visibleColumns = useGridSelector(apiRef, gridVisibleColumnDefinitionsSelector); @@ -334,7 +331,7 @@ export function useGridVirtualizer( apiRef.current.store.state.virtualization = virtualizer.store.state.virtualization; }); - useStoreEffect(virtualizer.store, Dimensions.selectors.dimensions, (_, dimensions) => { + useStoreEffect(virtualizer.store, Dimensions.selectors.dimensions, (prev, dimensions) => { apiRef.current.setState((gridState) => ({ ...gridState, dimensions: addGridDimensions( @@ -371,4 +368,8 @@ export function useGridVirtualizer( apiRef.current.register('private', { virtualizer, }); + + useGridRowsMeta(apiRef, rootProps); + + return virtualizer; } diff --git a/packages/x-data-grid/src/hooks/features/columns/useGridColumnSpanning.ts b/packages/x-data-grid/src/hooks/features/columns/useGridColumnSpanning.ts index a1921f528f507..2a7509ca5bb3a 100644 --- a/packages/x-data-grid/src/hooks/features/columns/useGridColumnSpanning.ts +++ b/packages/x-data-grid/src/hooks/features/columns/useGridColumnSpanning.ts @@ -12,12 +12,15 @@ import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity'; * @requires useGridParamsApi (method) */ export const useGridColumnSpanning = (apiRef: RefObject) => { - const virtualizer = apiRef.current.virtualizer; - - const resetColSpan: GridColumnSpanningPrivateApi['resetColSpan'] = virtualizer.api.resetColSpan; - const getCellColSpanInfo: GridColumnSpanningApi['unstable_getCellColSpanInfo'] = - virtualizer.api.getCellColSpanInfo; - const calculateColSpan = virtualizer.api.calculateColSpan; + const resetColSpan: GridColumnSpanningPrivateApi['resetColSpan'] = () => { + return apiRef.current.virtualizer.api.resetColSpan(); + }; + const getCellColSpanInfo: GridColumnSpanningApi['unstable_getCellColSpanInfo'] = (...params) => { + return apiRef.current.virtualizer.api.getCellColSpanInfo(...params); + }; + const calculateColSpan: GridColumnSpanningPrivateApi['calculateColSpan'] = (...params) => { + apiRef.current.virtualizer.api.calculateColSpan(...params); + }; const columnSpanningPublicApi: GridColumnSpanningApi = { unstable_getCellColSpanInfo: getCellColSpanInfo, diff --git a/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts b/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts index d11f339a6490d..e2268ffa8691d 100644 --- a/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts +++ b/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts @@ -106,8 +106,8 @@ const columnsTotalWidthSelector = createSelector( export function useGridDimensions(apiRef: RefObject, props: RootProps) { const virtualizer = apiRef.current.virtualizer; - const updateDimensions = virtualizer.api.updateDimensions; - const getViewportPageSize = virtualizer.api.getViewportPageSize; + const updateDimensions = virtualizer?.api.updateDimensions; + const getViewportPageSize = virtualizer?.api.getViewportPageSize; const getRootDimensions = React.useCallback(() => gridDimensionsSelector(apiRef), [apiRef]); @@ -171,6 +171,11 @@ export function useGridDimensions(apiRef: RefObject, pr apiRef.current.store, (s) => s.dimensions, (previous, next) => { + // not sure why previous can be undefined here + if (!next.isReady || !previous) { + return; + } + if (apiRef.current.rootElementRef.current) { setCSSVariables(apiRef.current.rootElementRef.current, next); } 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 9c64465523dad..893a7d638ddc6 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts @@ -234,10 +234,9 @@ export const useGridRowSpanning = ( apiRef: RefObject, props: Pick, ): void => { - const store = apiRef.current.virtualizer.store; - const updateRowSpanningState = React.useCallback( (renderContext: GridRenderContext, resetState: boolean = false) => { + const store = apiRef.current.virtualizer.store; const { range, rows: visibleRows } = getVisibleRows(apiRef); if (resetState && store.getSnapshot().rowSpanning !== EMPTY_STATE) { store.set('rowSpanning', EMPTY_STATE); @@ -291,7 +290,7 @@ export const useGridRowSpanning = ( store.set('rowSpanning', newState); }, - [apiRef, store], + [apiRef], ); // Reset events trigger a full re-computation of the row spanning state: @@ -320,6 +319,10 @@ export const useGridRowSpanning = ( useGridEvent(apiRef, 'columnsChange', runIf(props.rowSpanning, resetRowSpanningState)); React.useEffect(() => { + const store = apiRef.current.virtualizer?.store; + if (!store) { + return; + } if (!props.rowSpanning) { if (store.state.rowSpanning !== EMPTY_STATE) { store.set('rowSpanning', EMPTY_STATE); @@ -327,5 +330,5 @@ export const useGridRowSpanning = ( } else if (store.state.rowSpanning.caches === EMPTY_CACHES) { resetRowSpanningState(); } - }, [apiRef, store, resetRowSpanningState, props.rowSpanning]); + }, [apiRef, resetRowSpanningState, props.rowSpanning]); }; diff --git a/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualization.tsx b/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualization.tsx index 74e68c918fc22..9ba07c40a1ca6 100644 --- a/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualization.tsx +++ b/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualization.tsx @@ -42,7 +42,6 @@ export function useGridVirtualization( apiRef: RefObject, rootProps: RootProps, ): void { - const { virtualizer } = apiRef.current; const { autoHeight, disableVirtualization } = rootProps; /* @@ -50,6 +49,7 @@ export function useGridVirtualization( */ const setVirtualization = (enabled: boolean) => { + const { virtualizer } = apiRef.current; enabled &&= HAS_LAYOUT; virtualizer.store.set('virtualization', { ...virtualizer.store.state.virtualization, @@ -60,6 +60,7 @@ export function useGridVirtualization( }; const setColumnVirtualization = (enabled: boolean) => { + const { virtualizer } = apiRef.current; enabled &&= HAS_LAYOUT; virtualizer.store.set('virtualization', { ...virtualizer.store.state.virtualization, @@ -74,7 +75,10 @@ export function useGridVirtualization( useGridApiMethod(apiRef, api, 'public'); - const forceUpdateRenderContext = virtualizer.api.forceUpdateRenderContext; + const forceUpdateRenderContext = () => { + const { virtualizer } = apiRef.current; + virtualizer?.api.scheduleUpdateRenderContext(); + }; apiRef.current.register('private', { updateRenderContext: forceUpdateRenderContext, @@ -90,7 +94,10 @@ export function useGridVirtualization( /* eslint-disable react-hooks/exhaustive-deps */ React.useEffect(() => { + if (!apiRef.current.virtualizer) { + return; + } setVirtualization(!rootProps.disableVirtualization); - }, [disableVirtualization, autoHeight]); + }, [apiRef, disableVirtualization, autoHeight]); /* eslint-enable react-hooks/exhaustive-deps */ } diff --git a/packages/x-virtualizer/src/features/dimensions.ts b/packages/x-virtualizer/src/features/dimensions.ts index f67d1bbbf8897..730a6b63a880c 100644 --- a/packages/x-virtualizer/src/features/dimensions.ts +++ b/packages/x-virtualizer/src/features/dimensions.ts @@ -122,140 +122,147 @@ function useDimensions(store: Store, params: VirtualizerParams, _api: onResize, } = params; - const containerNode = refs.container.current; - - const updateDimensions = React.useCallback(() => { - if (isFirstSizing.current) { - return; - } - - const rootSize = selectors.rootSize(store.state); - const rowsMeta = selectors.rowsMeta(store.state); - - // All the floating point dimensions should be rounded to .1 decimal places to avoid subpixel rendering issues - // https://github.com/mui/mui-x/issues/9550#issuecomment-1619020477 - // https://github.com/mui/mui-x/issues/15721 - const scrollbarSize = measureScrollbarSize(containerNode, params.dimensions.scrollbarSize); - - const topContainerHeight = topPinnedHeight + rowsMeta.pinnedTopRowsTotalHeight; - const bottomContainerHeight = bottomPinnedHeight + rowsMeta.pinnedBottomRowsTotalHeight; + const updateDimensions = React.useCallback( + (firstUpdate?: boolean) => { + if (firstUpdate) { + isFirstSizing.current = false; + } + if (isFirstSizing.current) { + return; + } - const contentSize = { - width: columnsTotalWidth, - height: roundToDecimalPlaces(rowsMeta.currentPageTotalHeight, 1), - }; + const containerNode = refs.container.current; + const rootSize = selectors.rootSize(store.state); + const rowsMeta = selectors.rowsMeta(store.state); - let viewportOuterSize: Size; - let viewportInnerSize: Size; - let hasScrollX = false; - let hasScrollY = false; + // All the floating point dimensions should be rounded to .1 decimal places to avoid subpixel rendering issues + // https://github.com/mui/mui-x/issues/9550#issuecomment-1619020477 + // https://github.com/mui/mui-x/issues/15721 + const scrollbarSize = measureScrollbarSize(containerNode, params.dimensions.scrollbarSize); - if (params.autoHeight) { - hasScrollY = false; - hasScrollX = Math.round(columnsTotalWidth) > Math.round(rootSize.width); + const topContainerHeight = topPinnedHeight + rowsMeta.pinnedTopRowsTotalHeight; + const bottomContainerHeight = bottomPinnedHeight + rowsMeta.pinnedBottomRowsTotalHeight; - viewportOuterSize = { - width: rootSize.width, - height: topContainerHeight + bottomContainerHeight + contentSize.height, - }; - viewportInnerSize = { - width: Math.max(0, viewportOuterSize.width - (hasScrollY ? scrollbarSize : 0)), - height: Math.max(0, viewportOuterSize.height - (hasScrollX ? scrollbarSize : 0)), - }; - } else { - viewportOuterSize = { - width: rootSize.width, - height: rootSize.height, - }; - viewportInnerSize = { - width: Math.max(0, viewportOuterSize.width), - height: Math.max(0, viewportOuterSize.height - topContainerHeight - bottomContainerHeight), + const contentSize = { + width: columnsTotalWidth, + height: roundToDecimalPlaces(rowsMeta.currentPageTotalHeight, 1), }; - const content = contentSize; - const container = viewportInnerSize; - - const hasScrollXIfNoYScrollBar = content.width > container.width; - const hasScrollYIfNoXScrollBar = content.height > container.height; - - if (hasScrollXIfNoYScrollBar || hasScrollYIfNoXScrollBar) { - hasScrollY = hasScrollYIfNoXScrollBar; - hasScrollX = content.width + (hasScrollY ? scrollbarSize : 0) > container.width; + let viewportOuterSize: Size; + let viewportInnerSize: Size; + let hasScrollX = false; + let hasScrollY = false; + + if (params.autoHeight) { + hasScrollY = false; + hasScrollX = Math.round(columnsTotalWidth) > Math.round(rootSize.width); + + viewportOuterSize = { + width: rootSize.width, + height: topContainerHeight + bottomContainerHeight + contentSize.height, + }; + viewportInnerSize = { + width: Math.max(0, viewportOuterSize.width - (hasScrollY ? scrollbarSize : 0)), + height: Math.max(0, viewportOuterSize.height - (hasScrollX ? scrollbarSize : 0)), + }; + } else { + viewportOuterSize = { + width: rootSize.width, + height: rootSize.height, + }; + viewportInnerSize = { + width: Math.max(0, viewportOuterSize.width), + height: Math.max( + 0, + viewportOuterSize.height - topContainerHeight - bottomContainerHeight, + ), + }; + + const content = contentSize; + const container = viewportInnerSize; + + const hasScrollXIfNoYScrollBar = content.width > container.width; + const hasScrollYIfNoXScrollBar = content.height > container.height; + + if (hasScrollXIfNoYScrollBar || hasScrollYIfNoXScrollBar) { + hasScrollY = hasScrollYIfNoXScrollBar; + hasScrollX = content.width + (hasScrollY ? scrollbarSize : 0) > container.width; + + // We recalculate the scroll y to consider the size of the x scrollbar. + if (hasScrollX) { + hasScrollY = content.height + scrollbarSize > container.height; + } + } - // We recalculate the scroll y to consider the size of the x scrollbar. + if (hasScrollY) { + viewportInnerSize.width -= scrollbarSize; + } if (hasScrollX) { - hasScrollY = content.height + scrollbarSize > container.height; + viewportInnerSize.height -= scrollbarSize; } } - if (hasScrollY) { - viewportInnerSize.width -= scrollbarSize; + if (params.disableHorizontalScroll) { + hasScrollX = false; } - if (hasScrollX) { - viewportInnerSize.height -= scrollbarSize; + + if (params.disableVerticalScroll) { + hasScrollY = false; } - } - if (params.disableHorizontalScroll) { - hasScrollX = false; - } + const rowWidth = Math.max( + viewportOuterSize.width, + columnsTotalWidth + (hasScrollY ? scrollbarSize : 0), + ); - if (params.disableVerticalScroll) { - hasScrollY = false; - } + const minimumSize = { + width: columnsTotalWidth, + height: topContainerHeight + contentSize.height + bottomContainerHeight, + }; + + const newDimensions: DimensionsState = { + isReady: true, + root: rootSize, + viewportOuterSize, + viewportInnerSize, + contentSize, + minimumSize, + hasScrollX, + hasScrollY, + scrollbarSize, + rowWidth, + rowHeight, + columnsTotalWidth, + leftPinnedWidth, + rightPinnedWidth, + topContainerHeight, + bottomContainerHeight, + }; - const rowWidth = Math.max( - viewportOuterSize.width, - columnsTotalWidth + (hasScrollY ? scrollbarSize : 0), - ); + const prevDimensions = store.state.dimensions; - const minimumSize = { - width: columnsTotalWidth, - height: topContainerHeight + contentSize.height + bottomContainerHeight, - }; + if (isDeepEqual(prevDimensions as any, newDimensions)) { + return; + } - const newDimensions: DimensionsState = { - isReady: true, - root: rootSize, - viewportOuterSize, - viewportInnerSize, - contentSize, - minimumSize, - hasScrollX, - hasScrollY, - scrollbarSize, - rowWidth, + store.update({ dimensions: newDimensions }); + onResize?.(newDimensions.root); + }, + [ + store, + params.dimensions.scrollbarSize, + params.autoHeight, + params.disableHorizontalScroll, + params.disableVerticalScroll, + onResize, rowHeight, columnsTotalWidth, leftPinnedWidth, rightPinnedWidth, - topContainerHeight, - bottomContainerHeight, - }; - - const prevDimensions = store.state.dimensions; - - if (isDeepEqual(prevDimensions as any, newDimensions)) { - return; - } - - store.update({ dimensions: newDimensions }); - onResize?.(newDimensions.root); - }, [ - store, - containerNode, - params.dimensions.scrollbarSize, - params.autoHeight, - params.disableHorizontalScroll, - params.disableVerticalScroll, - onResize, - rowHeight, - columnsTotalWidth, - leftPinnedWidth, - rightPinnedWidth, - topPinnedHeight, - bottomPinnedHeight, - ]); + topPinnedHeight, + bottomPinnedHeight, + ], + ); const { resizeThrottleMs } = params; const updateDimensionCallback = useEventCallback(updateDimensions); @@ -265,23 +272,6 @@ function useDimensions(store: Store, params: VirtualizerParams, _api: ); React.useEffect(() => debouncedUpdateDimensions?.clear, [debouncedUpdateDimensions]); - const setRootSize = useEventCallback((rootSize: Size) => { - store.state.rootSize = rootSize; - - if (isFirstSizing.current || !debouncedUpdateDimensions) { - // We want to initialize the grid dimensions as soon as possible to avoid flickering - isFirstSizing.current = false; - updateDimensions(); - } else { - debouncedUpdateDimensions(); - } - }); - - useLayoutEffect( - () => observeRootNode(containerNode, store, setRootSize), - [containerNode, store, setRootSize], - ); - useLayoutEffect(updateDimensions, [updateDimensions]); const rowsMeta = useRowsMeta(store, params, updateDimensions); @@ -527,7 +517,7 @@ function useRowsMeta( }; } -function observeRootNode( +export function observeRootNode( node: Element | null, store: Store, setRootSize: (size: Size) => void, diff --git a/packages/x-virtualizer/src/features/virtualization.ts b/packages/x-virtualizer/src/features/virtualization.ts index 98c0c4bc77612..36952235f5fb7 100644 --- a/packages/x-virtualizer/src/features/virtualization.ts +++ b/packages/x-virtualizer/src/features/virtualization.ts @@ -10,9 +10,9 @@ import * as platform from '@mui/x-internals/platform'; import { useRunOnce } from '@mui/x-internals/useRunOnce'; import { useFirstRender } from '@mui/x-internals/useFirstRender'; import { createSelector, useStore, useStoreEffect, Store } from '@mui/x-internals/store'; -import { PinnedRows, PinnedColumns } from '../models/core'; +import { PinnedRows, PinnedColumns, Size } from '../models/core'; import type { CellColSpanInfo } from '../models/colspan'; -import { Dimensions } from './dimensions'; +import { Dimensions, observeRootNode } from './dimensions'; import type { BaseState, VirtualizerParams } from '../useVirtualizer'; import { PinnedRowPosition, @@ -139,7 +139,7 @@ function useVirtualization(store: Store, params: VirtualizerParams, a const hasBottomPinnedRows = pinnedRows.bottom.length > 0; const [panels, setPanels] = React.useState(EMPTY_DETAIL_PANELS); - const [, setRefTick] = React.useState(0); + const isUpdateScheduled = React.useRef(false); const isRenderContextReady = React.useRef(false); @@ -306,6 +306,17 @@ function useVirtualization(store: Store, params: VirtualizerParams, a updateRenderContext(nextRenderContext); }); + useEnhancedEffect(() => { + if (isUpdateScheduled.current) { + forceUpdateRenderContext(); + isUpdateScheduled.current = false; + } + }); + + const scheduleUpdateRenderContext = () => { + isUpdateScheduled.current = true; + }; + const handleScroll = useEventCallback(() => { if (ignoreNextScrollEvent.current) { ignoreNextScrollEvent.current = false; @@ -606,19 +617,35 @@ function useVirtualization(store: Store, params: VirtualizerParams, a useStoreEffect(store, Dimensions.selectors.dimensions, forceUpdateRenderContext); - const refSetter = (name: keyof typeof refs) => (node: HTMLDivElement | null) => { - if (node && refs[name].current !== node) { - refs[name].current = node; - setRefTick((tick) => tick + 1); - } - }; + const refSetter = React.useCallback( + (name: keyof typeof refs, cb?: (node: HTMLDivElement | null) => void) => + (node: HTMLDivElement | null) => { + if (node && refs[name].current !== node) { + refs[name].current = node; + return cb?.(node); + } + }, + [refs], + ); + const isFirstSizing = React.useRef(true); const getters = { setPanels, getOffsetTop, getRows, getContainerProps: () => ({ - ref: refSetter('container'), + ref: refSetter('container', (node) => { + return observeRootNode(node, store, (rootSize: Size) => { + store.state.rootSize = rootSize; + if (isFirstSizing.current || !api.debouncedUpdateDimensions) { + // We want to initialize the grid dimensions as soon as possible to avoid flickering + api.updateDimensions(isFirstSizing.current); + isFirstSizing.current = false; + } else { + api.debouncedUpdateDimensions(); + } + }); + }), }), getScrollerProps: () => ({ ref: refSetter('scroller'), @@ -649,17 +676,6 @@ function useVirtualization(store: Store, params: VirtualizerParams, a }), }; - useFirstRender(() => { - store.state = { - ...store.state, - getters, - }; - }); - React.useEffect(() => { - store.update({ getters }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, Object.values(getters)); - /* Placeholder API functions for colspan & rowspan to re-implement */ const getCellColSpanInfo: AbstractAPI['getCellColSpanInfo'] = () => { @@ -679,6 +695,7 @@ function useVirtualization(store: Store, params: VirtualizerParams, a useVirtualization: () => useStore(store, (state) => state), setPanels, forceUpdateRenderContext, + scheduleUpdateRenderContext, getCellColSpanInfo, calculateColSpan, getHiddenCellsOrigin, From a03a136b99fc8bcf1934978e835d6992be64fcf6 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Mon, 13 Oct 2025 19:01:16 +0200 Subject: [PATCH 14/60] fix --- .../x-data-grid/src/hooks/features/sorting/useGridSorting.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts b/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts index 92d51fc685f46..f8221ca7cacd7 100644 --- a/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts +++ b/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts @@ -141,7 +141,7 @@ export const useGridSorting = ( * API METHODS */ const applySorting = React.useCallback(() => { - let didChange = false; + let didChange = props.sortingMode === 'server'; apiRef.current.setState((state) => { if (props.sortingMode === 'server') { logger.debug('Skipping sorting rows as sortingMode = server'); From 62b16f018f19436ba2e4dea150f4e1ad94e8819d Mon Sep 17 00:00:00 2001 From: lauri865 Date: Mon, 13 Oct 2025 19:36:46 +0200 Subject: [PATCH 15/60] fixes --- .../features/dimensions/useGridDimensions.ts | 24 +++++----- .../src/features/virtualization.ts | 44 +++++++++++-------- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts b/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts index e2268ffa8691d..eadc77d28b57a 100644 --- a/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts +++ b/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts @@ -74,18 +74,21 @@ export const dimensionsStateInitializer: GridStateInitializer = ( const dimensions = EMPTY_DIMENSIONS; const density = gridDensityFactorSelector(apiRef); + const dimensionsWithStatic = { + ...dimensions, + ...getStaticDimensions( + props, + apiRef, + density, + gridVisiblePinnedColumnDefinitionsSelector(apiRef), + ), + }; + + apiRef.current.store.state.dimensions = dimensionsWithStatic; return { ...state, - dimensions: { - ...dimensions, - ...getStaticDimensions( - props, - apiRef, - density, - gridVisiblePinnedColumnDefinitionsSelector(apiRef), - ), - }, + dimensions: dimensionsWithStatic, }; }; @@ -171,8 +174,7 @@ export function useGridDimensions(apiRef: RefObject, pr apiRef.current.store, (s) => s.dimensions, (previous, next) => { - // not sure why previous can be undefined here - if (!next.isReady || !previous) { + if (!next.isReady) { return; } diff --git a/packages/x-virtualizer/src/features/virtualization.ts b/packages/x-virtualizer/src/features/virtualization.ts index 36952235f5fb7..fa3df875da854 100644 --- a/packages/x-virtualizer/src/features/virtualization.ts +++ b/packages/x-virtualizer/src/features/virtualization.ts @@ -618,34 +618,38 @@ function useVirtualization(store: Store, params: VirtualizerParams, a useStoreEffect(store, Dimensions.selectors.dimensions, forceUpdateRenderContext); const refSetter = React.useCallback( - (name: keyof typeof refs, cb?: (node: HTMLDivElement | null) => void) => - (node: HTMLDivElement | null) => { - if (node && refs[name].current !== node) { - refs[name].current = node; - return cb?.(node); - } - }, + (name: keyof typeof refs) => (node: HTMLDivElement | null) => { + if (node && refs[name].current !== node) { + refs[name].current = node; + } + }, [refs], ); + const containerRef = useEventCallback((node: HTMLDivElement | null) => { + if (node && refs.container.current !== node) { + refs.container.current = node; + return observeRootNode(node, store, (rootSize: Size) => { + store.state.rootSize = rootSize; + store.update({ rootSize }); + if (isFirstSizing.current || !api.debouncedUpdateDimensions) { + // We want to initialize the grid dimensions as soon as possible to avoid flickering + api.updateDimensions(isFirstSizing.current); + isFirstSizing.current = false; + } else { + api.debouncedUpdateDimensions(); + } + }); + } + }); + const isFirstSizing = React.useRef(true); const getters = { setPanels, getOffsetTop, getRows, getContainerProps: () => ({ - ref: refSetter('container', (node) => { - return observeRootNode(node, store, (rootSize: Size) => { - store.state.rootSize = rootSize; - if (isFirstSizing.current || !api.debouncedUpdateDimensions) { - // We want to initialize the grid dimensions as soon as possible to avoid flickering - api.updateDimensions(isFirstSizing.current); - isFirstSizing.current = false; - } else { - api.debouncedUpdateDimensions(); - } - }); - }), + ref: containerRef, }), getScrollerProps: () => ({ ref: refSetter('scroller'), @@ -831,7 +835,9 @@ function computeRenderContext( renderContext.lastColumnIndex = lastColumnIndex; } + console.log('computed render context', renderContext.lastRowIndex); const actualRenderContext = deriveRenderContext(inputs, renderContext, scrollCache); + console.log('computed render context', actualRenderContext, inputs, renderContext); return actualRenderContext; } From 30a1e05a31ce80d1701fc4bff1f250e4c014114e Mon Sep 17 00:00:00 2001 From: lauri865 Date: Mon, 13 Oct 2025 19:38:48 +0200 Subject: [PATCH 16/60] console --- packages/x-virtualizer/src/features/virtualization.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/x-virtualizer/src/features/virtualization.ts b/packages/x-virtualizer/src/features/virtualization.ts index fa3df875da854..cd045e0d26e7f 100644 --- a/packages/x-virtualizer/src/features/virtualization.ts +++ b/packages/x-virtualizer/src/features/virtualization.ts @@ -835,9 +835,7 @@ function computeRenderContext( renderContext.lastColumnIndex = lastColumnIndex; } - console.log('computed render context', renderContext.lastRowIndex); const actualRenderContext = deriveRenderContext(inputs, renderContext, scrollCache); - console.log('computed render context', actualRenderContext, inputs, renderContext); return actualRenderContext; } From 8f2537bdf9ca3e18873f8fb710ca6ae26224d96c Mon Sep 17 00:00:00 2001 From: lauri865 Date: Mon, 13 Oct 2025 19:53:06 +0200 Subject: [PATCH 17/60] fixes --- .../src/hooks/features/aggregation/useGridAggregation.ts | 6 ++++++ .../src/hooks/features/filter/useGridFilter.tsx | 2 +- packages/x-data-grid/src/hooks/utils/useRunOncePerLoop.ts | 7 +++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts index d6bd93024900c..034f8166d9247 100644 --- a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts +++ b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts @@ -317,4 +317,10 @@ export const useGridAggregation = ( apiRef.current.setAggregationModel(props.aggregationModel); } }, [apiRef, props.aggregationModel]); + + React.useEffect(() => { + if (props.getAggregationPosition !== undefined) { + deferredApplyAggregation(); + } + }, [props.getAggregationPosition]); }; diff --git a/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx b/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx index 604da105930c1..1575bb632b040 100644 --- a/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx +++ b/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx @@ -141,7 +141,7 @@ export const useGridFilter = ( }; } - return state; + return newState; }); if (didChange) { apiRef.current.publishEvent('filteredRowsSet'); diff --git a/packages/x-data-grid/src/hooks/utils/useRunOncePerLoop.ts b/packages/x-data-grid/src/hooks/utils/useRunOncePerLoop.ts index cd987f0676cc3..03507d9c4811c 100644 --- a/packages/x-data-grid/src/hooks/utils/useRunOncePerLoop.ts +++ b/packages/x-data-grid/src/hooks/utils/useRunOncePerLoop.ts @@ -10,6 +10,13 @@ export function useRunOncePerLoop void>(callback: scheduledCallbackRef.current = null; callback(...args); }; + + // fallback in case no state is being set + queueMicrotask(() => { + if (scheduledCallbackRef.current) { + scheduledCallbackRef.current(); + } + }); }, [callback], ); From 352b5c303e4b91abf2a33e94a1228c13b5dccacc Mon Sep 17 00:00:00 2001 From: lauri865 Date: Mon, 13 Oct 2025 21:05:30 +0200 Subject: [PATCH 18/60] fix --- .../aggregation/useGridAggregation.ts | 25 +++++++++++-------- .../src/hooks/utils/useRunOncePerLoop.ts | 7 ------ 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts index 034f8166d9247..70342cef78b7e 100644 --- a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts +++ b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts @@ -16,6 +16,7 @@ import { useGridRegisterPipeProcessor, GridStateInitializer, GridPipeProcessor, + gridPivotActiveSelector, } from '@mui/x-data-grid-pro/internals'; import { DataGridPremiumProcessedProps } from '../../../models/dataGridPremiumProps'; import { GridPrivateApiPremium } from '../../../models/gridApiPremium'; @@ -110,19 +111,19 @@ export const useGridAggregation = ( const visibleColumns = gridVisibleColumnFieldsSelector(apiRef); const chunks: string[][] = []; - const sortFields = gridSortModelSelector(apiRef).map((s) => s.field); + const sortedAggregatedFields = gridSortModelSelector(apiRef) + .map((s) => s.field) + .filter((field) => aggregatedFields.includes(field)); const visibleAggregatedFields = visibleColumns .slice(renderContext.firstColumnIndex, renderContext.lastColumnIndex + 1) .filter((field) => aggregatedFields.includes(field)); - const visibleAggregatedFieldsWithSort = visibleAggregatedFields.concat( - sortFields.filter( - (field) => aggregationRules[field] && !visibleAggregatedFields.includes(field), - ), - ); - + const visibleAggregatedFieldsWithSort = [ + ...visibleAggregatedFields, + ...sortedAggregatedFields.filter((field) => !visibleAggregatedFields.includes(field)), + ]; const hasAggregatedSortedField = - gridRowMaximumTreeDepthSelector(apiRef) > 1 && - sortFields.some((field) => aggregationRules[field]); + gridRowMaximumTreeDepthSelector(apiRef) > 1 && sortedAggregatedFields.length > 0; + if (visibleAggregatedFields.length > 0) { chunks.push(visibleAggregatedFieldsWithSort); } @@ -249,6 +250,7 @@ export const useGridAggregation = ( * EVENTS */ const checkAggregationRulesDiff = React.useCallback(() => { + const pivotingActive = gridPivotActiveSelector(apiRef); const { rulesOnLastRowHydration, rulesOnLastColumnHydration } = apiRef.current.caches.aggregation; @@ -262,7 +264,10 @@ export const useGridAggregation = ( ); // Re-apply the row hydration to add / remove the aggregation footers - if (!props.dataSource && !areAggregationRulesEqual(rulesOnLastRowHydration, aggregationRules)) { + if ( + (!props.dataSource || pivotingActive) && + !areAggregationRulesEqual(rulesOnLastRowHydration, aggregationRules) + ) { apiRef.current.requestPipeProcessorsApplication('hydrateRows'); deferredApplyAggregation(); } diff --git a/packages/x-data-grid/src/hooks/utils/useRunOncePerLoop.ts b/packages/x-data-grid/src/hooks/utils/useRunOncePerLoop.ts index 03507d9c4811c..cd987f0676cc3 100644 --- a/packages/x-data-grid/src/hooks/utils/useRunOncePerLoop.ts +++ b/packages/x-data-grid/src/hooks/utils/useRunOncePerLoop.ts @@ -10,13 +10,6 @@ export function useRunOncePerLoop void>(callback: scheduledCallbackRef.current = null; callback(...args); }; - - // fallback in case no state is being set - queueMicrotask(() => { - if (scheduledCallbackRef.current) { - scheduledCallbackRef.current(); - } - }); }, [callback], ); From 0a5f3f06653d07d70d47562b1fe3444967dcc667 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Mon, 13 Oct 2025 21:48:44 +0200 Subject: [PATCH 19/60] fix --- .../hooks/features/dimensions/useGridDimensions.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts b/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts index eadc77d28b57a..9092517a9a39a 100644 --- a/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts +++ b/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts @@ -108,10 +108,6 @@ const columnsTotalWidthSelector = createSelector( ); export function useGridDimensions(apiRef: RefObject, props: RootProps) { - const virtualizer = apiRef.current.virtualizer; - const updateDimensions = virtualizer?.api.updateDimensions; - const getViewportPageSize = virtualizer?.api.getViewportPageSize; - const getRootDimensions = React.useCallback(() => gridDimensionsSelector(apiRef), [apiRef]); const apiPublic: GridDimensionsApi = { @@ -119,8 +115,12 @@ export function useGridDimensions(apiRef: RefObject, pr }; const apiPrivate: GridDimensionsPrivateApi = { - updateDimensions, - getViewportPageSize, + updateDimensions: () => { + return apiRef.current.virtualizer.api.updateDimensions(); + }, + getViewportPageSize: () => { + return apiRef.current.virtualizer.api.getViewportPageSize(); + }, }; useGridApiMethod(apiRef, apiPublic, 'public'); From 0d345b5a5b199ec062163204c620edb18005d34f Mon Sep 17 00:00:00 2001 From: lauri865 Date: Mon, 13 Oct 2025 22:06:54 +0200 Subject: [PATCH 20/60] tst --- .../hooks/features/filter/useGridFilter.tsx | 30 ++++--------------- .../hooks/features/sorting/useGridSorting.ts | 22 ++++---------- .../virtualization/useGridVirtualization.tsx | 1 + 3 files changed, 12 insertions(+), 41 deletions(-) diff --git a/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx b/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx index 1575bb632b040..4d774bfcbf924 100644 --- a/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx +++ b/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx @@ -3,7 +3,6 @@ import { lruMemoize } from '@mui/x-internals/lruMemoize'; import { RefObject } from '@mui/x-internals/types'; import useEnhancedEffect from '@mui/utils/useEnhancedEffect'; import { isDeepEqual } from '@mui/x-internals/isDeepEqual'; -import { isObjectEmpty } from '@mui/x-internals/isObjectEmpty'; import { GridEventListener } from '../../../models/events'; import { DataGridProcessedProps } from '../../../models/props/DataGridProps'; import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity'; @@ -20,7 +19,7 @@ import { GridPreferencePanelsValue } from '../preferencesPanel/gridPreferencePan import { defaultGridFilterLookup, getDefaultGridFilterModel } from './gridFilterState'; import { gridFilterModelSelector } from './gridFilterSelector'; import { useFirstRender } from '../../utils/useFirstRender'; -import { gridRowCountSelector, gridRowsLookupSelector } from '../rows'; +import { gridRowsLookupSelector } from '../rows'; import { GridPipeProcessor, useGridRegisterPipeProcessor } from '../../core/pipeProcessing'; import { GRID_DEFAULT_STRATEGY, @@ -108,12 +107,7 @@ export const useGridFilter = ( changeEvent: 'filterModelChange', }); - const firstRender = React.useRef(true); const updateFilteredRows = React.useCallback(() => { - if (!gridRowCountSelector(apiRef)) { - return; - } - let didChange = false; apiRef.current.setState((state) => { const filterModel = gridFilterModelSelector(apiRef); const filterState = apiRef.current.getFilterState(filterModel); @@ -128,24 +122,12 @@ export const useGridFilter = ( const visibleRowsLookupState = getVisibleRowsLookupState(apiRef, newState); - if ( - (visibleRowsLookupState !== state.visibleRowsLookup && - !(isObjectEmpty(visibleRowsLookupState) && isObjectEmpty(state.visibleRowsLookup))) || - firstRender.current - ) { - firstRender.current = false; - didChange = true; - return { - ...newState, - visibleRowsLookup: visibleRowsLookupState, - }; - } - - return newState; + return { + ...newState, + visibleRowsLookup: visibleRowsLookupState, + }; }); - if (didChange) { - apiRef.current.publishEvent('filteredRowsSet'); - } + apiRef.current.publishEvent('filteredRowsSet'); }, [apiRef]); const addColumnMenuItem = React.useCallback>( diff --git a/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts b/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts index f8221ca7cacd7..a40c01f7bd4b1 100644 --- a/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts +++ b/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts @@ -1,7 +1,6 @@ import * as React from 'react'; import { RefObject } from '@mui/x-internals/types'; import useEnhancedEffect from '@mui/utils/useEnhancedEffect'; -import { isObjectEmpty } from '@mui/x-internals/isObjectEmpty'; import { GridEventListener } from '../../../models/events'; import { DataGridProcessedProps } from '../../../models/props/DataGridProps'; import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity'; @@ -141,7 +140,6 @@ export const useGridSorting = ( * API METHODS */ const applySorting = React.useCallback(() => { - let didChange = props.sortingMode === 'server'; apiRef.current.setState((state) => { if (props.sortingMode === 'server') { logger.debug('Skipping sorting rows as sortingMode = server'); @@ -164,23 +162,13 @@ export const useGridSorting = ( sortRowList, }); - if ( - sortedRows !== state.sorting.sortedRows && - !(isObjectEmpty(sortedRows) && isObjectEmpty(state.sorting.sortedRows)) - ) { - didChange = true; - return { - ...state, - sorting: { ...state.sorting, sortedRows }, - }; - } - - return state; + return { + ...state, + sorting: { ...state.sorting, sortedRows }, + }; }); - if (didChange) { - apiRef.current.publishEvent('sortedRowsSet'); - } + apiRef.current.publishEvent('sortedRowsSet'); }, [apiRef, logger, props.sortingMode]); const setSortModel = React.useCallback( diff --git a/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualization.tsx b/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualization.tsx index 9ba07c40a1ca6..13cd3d11e068e 100644 --- a/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualization.tsx +++ b/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualization.tsx @@ -89,6 +89,7 @@ export function useGridVirtualization( */ useGridEventPriority(apiRef, 'sortedRowsSet', forceUpdateRenderContext); + useGridEventPriority(apiRef, 'filteredRowsSet', forceUpdateRenderContext); useGridEventPriority(apiRef, 'paginationModelChange', forceUpdateRenderContext); useGridEventPriority(apiRef, 'columnsChange', forceUpdateRenderContext); From 4cb956b3dacbba54cf107c71adb11a2e3236b958 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Mon, 13 Oct 2025 22:54:35 +0200 Subject: [PATCH 21/60] dim --- packages/x-virtualizer/src/features/dimensions.ts | 2 +- packages/x-virtualizer/src/features/virtualization.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/x-virtualizer/src/features/dimensions.ts b/packages/x-virtualizer/src/features/dimensions.ts index 730a6b63a880c..9fe59422ab433 100644 --- a/packages/x-virtualizer/src/features/dimensions.ts +++ b/packages/x-virtualizer/src/features/dimensions.ts @@ -418,8 +418,8 @@ function useRowsMeta( pinnedBottomRowsTotalHeight, }; + store.set('rowsMeta', rowsMeta); if (didHeightsChange) { - store.set('rowsMeta', rowsMeta); updateDimensions(); } diff --git a/packages/x-virtualizer/src/features/virtualization.ts b/packages/x-virtualizer/src/features/virtualization.ts index cd045e0d26e7f..84b8abd48a647 100644 --- a/packages/x-virtualizer/src/features/virtualization.ts +++ b/packages/x-virtualizer/src/features/virtualization.ts @@ -306,6 +306,12 @@ function useVirtualization(store: Store, params: VirtualizerParams, a updateRenderContext(nextRenderContext); }); + useStoreEffect(store, Dimensions.selectors.dimensions, (previous, next) => { + if (next.isReady) { + forceUpdateRenderContext; + } + }); + useEnhancedEffect(() => { if (isUpdateScheduled.current) { forceUpdateRenderContext(); From 87db6a267b89f500a12a397b943296f3c8c05252 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Mon, 13 Oct 2025 23:32:53 +0200 Subject: [PATCH 22/60] virt --- .../src/hooks/core/useGridVirtualizer.tsx | 20 ++++++++++++------- .../src/features/virtualization.ts | 9 +++++---- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx b/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx index c63c6d7c2d6f4..fd991b07927be 100644 --- a/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx +++ b/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx @@ -5,7 +5,13 @@ import { useRtl } from '@mui/system/RtlProvider'; import { roundToDecimalPlaces } from '@mui/x-internals/math'; import { lruMemoize } from '@mui/x-internals/lruMemoize'; import { useStoreEffect } from '@mui/x-internals/store'; -import { useVirtualizer, Dimensions, VirtualizerParams, Virtualization } from '@mui/x-virtualizer'; +import { + useVirtualizer, + Dimensions, + VirtualizerParams, + Virtualization, + EMPTY_RENDER_CONTEXT, +} from '@mui/x-virtualizer'; import { useFirstRender } from '../utils/useFirstRender'; import { GridStateColDef } from '../../models/colDef/gridColDef'; import { createSelector } from '../../utils/createSelector'; @@ -353,14 +359,14 @@ export function useGridVirtualizer() { } }); - useStoreEffect(virtualizer.store, Virtualization.selectors.renderContext, (_, renderContext) => { - if (renderContext !== apiRef.current.state.virtualization.renderContext) { + useStoreEffect(virtualizer.store, Virtualization.selectors.store, (_, virtualization) => { + if (virtualization.renderContext === EMPTY_RENDER_CONTEXT) { + return; + } + if (virtualization !== apiRef.current.state.virtualization) { apiRef.current.setState((gridState) => ({ ...gridState, - virtualization: { - ...gridState.virtualization, - renderContext, - }, + virtualization, })); } }); diff --git a/packages/x-virtualizer/src/features/virtualization.ts b/packages/x-virtualizer/src/features/virtualization.ts index 84b8abd48a647..c530cce361d90 100644 --- a/packages/x-virtualizer/src/features/virtualization.ts +++ b/packages/x-virtualizer/src/features/virtualization.ts @@ -61,6 +61,7 @@ export const EMPTY_RENDER_CONTEXT = { }; const selectors = { + store: createSelector((state: BaseState) => state.virtualization), renderContext: createSelector((state: BaseState) => state.virtualization.renderContext), enabledForRows: createSelector((state: BaseState) => state.virtualization.enabledForRows), enabledForColumns: createSelector((state: BaseState) => state.virtualization.enabledForColumns), @@ -312,15 +313,15 @@ function useVirtualization(store: Store, params: VirtualizerParams, a } }); + const [key, setKey] = React.useState(0); useEnhancedEffect(() => { - if (isUpdateScheduled.current) { + if (key) { forceUpdateRenderContext(); - isUpdateScheduled.current = false; } - }); + }, [key]); const scheduleUpdateRenderContext = () => { - isUpdateScheduled.current = true; + setKey((k) => k + 1); }; const handleScroll = useEventCallback(() => { From 1bf65c2bb6fbe11c402c6483593e127b05bc214a Mon Sep 17 00:00:00 2001 From: lauri865 Date: Mon, 13 Oct 2025 23:47:13 +0200 Subject: [PATCH 23/60] fix --- .../hooks/features/virtualization/useGridVirtualization.tsx | 1 - packages/x-virtualizer/src/features/dimensions.ts | 2 +- packages/x-virtualizer/src/features/virtualization.ts | 6 +++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualization.tsx b/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualization.tsx index 13cd3d11e068e..9ba07c40a1ca6 100644 --- a/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualization.tsx +++ b/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualization.tsx @@ -89,7 +89,6 @@ export function useGridVirtualization( */ useGridEventPriority(apiRef, 'sortedRowsSet', forceUpdateRenderContext); - useGridEventPriority(apiRef, 'filteredRowsSet', forceUpdateRenderContext); useGridEventPriority(apiRef, 'paginationModelChange', forceUpdateRenderContext); useGridEventPriority(apiRef, 'columnsChange', forceUpdateRenderContext); diff --git a/packages/x-virtualizer/src/features/dimensions.ts b/packages/x-virtualizer/src/features/dimensions.ts index 9fe59422ab433..730a6b63a880c 100644 --- a/packages/x-virtualizer/src/features/dimensions.ts +++ b/packages/x-virtualizer/src/features/dimensions.ts @@ -418,8 +418,8 @@ function useRowsMeta( pinnedBottomRowsTotalHeight, }; - store.set('rowsMeta', rowsMeta); if (didHeightsChange) { + store.set('rowsMeta', rowsMeta); updateDimensions(); } diff --git a/packages/x-virtualizer/src/features/virtualization.ts b/packages/x-virtualizer/src/features/virtualization.ts index c530cce361d90..1db74fe6919c2 100644 --- a/packages/x-virtualizer/src/features/virtualization.ts +++ b/packages/x-virtualizer/src/features/virtualization.ts @@ -292,7 +292,7 @@ function useVirtualization(store: Store, params: VirtualizerParams, a return nextRenderContext; }); - const forceUpdateRenderContext = useEventCallback(() => { + const forceUpdateRenderContext = () => { // skip update if dimensions are not ready and virtualization is enabled if ( !Dimensions.selectors.dimensions(store.state).isReady && @@ -305,11 +305,11 @@ function useVirtualization(store: Store, params: VirtualizerParams, a // Reset the frozen context when the render context changes, see the illustration in https://github.com/mui/mui-x/pull/12353 frozenContext.current = undefined; updateRenderContext(nextRenderContext); - }); + }; useStoreEffect(store, Dimensions.selectors.dimensions, (previous, next) => { if (next.isReady) { - forceUpdateRenderContext; + forceUpdateRenderContext(); } }); From 402f14dc3c6c49b6d17b038ed3c1d7fef733b980 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Mon, 13 Oct 2025 23:58:48 +0200 Subject: [PATCH 24/60] lint --- .../useDataGridPremiumComponent.tsx | 1 - .../features/aggregation/useGridAggregation.ts | 2 +- .../virtualization/GridVirtualScroller.tsx | 2 +- .../x-virtualizer/src/features/dimensions.ts | 1 + .../src/features/virtualization.ts | 18 ++++++++++-------- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx index fe3787c2bf4b0..c2d48c4c57f76 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx @@ -23,7 +23,6 @@ import { useGridRows, useGridRowsPreProcessors, rowsStateInitializer, - useGridRowsMeta, useGridParamsApi, useGridRowSelection, useGridSorting, diff --git a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts index 70342cef78b7e..6e531cc0a30ca 100644 --- a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts +++ b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts @@ -327,5 +327,5 @@ export const useGridAggregation = ( if (props.getAggregationPosition !== undefined) { deferredApplyAggregation(); } - }, [props.getAggregationPosition]); + }, [deferredApplyAggregation, props.getAggregationPosition]); }; diff --git a/packages/x-data-grid/src/components/virtualization/GridVirtualScroller.tsx b/packages/x-data-grid/src/components/virtualization/GridVirtualScroller.tsx index 8e5670e038a42..02ab8358bc139 100644 --- a/packages/x-data-grid/src/components/virtualization/GridVirtualScroller.tsx +++ b/packages/x-data-grid/src/components/virtualization/GridVirtualScroller.tsx @@ -113,7 +113,7 @@ function GridVirtualScroller(props: GridVirtualScrollerProps) { getScrollAreaProps, } = virtualScroller; - const rows = virtualScroller.getRows(undefined, gridRowTreeSelector(apiRef)); + const rows = getRows(undefined, gridRowTreeSelector(apiRef)); return ( diff --git a/packages/x-virtualizer/src/features/dimensions.ts b/packages/x-virtualizer/src/features/dimensions.ts index 730a6b63a880c..79b051eb718fa 100644 --- a/packages/x-virtualizer/src/features/dimensions.ts +++ b/packages/x-virtualizer/src/features/dimensions.ts @@ -250,6 +250,7 @@ function useDimensions(store: Store, params: VirtualizerParams, _api: }, [ store, + refs.container, params.dimensions.scrollbarSize, params.autoHeight, params.disableHorizontalScroll, diff --git a/packages/x-virtualizer/src/features/virtualization.ts b/packages/x-virtualizer/src/features/virtualization.ts index 1db74fe6919c2..c19d5c3a2c976 100644 --- a/packages/x-virtualizer/src/features/virtualization.ts +++ b/packages/x-virtualizer/src/features/virtualization.ts @@ -8,7 +8,6 @@ import useEnhancedEffect from '@mui/utils/useEnhancedEffect'; import type { integer } from '@mui/x-internals/types'; import * as platform from '@mui/x-internals/platform'; import { useRunOnce } from '@mui/x-internals/useRunOnce'; -import { useFirstRender } from '@mui/x-internals/useFirstRender'; import { createSelector, useStore, useStoreEffect, Store } from '@mui/x-internals/store'; import { PinnedRows, PinnedColumns, Size } from '../models/core'; import type { CellColSpanInfo } from '../models/colspan'; @@ -307,21 +306,23 @@ function useVirtualization(store: Store, params: VirtualizerParams, a updateRenderContext(nextRenderContext); }; + const forceUpdateRenderContextCallback = useEventCallback(forceUpdateRenderContext); + useStoreEffect(store, Dimensions.selectors.dimensions, (previous, next) => { if (next.isReady) { forceUpdateRenderContext(); } }); - const [key, setKey] = React.useState(0); useEnhancedEffect(() => { - if (key) { + if (isUpdateScheduled.current) { forceUpdateRenderContext(); + isUpdateScheduled.current = false; } - }, [key]); + }); const scheduleUpdateRenderContext = () => { - setKey((k) => k + 1); + isUpdateScheduled.current = true; }; const handleScroll = useEventCallback(() => { @@ -561,8 +562,8 @@ function useVirtualization(store: Store, params: VirtualizerParams, a if (!isRenderContextReady.current) { return; } - forceUpdateRenderContext(); - }, [enabledForColumns, enabledForRows, forceUpdateRenderContext]); + forceUpdateRenderContextCallback(); + }, [enabledForColumns, enabledForRows, forceUpdateRenderContextCallback]); useEnhancedEffect(() => { if (refs.scroller.current) { @@ -633,6 +634,7 @@ function useVirtualization(store: Store, params: VirtualizerParams, a [refs], ); + const isFirstSizing = React.useRef(true); const containerRef = useEventCallback((node: HTMLDivElement | null) => { if (node && refs.container.current !== node) { refs.container.current = node; @@ -648,9 +650,9 @@ function useVirtualization(store: Store, params: VirtualizerParams, a } }); } + return undefined; }); - const isFirstSizing = React.useRef(true); const getters = { setPanels, getOffsetTop, From 57c98151fa41908e4f87352aebf8d7e2d37a4b3e Mon Sep 17 00:00:00 2001 From: lauri865 Date: Tue, 14 Oct 2025 00:27:57 +0200 Subject: [PATCH 25/60] update --- packages/x-virtualizer/src/features/virtualization.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/x-virtualizer/src/features/virtualization.ts b/packages/x-virtualizer/src/features/virtualization.ts index c19d5c3a2c976..eaebd9bf6f451 100644 --- a/packages/x-virtualizer/src/features/virtualization.ts +++ b/packages/x-virtualizer/src/features/virtualization.ts @@ -635,12 +635,17 @@ function useVirtualization(store: Store, params: VirtualizerParams, a ); const isFirstSizing = React.useRef(true); + + const cleanup = React.useRef<() => void | undefined>(undefined); + React.useEffect(() => { + //return cleanup.current; + }, [cleanup.current]); + const containerRef = useEventCallback((node: HTMLDivElement | null) => { if (node && refs.container.current !== node) { refs.container.current = node; - return observeRootNode(node, store, (rootSize: Size) => { + cleanup.current = observeRootNode(node, store, (rootSize: Size) => { store.state.rootSize = rootSize; - store.update({ rootSize }); if (isFirstSizing.current || !api.debouncedUpdateDimensions) { // We want to initialize the grid dimensions as soon as possible to avoid flickering api.updateDimensions(isFirstSizing.current); @@ -650,7 +655,6 @@ function useVirtualization(store: Store, params: VirtualizerParams, a } }); } - return undefined; }); const getters = { From 16136577720cd969d47d39e2b14ddf5cad61a4e9 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Tue, 14 Oct 2025 00:56:33 +0200 Subject: [PATCH 26/60] remove unnecessary event handler --- .../src/hooks/features/rowSelection/useGridRowSelection.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/x-data-grid/src/hooks/features/rowSelection/useGridRowSelection.ts b/packages/x-data-grid/src/hooks/features/rowSelection/useGridRowSelection.ts index 496d4a1cf9d04..8f6cee5c7a91f 100644 --- a/packages/x-data-grid/src/hooks/features/rowSelection/useGridRowSelection.ts +++ b/packages/x-data-grid/src/hooks/features/rowSelection/useGridRowSelection.ts @@ -165,6 +165,7 @@ export const useGridRowSelection = ( */ const setRowSelectionModel = React.useCallback( (model, reason) => { + console.log('model', model, reason); if ( props.signature === GridSignature.DataGrid && !canHaveMultipleSelection && @@ -874,11 +875,6 @@ export const useGridRowSelection = ( apiRef.current.setRowSelectionModel(propRowSelectionModel); }); - useGridEvent( - apiRef, - 'sortedRowsSet', - runIfRowSelectionIsEnabled(() => removeOutdatedSelection(true)), - ); useGridEvent( apiRef, 'filteredRowsSet', From a93686786875946c7f890b914c999ea1178a08d0 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Tue, 14 Oct 2025 01:01:39 +0200 Subject: [PATCH 27/60] log --- .../src/hooks/features/rowSelection/useGridRowSelection.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/x-data-grid/src/hooks/features/rowSelection/useGridRowSelection.ts b/packages/x-data-grid/src/hooks/features/rowSelection/useGridRowSelection.ts index 8f6cee5c7a91f..4027e7ec9d46d 100644 --- a/packages/x-data-grid/src/hooks/features/rowSelection/useGridRowSelection.ts +++ b/packages/x-data-grid/src/hooks/features/rowSelection/useGridRowSelection.ts @@ -165,7 +165,6 @@ export const useGridRowSelection = ( */ const setRowSelectionModel = React.useCallback( (model, reason) => { - console.log('model', model, reason); if ( props.signature === GridSignature.DataGrid && !canHaveMultipleSelection && From 29fbafbf3268f9ed3772a127d2e3ff13b21f5811 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Tue, 14 Oct 2025 01:19:49 +0200 Subject: [PATCH 28/60] fix --- packages/x-virtualizer/src/features/virtualization.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/x-virtualizer/src/features/virtualization.ts b/packages/x-virtualizer/src/features/virtualization.ts index eaebd9bf6f451..b1524954fc89c 100644 --- a/packages/x-virtualizer/src/features/virtualization.ts +++ b/packages/x-virtualizer/src/features/virtualization.ts @@ -637,9 +637,10 @@ function useVirtualization(store: Store, params: VirtualizerParams, a const isFirstSizing = React.useRef(true); const cleanup = React.useRef<() => void | undefined>(undefined); + const cleanupFn = cleanup.current; React.useEffect(() => { - //return cleanup.current; - }, [cleanup.current]); + return cleanupFn; + }, [cleanupFn]); const containerRef = useEventCallback((node: HTMLDivElement | null) => { if (node && refs.container.current !== node) { From fdd510e47a0fd6a01dbbff5d136381e3b1579e09 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Tue, 14 Oct 2025 10:01:19 +0200 Subject: [PATCH 29/60] add an event for when aggregation finishes --- .../src/hooks/features/aggregation/useGridAggregation.ts | 3 ++- .../features/chartsIntegration/useGridChartsIntegration.tsx | 5 +++++ packages/x-data-grid/src/models/events/gridEventLookup.ts | 5 +++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts index 6e531cc0a30ca..b8594e82ef9fc 100644 --- a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts +++ b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts @@ -148,6 +148,7 @@ export const useGridAggregation = ( const currentChunk = chunks[chunkIndex]; if (!currentChunk) { + apiRef.current.publishEvent('aggregationLookupSet'); abortControllerRef.current = null; return; } @@ -177,7 +178,7 @@ export const useGridAggregation = ( aggregation: { ...state.aggregation, lookup: { ...aggregationLookup } }, })); - if (chunkIndex === 0 && hasAggregatedSortedField) { + if (chunkIndex === 0) { apiRef.current.applySorting(); } diff --git a/packages/x-data-grid-premium/src/hooks/features/chartsIntegration/useGridChartsIntegration.tsx b/packages/x-data-grid-premium/src/hooks/features/chartsIntegration/useGridChartsIntegration.tsx index 1a93e6bd926d0..db87d91102bfe 100644 --- a/packages/x-data-grid-premium/src/hooks/features/chartsIntegration/useGridChartsIntegration.tsx +++ b/packages/x-data-grid-premium/src/hooks/features/chartsIntegration/useGridChartsIntegration.tsx @@ -879,6 +879,11 @@ export const useGridChartsIntegration = ( 'sortedRowsSet', runIf(isChartsIntegrationAvailable, () => debouncedHandleRowDataUpdate(syncedChartIds)), ); + useGridEvent( + apiRef, + 'aggregationLookupSet', + runIf(isChartsIntegrationAvailable, () => debouncedHandleRowDataUpdate(syncedChartIds)), + ); React.useEffect(() => { if (!activeChartId && availableChartIds.length > 0) { diff --git a/packages/x-data-grid/src/models/events/gridEventLookup.ts b/packages/x-data-grid/src/models/events/gridEventLookup.ts index df38a32d24100..6f102e21faa88 100644 --- a/packages/x-data-grid/src/models/events/gridEventLookup.ts +++ b/packages/x-data-grid/src/models/events/gridEventLookup.ts @@ -492,6 +492,11 @@ export interface GridEventLookup * @ignore - do not document */ sortedRowsSet: {}; + /** + * Fired when the aggregations are done + * @ignore - do not document + */ + aggregationLookupSet: {}; /** * Fired when the expansion of a row is changed. Called with a [[GridGroupNode]] object. */ From 4bffff5b603d925976d8c1a71b824ca4f957ea5d Mon Sep 17 00:00:00 2001 From: lauri865 Date: Tue, 14 Oct 2025 10:19:49 +0200 Subject: [PATCH 30/60] fix --- packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx b/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx index fd991b07927be..fb1aa4d20080a 100644 --- a/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx +++ b/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx @@ -251,7 +251,7 @@ export function useGridVirtualizer() { ), virtualizeColumnsWithAutoRowHeight: rootProps.virtualizeColumnsWithAutoRowHeight, - focusedVirtualCell: useEventCallback(() => focusedVirtualCell), + focusedVirtualCell: () => focusedVirtualCell, resizeThrottleMs: rootProps.resizeThrottleMs, onResize: useEventCallback((size) => apiRef.current.publishEvent('resize', size)), From 11ee56aebf89b0afe93702f043abf4fa054bfd99 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Tue, 14 Oct 2025 10:22:56 +0200 Subject: [PATCH 31/60] oops --- .../src/hooks/features/aggregation/useGridAggregation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts index b8594e82ef9fc..ee2412c3fb4be 100644 --- a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts +++ b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts @@ -178,7 +178,7 @@ export const useGridAggregation = ( aggregation: { ...state.aggregation, lookup: { ...aggregationLookup } }, })); - if (chunkIndex === 0) { + if (chunkIndex === 0 && hasAggregatedSortedField) { apiRef.current.applySorting(); } From 9fbeadee87ed272cd064e8da66b369a13060fc7b Mon Sep 17 00:00:00 2001 From: lauri865 Date: Tue, 14 Oct 2025 12:11:27 +0200 Subject: [PATCH 32/60] fix --- .../src/hooks/features/rows/useGridRowSpanning.ts | 7 ++++++- packages/x-virtualizer/src/features/virtualization.ts | 1 + 2 files changed, 7 insertions(+), 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 893a7d638ddc6..4e6f1107faa9d 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts @@ -238,8 +238,13 @@ export const useGridRowSpanning = ( (renderContext: GridRenderContext, resetState: boolean = false) => { const store = apiRef.current.virtualizer.store; const { range, rows: visibleRows } = getVisibleRows(apiRef); - if (resetState && store.getSnapshot().rowSpanning !== EMPTY_STATE) { + if (resetState) { store.set('rowSpanning', EMPTY_STATE); + // HACK: virtualizer hasn't yet updated with the latest rows, hence it doesn't make sense to update the row spanning state at this point + // TO-DO: ideally virtualizer shouldn't have a cross-dependency with rows that it renders + if (apiRef.current.virtualizer.api.getters.rows !== visibleRows) { + return; + } } if (range === null || !isRowContextInitialized(renderContext)) { diff --git a/packages/x-virtualizer/src/features/virtualization.ts b/packages/x-virtualizer/src/features/virtualization.ts index b1524954fc89c..9bd685f264c12 100644 --- a/packages/x-virtualizer/src/features/virtualization.ts +++ b/packages/x-virtualizer/src/features/virtualization.ts @@ -662,6 +662,7 @@ function useVirtualization(store: Store, params: VirtualizerParams, a setPanels, getOffsetTop, getRows, + rows: params.rows, getContainerProps: () => ({ ref: containerRef, }), From 12feebdd6d64be326c4192bce3fffd49a46c2491 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Tue, 14 Oct 2025 12:36:02 +0200 Subject: [PATCH 33/60] remove hack for now --- .../src/hooks/features/rows/useGridRowSpanning.ts | 5 ----- 1 file changed, 5 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 4e6f1107faa9d..060c4317d0348 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts @@ -240,11 +240,6 @@ export const useGridRowSpanning = ( const { range, rows: visibleRows } = getVisibleRows(apiRef); if (resetState) { store.set('rowSpanning', EMPTY_STATE); - // HACK: virtualizer hasn't yet updated with the latest rows, hence it doesn't make sense to update the row spanning state at this point - // TO-DO: ideally virtualizer shouldn't have a cross-dependency with rows that it renders - if (apiRef.current.virtualizer.api.getters.rows !== visibleRows) { - return; - } } if (range === null || !isRowContextInitialized(renderContext)) { From 1ab86c4160c294c0f025b1df94c6788004c0071f Mon Sep 17 00:00:00 2001 From: lauri865 Date: Tue, 14 Oct 2025 13:00:01 +0200 Subject: [PATCH 34/60] fix --- .../x-data-grid/src/tests/rowSpanning.DataGrid.test.tsx | 7 ++++--- packages/x-virtualizer/src/features/dimensions.ts | 2 +- 2 files changed, 5 insertions(+), 4 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 a45c1731ef529..a6222adb53ecb 100644 --- a/packages/x-data-grid/src/tests/rowSpanning.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/rowSpanning.DataGrid.test.tsx @@ -1,10 +1,10 @@ import * as React from 'react'; import { RefObject } from '@mui/x-internals/types'; -import { createRenderer, fireEvent, act } from '@mui/internal-test-utils'; +import { createRenderer, fireEvent, act, waitFor } from '@mui/internal-test-utils'; import { spy } from 'sinon'; import { DataGrid, useGridApiRef, DataGridProps, GridApi } from '@mui/x-data-grid'; import { unwrapPrivateAPI } from '@mui/x-data-grid/internals'; -import { getCell, getActiveCell } from 'test/utils/helperFn'; +import { getCell, getActiveCell, microtasks } from 'test/utils/helperFn'; import { isJSDOM } from 'test/utils/skipIf'; describe.skipIf(isJSDOM)(' - Row spanning', () => { @@ -283,7 +283,8 @@ describe.skipIf(isJSDOM)(' - Row spanning', () => { }); // 2 updates on `rows` update, one for the reset (necessary to track deleted or updated data values) and one for the new computed state - expect(rowSpanningStateUpdates).to.equal(2); + // virtualizer updates on the next tick, so we need to wait for it + waitFor(() => expect(rowSpanningStateUpdates).to.equal(2)); dispose(); }); diff --git a/packages/x-virtualizer/src/features/dimensions.ts b/packages/x-virtualizer/src/features/dimensions.ts index 79b051eb718fa..1c9065050776a 100644 --- a/packages/x-virtualizer/src/features/dimensions.ts +++ b/packages/x-virtualizer/src/features/dimensions.ts @@ -419,8 +419,8 @@ function useRowsMeta( pinnedBottomRowsTotalHeight, }; + store.set('rowsMeta', rowsMeta); if (didHeightsChange) { - store.set('rowsMeta', rowsMeta); updateDimensions(); } From bc96ff9748c393e0b38f30a5599ff1642ace926d Mon Sep 17 00:00:00 2001 From: lauri865 Date: Tue, 14 Oct 2025 13:45:56 +0200 Subject: [PATCH 35/60] fix --- packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx b/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx index fb1aa4d20080a..fd991b07927be 100644 --- a/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx +++ b/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx @@ -251,7 +251,7 @@ export function useGridVirtualizer() { ), virtualizeColumnsWithAutoRowHeight: rootProps.virtualizeColumnsWithAutoRowHeight, - focusedVirtualCell: () => focusedVirtualCell, + focusedVirtualCell: useEventCallback(() => focusedVirtualCell), resizeThrottleMs: rootProps.resizeThrottleMs, onResize: useEventCallback((size) => apiRef.current.publishEvent('resize', size)), From b43dfdc90b8a67c6f2a7ea4341696caef307ac33 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Tue, 14 Oct 2025 13:48:27 +0200 Subject: [PATCH 36/60] fix --- .../x-data-grid/src/tests/rowSpanning.DataGrid.test.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 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 a6222adb53ecb..a45c1731ef529 100644 --- a/packages/x-data-grid/src/tests/rowSpanning.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/rowSpanning.DataGrid.test.tsx @@ -1,10 +1,10 @@ import * as React from 'react'; import { RefObject } from '@mui/x-internals/types'; -import { createRenderer, fireEvent, act, waitFor } from '@mui/internal-test-utils'; +import { createRenderer, fireEvent, act } from '@mui/internal-test-utils'; import { spy } from 'sinon'; import { DataGrid, useGridApiRef, DataGridProps, GridApi } from '@mui/x-data-grid'; import { unwrapPrivateAPI } from '@mui/x-data-grid/internals'; -import { getCell, getActiveCell, microtasks } from 'test/utils/helperFn'; +import { getCell, getActiveCell } from 'test/utils/helperFn'; import { isJSDOM } from 'test/utils/skipIf'; describe.skipIf(isJSDOM)(' - Row spanning', () => { @@ -283,8 +283,7 @@ describe.skipIf(isJSDOM)(' - Row spanning', () => { }); // 2 updates on `rows` update, one for the reset (necessary to track deleted or updated data values) and one for the new computed state - // virtualizer updates on the next tick, so we need to wait for it - waitFor(() => expect(rowSpanningStateUpdates).to.equal(2)); + expect(rowSpanningStateUpdates).to.equal(2); dispose(); }); From 5199dfd4c0f62fa0207e39c4291a2d2fa4facf97 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Tue, 14 Oct 2025 13:53:41 +0200 Subject: [PATCH 37/60] deduplicate row spanning calls --- .../hooks/features/rows/useGridRowSpanning.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 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 060c4317d0348..32f18abb1c416 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts @@ -17,6 +17,7 @@ import { useGridEvent } from '../../utils/useGridEvent'; import { runIf } from '../../../utils/utils'; import { gridPageSizeSelector } from '../pagination'; import { gridDataRowIdsSelector } from './gridRowsSelector'; +import { useRunOncePerLoop } from '../../utils/useRunOncePerLoop'; export interface GridRowSpanningState extends RowSpanningState {} @@ -307,16 +308,22 @@ export const useGridRowSpanning = ( updateRowSpanningState(renderContext, true); }, [apiRef, updateRowSpanningState]); + const deferredResetRowSpanningState = useRunOncePerLoop(resetRowSpanningState); + useGridEvent( apiRef, 'renderedRowsIntervalChange', - runIf(props.rowSpanning, updateRowSpanningState), + runIf(props.rowSpanning, deferredResetRowSpanningState), ); - useGridEvent(apiRef, 'sortedRowsSet', runIf(props.rowSpanning, resetRowSpanningState)); - useGridEvent(apiRef, 'paginationModelChange', runIf(props.rowSpanning, resetRowSpanningState)); - useGridEvent(apiRef, 'filteredRowsSet', runIf(props.rowSpanning, resetRowSpanningState)); - useGridEvent(apiRef, 'columnsChange', runIf(props.rowSpanning, resetRowSpanningState)); + useGridEvent(apiRef, 'sortedRowsSet', runIf(props.rowSpanning, deferredResetRowSpanningState)); + useGridEvent( + apiRef, + 'paginationModelChange', + runIf(props.rowSpanning, deferredResetRowSpanningState), + ); + useGridEvent(apiRef, 'filteredRowsSet', runIf(props.rowSpanning, deferredResetRowSpanningState)); + useGridEvent(apiRef, 'columnsChange', runIf(props.rowSpanning, deferredResetRowSpanningState)); React.useEffect(() => { const store = apiRef.current.virtualizer?.store; From 9262c19417bafbd6ccae7d67e37a0ba8b0cb5cc7 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Tue, 14 Oct 2025 15:17:00 +0200 Subject: [PATCH 38/60] fixes --- packages/x-data-grid/src/components/cell/GridCell.tsx | 3 ++- packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/x-data-grid/src/components/cell/GridCell.tsx b/packages/x-data-grid/src/components/cell/GridCell.tsx index 113a3c03c9546..cd17a44dc9ddf 100644 --- a/packages/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/x-data-grid/src/components/cell/GridCell.tsx @@ -40,6 +40,7 @@ import { useGridPrivateApiContext } from '../../hooks/utils/useGridPrivateApiCon import { gridEditCellStateSelector } from '../../hooks/features/editing/gridEditingSelectors'; import { attachPinnedStyle } from '../../internals/utils'; import { useGridConfiguration } from '../../hooks/utils/useGridConfiguration'; +import useEnhancedEffect from '@mui/utils/useEnhancedEffect'; export const gridPinnedColumnPositionLookup = { [PinnedColumnPosition.LEFT]: GridPinnedColumnPosition.LEFT, @@ -350,7 +351,7 @@ const GridCell = forwardRef(function GridCell(pro return cellStyle; }, [width, isNotVisible, styleProp, pinnedOffset, pinnedPosition, isRtl, rowSpan]); - React.useEffect(() => { + useEnhancedEffect(() => { if (!hasFocus || cellMode === GridCellModes.Edit) { return; } diff --git a/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx b/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx index fd991b07927be..425ec9b8fc08b 100644 --- a/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx +++ b/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx @@ -158,8 +158,6 @@ export function useGridVirtualizer() { const { getRowHeight, getEstimatedRowHeight, getRowSpacing } = rootProps; // - const focusedVirtualCell = useGridSelector(apiRef, gridFocusedVirtualCellSelector); - const RowSlot = rootProps.slots.row; const rowSlotProps = rootProps.slotProps?.row; @@ -251,7 +249,7 @@ export function useGridVirtualizer() { ), virtualizeColumnsWithAutoRowHeight: rootProps.virtualizeColumnsWithAutoRowHeight, - focusedVirtualCell: useEventCallback(() => focusedVirtualCell), + focusedVirtualCell: useEventCallback(() => gridFocusedVirtualCellSelector(apiRef)), resizeThrottleMs: rootProps.resizeThrottleMs, onResize: useEventCallback((size) => apiRef.current.publishEvent('resize', size)), From 806bc9aa23418435924b7752ca1714c73d08d12e Mon Sep 17 00:00:00 2001 From: lauri865 Date: Tue, 14 Oct 2025 15:31:57 +0200 Subject: [PATCH 39/60] fix test --- packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx b/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx index c8a5ca2f25eb6..4794642c0a558 100644 --- a/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { createRenderer, fireEvent, screen, act } from '@mui/internal-test-utils'; +import { createRenderer, fireEvent, screen, act, waitFor } from '@mui/internal-test-utils'; import { spy } from 'sinon'; import { getActiveCell, @@ -199,7 +199,9 @@ describe(' - Keyboard', () => { await user.click(cell); expect(getActiveCell()).to.equal('8-1'); fireEvent.keyDown(document.activeElement!, { key: 'PageUp' }); - expect(getActiveCell()).to.equal(`3-1`); + waitFor(() => { + expect(getActiveCell()).to.equal(`3-1`); + }); }, ); From 15cc4fb56039fbf49e73314efe8479a6791abdce Mon Sep 17 00:00:00 2001 From: lauri865 Date: Tue, 14 Oct 2025 15:41:23 +0200 Subject: [PATCH 40/60] fix --- .../hooks/features/rows/useGridRowSpanning.ts | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 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 32f18abb1c416..a47a7c1db9f76 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts @@ -300,30 +300,26 @@ export const useGridRowSpanning = ( // - The sorting is applied // - The `paginationModel` is updated // - The rows are updated + const deferredUpdateRowSpawnningState = useRunOncePerLoop(updateRowSpanningState); + const resetRowSpanningState = React.useCallback(() => { const renderContext = gridRenderContextSelector(apiRef); if (!isRowContextInitialized(renderContext)) { return; } - updateRowSpanningState(renderContext, true); - }, [apiRef, updateRowSpanningState]); - - const deferredResetRowSpanningState = useRunOncePerLoop(resetRowSpanningState); + deferredUpdateRowSpawnningState(renderContext, true); + }, [apiRef, deferredUpdateRowSpawnningState]); useGridEvent( apiRef, 'renderedRowsIntervalChange', - runIf(props.rowSpanning, deferredResetRowSpanningState), + runIf(props.rowSpanning, resetRowSpanningState), ); - useGridEvent(apiRef, 'sortedRowsSet', runIf(props.rowSpanning, deferredResetRowSpanningState)); - useGridEvent( - apiRef, - 'paginationModelChange', - runIf(props.rowSpanning, deferredResetRowSpanningState), - ); - useGridEvent(apiRef, 'filteredRowsSet', runIf(props.rowSpanning, deferredResetRowSpanningState)); - useGridEvent(apiRef, 'columnsChange', runIf(props.rowSpanning, deferredResetRowSpanningState)); + useGridEvent(apiRef, 'sortedRowsSet', runIf(props.rowSpanning, resetRowSpanningState)); + useGridEvent(apiRef, 'paginationModelChange', runIf(props.rowSpanning, resetRowSpanningState)); + useGridEvent(apiRef, 'filteredRowsSet', runIf(props.rowSpanning, resetRowSpanningState)); + useGridEvent(apiRef, 'columnsChange', runIf(props.rowSpanning, resetRowSpanningState)); React.useEffect(() => { const store = apiRef.current.virtualizer?.store; From 7d1b7e0fb7e371b60f97f1b411af8de2786653b2 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Tue, 14 Oct 2025 16:12:08 +0200 Subject: [PATCH 41/60] fix keyboard test in the browser --- packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx b/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx index 4794642c0a558..56be4716f8532 100644 --- a/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx @@ -258,9 +258,9 @@ describe(' - Keyboard', () => { it('should navigate to the 1st cell of the 1st row when pressing "Home" + ctrlKey of metaKey of shiftKey', async () => { const { user } = render(); - const cell = getCell(8, 1); + const cell = getCell(5, 1); await user.click(cell); - expect(getActiveCell()).to.equal('8-1'); + expect(getActiveCell()).to.equal('5-1'); fireEvent.keyDown(document.activeElement!, { key: 'Home', ctrlKey: true }); expect(getActiveCell()).to.equal('0-0'); @@ -268,7 +268,7 @@ describe(' - Keyboard', () => { await act(async () => cell.scrollIntoView()); } await user.click(cell); - expect(getActiveCell()).to.equal('8-1'); + expect(getActiveCell()).to.equal('5-1'); fireEvent.keyDown(document.activeElement!, { key: 'Home', metaKey: true }); expect(getActiveCell()).to.equal('0-0'); @@ -276,7 +276,7 @@ describe(' - Keyboard', () => { await act(async () => cell.scrollIntoView()); } await user.click(cell); - expect(getActiveCell()).to.equal('8-1'); + expect(getActiveCell()).to.equal('5-1'); fireEvent.keyDown(document.activeElement!, { key: 'Home', shiftKey: true }); expect(getActiveCell()).to.equal('0-0'); }); From 970ea9451c54d2790074aeaa457825fcd7e207e6 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Tue, 14 Oct 2025 16:28:44 +0200 Subject: [PATCH 42/60] fix --- .../x-data-grid/src/components/cell/GridCell.tsx | 2 +- .../src/tests/keyboard.DataGrid.test.tsx | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/x-data-grid/src/components/cell/GridCell.tsx b/packages/x-data-grid/src/components/cell/GridCell.tsx index cd17a44dc9ddf..672cea9299d68 100644 --- a/packages/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/x-data-grid/src/components/cell/GridCell.tsx @@ -11,6 +11,7 @@ import { useRtl } from '@mui/system/RtlProvider'; import { forwardRef } from '@mui/x-internals/forwardRef'; import { useStore } from '@mui/x-internals/store'; import { Rowspan } from '@mui/x-virtualizer'; +import useEnhancedEffect from '@mui/utils/useEnhancedEffect'; import { doesSupportPreventScroll } from '../../utils/doesSupportPreventScroll'; import { getDataGridUtilityClass, gridClasses } from '../../constants/gridClasses'; import { @@ -40,7 +41,6 @@ import { useGridPrivateApiContext } from '../../hooks/utils/useGridPrivateApiCon import { gridEditCellStateSelector } from '../../hooks/features/editing/gridEditingSelectors'; import { attachPinnedStyle } from '../../internals/utils'; import { useGridConfiguration } from '../../hooks/utils/useGridConfiguration'; -import useEnhancedEffect from '@mui/utils/useEnhancedEffect'; export const gridPinnedColumnPositionLookup = { [PinnedColumnPosition.LEFT]: GridPinnedColumnPosition.LEFT, diff --git a/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx b/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx index 56be4716f8532..19f229a7f9833 100644 --- a/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx @@ -199,7 +199,7 @@ describe(' - Keyboard', () => { await user.click(cell); expect(getActiveCell()).to.equal('8-1'); fireEvent.keyDown(document.activeElement!, { key: 'PageUp' }); - waitFor(() => { + await waitFor(() => { expect(getActiveCell()).to.equal(`3-1`); }); }, @@ -258,27 +258,27 @@ describe(' - Keyboard', () => { it('should navigate to the 1st cell of the 1st row when pressing "Home" + ctrlKey of metaKey of shiftKey', async () => { const { user } = render(); - const cell = getCell(5, 1); + const cell = getCell(8, 1); await user.click(cell); - expect(getActiveCell()).to.equal('5-1'); + expect(getActiveCell()).to.equal('8-1'); fireEvent.keyDown(document.activeElement!, { key: 'Home', ctrlKey: true }); - expect(getActiveCell()).to.equal('0-0'); + await waitFor(() => expect(getActiveCell()).to.equal('0-0')); if (!isJSDOM) { await act(async () => cell.scrollIntoView()); } await user.click(cell); - expect(getActiveCell()).to.equal('5-1'); + expect(getActiveCell()).to.equal('8-1'); fireEvent.keyDown(document.activeElement!, { key: 'Home', metaKey: true }); - expect(getActiveCell()).to.equal('0-0'); + await waitFor(() => expect(getActiveCell()).to.equal('0-0')); if (!isJSDOM) { await act(async () => cell.scrollIntoView()); } await user.click(cell); - expect(getActiveCell()).to.equal('5-1'); + expect(getActiveCell()).to.equal('8-1'); fireEvent.keyDown(document.activeElement!, { key: 'Home', shiftKey: true }); - expect(getActiveCell()).to.equal('0-0'); + await waitFor(() => expect(getActiveCell()).to.equal('0-0')); }); it('should navigate to the last cell of the current row when pressing "End"', async () => { From 25b27adba73ad638d0b7a960eb2214f93684cdeb Mon Sep 17 00:00:00 2001 From: lauri865 Date: Tue, 14 Oct 2025 16:43:02 +0200 Subject: [PATCH 43/60] test --- .../src/tests/keyboard.DataGrid.test.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx b/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx index 19f229a7f9833..c16401d5184c4 100644 --- a/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx @@ -258,27 +258,27 @@ describe(' - Keyboard', () => { it('should navigate to the 1st cell of the 1st row when pressing "Home" + ctrlKey of metaKey of shiftKey', async () => { const { user } = render(); - const cell = getCell(8, 1); + let cell = getCell(5, 1); await user.click(cell); - expect(getActiveCell()).to.equal('8-1'); + expect(getActiveCell()).to.equal('5-1'); fireEvent.keyDown(document.activeElement!, { key: 'Home', ctrlKey: true }); - await waitFor(() => expect(getActiveCell()).to.equal('0-0')); + expect(getActiveCell()).to.equal('0-0'); if (!isJSDOM) { await act(async () => cell.scrollIntoView()); } await user.click(cell); - expect(getActiveCell()).to.equal('8-1'); + expect(getActiveCell()).to.equal('5-1'); fireEvent.keyDown(document.activeElement!, { key: 'Home', metaKey: true }); - await waitFor(() => expect(getActiveCell()).to.equal('0-0')); + expect(getActiveCell()).to.equal('0-0'); if (!isJSDOM) { await act(async () => cell.scrollIntoView()); } await user.click(cell); - expect(getActiveCell()).to.equal('8-1'); + expect(getActiveCell()).to.equal('5-1'); fireEvent.keyDown(document.activeElement!, { key: 'Home', shiftKey: true }); - await waitFor(() => expect(getActiveCell()).to.equal('0-0')); + expect(getActiveCell()).to.equal('0-0'); }); it('should navigate to the last cell of the current row when pressing "End"', async () => { From 3b45c7bda9783b4bd7a65686114761d049f8d0b4 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Tue, 14 Oct 2025 16:51:52 +0200 Subject: [PATCH 44/60] test --- packages/x-data-grid/src/tests/columnSpanning.DataGrid.test.tsx | 2 +- packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/x-data-grid/src/tests/columnSpanning.DataGrid.test.tsx b/packages/x-data-grid/src/tests/columnSpanning.DataGrid.test.tsx index 02bc2599da5ea..f6a10c22d1c08 100644 --- a/packages/x-data-grid/src/tests/columnSpanning.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/columnSpanning.DataGrid.test.tsx @@ -449,7 +449,7 @@ describe(' - Column spanning', () => { // should be scrolled to the end of the cell expect(virtualScroller.scrollLeft).to.equal(3 * 100); - await user.keyboard('{ArrowLeft}{ArrowLeft}'); + await user.keyboard('{ArrowLeft>}{ArrowLeft}'); expect(getActiveCell()).to.equal('0-0'); diff --git a/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx b/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx index c16401d5184c4..6288da72fd588 100644 --- a/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx @@ -258,7 +258,7 @@ describe(' - Keyboard', () => { it('should navigate to the 1st cell of the 1st row when pressing "Home" + ctrlKey of metaKey of shiftKey', async () => { const { user } = render(); - let cell = getCell(5, 1); + const cell = getCell(5, 1); await user.click(cell); expect(getActiveCell()).to.equal('5-1'); fireEvent.keyDown(document.activeElement!, { key: 'Home', ctrlKey: true }); From dae6c7f062ba74ce6459a0eb61e924281538de5c Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 14 Oct 2025 21:58:43 +0200 Subject: [PATCH 45/60] empty From 217d960e59c9cf6d6a8d528283ff8c1005031a1f Mon Sep 17 00:00:00 2001 From: Rom Grk Date: Tue, 14 Oct 2025 16:25:24 -0400 Subject: [PATCH 46/60] lint Signed-off-by: Rom Grk --- packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx b/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx index 425ec9b8fc08b..038b12a2ae16f 100644 --- a/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx +++ b/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx @@ -335,7 +335,7 @@ export function useGridVirtualizer() { apiRef.current.store.state.virtualization = virtualizer.store.state.virtualization; }); - useStoreEffect(virtualizer.store, Dimensions.selectors.dimensions, (prev, dimensions) => { + useStoreEffect(virtualizer.store, Dimensions.selectors.dimensions, (_, dimensions) => { apiRef.current.setState((gridState) => ({ ...gridState, dimensions: addGridDimensions( From 142421922e5be9791461f0bdfe0cc0926142793c Mon Sep 17 00:00:00 2001 From: lauri865 Date: Tue, 14 Oct 2025 23:12:03 +0200 Subject: [PATCH 47/60] fix tests --- .../src/hooks/core/useGridVirtualizer.tsx | 5 +++++ .../src/tests/columnSpanning.DataGrid.test.tsx | 2 +- .../src/tests/keyboard.DataGrid.test.tsx | 14 ++++++-------- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx b/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx index 038b12a2ae16f..59b6e79f44459 100644 --- a/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx +++ b/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx @@ -43,6 +43,7 @@ import { useGridOverlays } from '../features/overlays/useGridOverlays'; import { useGridRootProps } from '../utils/useGridRootProps'; import { useGridPrivateApiContext } from '../utils/useGridPrivateApiContext'; import { useGridRowsMeta } from '../features/rows/useGridRowsMeta'; +import { eslintUseValue } from '../../utils/utils'; const columnsTotalWidthSelector = createSelector( gridVisibleColumnDefinitionsSelector, @@ -161,6 +162,10 @@ export function useGridVirtualizer() { const RowSlot = rootProps.slots.row; const rowSlotProps = rootProps.slotProps?.row; + const focusedVirtualCell = useGridSelector(apiRef, gridFocusedVirtualCellSelector); + // We need it to trigger a new render, but rowsMeta needs access to the latest value, hence we cannot pass it to the focusedVirtualCell callback in the virtualizer params + eslintUseValue(focusedVirtualCell); + const virtualizer = useVirtualizer({ refs: { container: apiRef.current.mainElementRef, diff --git a/packages/x-data-grid/src/tests/columnSpanning.DataGrid.test.tsx b/packages/x-data-grid/src/tests/columnSpanning.DataGrid.test.tsx index f6a10c22d1c08..02bc2599da5ea 100644 --- a/packages/x-data-grid/src/tests/columnSpanning.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/columnSpanning.DataGrid.test.tsx @@ -449,7 +449,7 @@ describe(' - Column spanning', () => { // should be scrolled to the end of the cell expect(virtualScroller.scrollLeft).to.equal(3 * 100); - await user.keyboard('{ArrowLeft>}{ArrowLeft}'); + await user.keyboard('{ArrowLeft}{ArrowLeft}'); expect(getActiveCell()).to.equal('0-0'); diff --git a/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx b/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx index 6288da72fd588..c8a5ca2f25eb6 100644 --- a/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { createRenderer, fireEvent, screen, act, waitFor } from '@mui/internal-test-utils'; +import { createRenderer, fireEvent, screen, act } from '@mui/internal-test-utils'; import { spy } from 'sinon'; import { getActiveCell, @@ -199,9 +199,7 @@ describe(' - Keyboard', () => { await user.click(cell); expect(getActiveCell()).to.equal('8-1'); fireEvent.keyDown(document.activeElement!, { key: 'PageUp' }); - await waitFor(() => { - expect(getActiveCell()).to.equal(`3-1`); - }); + expect(getActiveCell()).to.equal(`3-1`); }, ); @@ -258,9 +256,9 @@ describe(' - Keyboard', () => { it('should navigate to the 1st cell of the 1st row when pressing "Home" + ctrlKey of metaKey of shiftKey', async () => { const { user } = render(); - const cell = getCell(5, 1); + const cell = getCell(8, 1); await user.click(cell); - expect(getActiveCell()).to.equal('5-1'); + expect(getActiveCell()).to.equal('8-1'); fireEvent.keyDown(document.activeElement!, { key: 'Home', ctrlKey: true }); expect(getActiveCell()).to.equal('0-0'); @@ -268,7 +266,7 @@ describe(' - Keyboard', () => { await act(async () => cell.scrollIntoView()); } await user.click(cell); - expect(getActiveCell()).to.equal('5-1'); + expect(getActiveCell()).to.equal('8-1'); fireEvent.keyDown(document.activeElement!, { key: 'Home', metaKey: true }); expect(getActiveCell()).to.equal('0-0'); @@ -276,7 +274,7 @@ describe(' - Keyboard', () => { await act(async () => cell.scrollIntoView()); } await user.click(cell); - expect(getActiveCell()).to.equal('5-1'); + expect(getActiveCell()).to.equal('8-1'); fireEvent.keyDown(document.activeElement!, { key: 'Home', shiftKey: true }); expect(getActiveCell()).to.equal('0-0'); }); From 3b070face34e203f69245a44031ad46fd28b5850 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Wed, 15 Oct 2025 00:03:18 +0200 Subject: [PATCH 48/60] reduce state updates --- .../src/tests/rowSelection.DataGridPro.test.tsx | 2 +- .../src/hooks/core/useGridVirtualizer.tsx | 4 ++++ .../virtualization/useGridVirtualization.tsx | 12 ++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx index 47f81e57e94bd..ed21e2eb64871 100644 --- a/packages/x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx @@ -811,7 +811,7 @@ describe(' - Row selection', () => { />, ); - expect(onRowSelectionModelChange.callCount).to.equal(4); + expect(onRowSelectionModelChange.callCount).to.equal(3); expect(onRowSelectionModelChange.lastCall.args[0]).to.deep.equal( includeRowSelection([2, 3, 4, 5, 6, 7, 1]), ); diff --git a/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx b/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx index 59b6e79f44459..a6d7524e483a5 100644 --- a/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx +++ b/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx @@ -341,6 +341,9 @@ export function useGridVirtualizer() { }); useStoreEffect(virtualizer.store, Dimensions.selectors.dimensions, (_, dimensions) => { + if (!dimensions.isReady) { + return; + } apiRef.current.setState((gridState) => ({ ...gridState, dimensions: addGridDimensions( @@ -367,6 +370,7 @@ export function useGridVirtualizer() { return; } if (virtualization !== apiRef.current.state.virtualization) { + console.log('virtualization change', virtualization); apiRef.current.setState((gridState) => ({ ...gridState, virtualization, diff --git a/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualization.tsx b/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualization.tsx index 9ba07c40a1ca6..ab549241b609b 100644 --- a/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualization.tsx +++ b/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualization.tsx @@ -51,6 +51,14 @@ export function useGridVirtualization( const setVirtualization = (enabled: boolean) => { const { virtualizer } = apiRef.current; enabled &&= HAS_LAYOUT; + const snapshot = virtualizer.store.getSnapshot(); + if ( + snapshot.virtualization.enabled === enabled && + snapshot.virtualization.enabledForRows === enabled && + snapshot.virtualization.enabledForColumns === enabled + ) { + return; + } virtualizer.store.set('virtualization', { ...virtualizer.store.state.virtualization, enabled, @@ -62,6 +70,10 @@ export function useGridVirtualization( const setColumnVirtualization = (enabled: boolean) => { const { virtualizer } = apiRef.current; enabled &&= HAS_LAYOUT; + const snapshot = virtualizer.store.getSnapshot(); + if (snapshot.virtualization.enabledForColumns === enabled) { + return; + } virtualizer.store.set('virtualization', { ...virtualizer.store.state.virtualization, enabledForColumns: enabled, From f09246dbb319054544bb5f4d22fedd6d0b23a132 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Wed, 15 Oct 2025 00:19:00 +0200 Subject: [PATCH 49/60] fix --- .../x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx | 2 +- packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx index ed21e2eb64871..47f81e57e94bd 100644 --- a/packages/x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx @@ -811,7 +811,7 @@ describe(' - Row selection', () => { />, ); - expect(onRowSelectionModelChange.callCount).to.equal(3); + expect(onRowSelectionModelChange.callCount).to.equal(4); expect(onRowSelectionModelChange.lastCall.args[0]).to.deep.equal( includeRowSelection([2, 3, 4, 5, 6, 7, 1]), ); diff --git a/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx b/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx index a6d7524e483a5..83788235af184 100644 --- a/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx +++ b/packages/x-data-grid/src/hooks/core/useGridVirtualizer.tsx @@ -370,7 +370,6 @@ export function useGridVirtualizer() { return; } if (virtualization !== apiRef.current.state.virtualization) { - console.log('virtualization change', virtualization); apiRef.current.setState((gridState) => ({ ...gridState, virtualization, From d5619fb8a8389a348c20595f7e7a83a48d7f6eaa Mon Sep 17 00:00:00 2001 From: lauri865 Date: Wed, 15 Oct 2025 01:04:55 +0200 Subject: [PATCH 50/60] reduce calls to sorting / filtering --- .../columnPinning/useGridColumnPinningPreProcessors.ts | 4 ++++ .../src/tests/detailPanel.DataGridPro.test.tsx | 6 +++--- .../src/tests/rowSelection.DataGridPro.test.tsx | 2 +- .../x-data-grid/src/hooks/features/filter/useGridFilter.tsx | 5 ++++- .../src/hooks/features/sorting/useGridSorting.ts | 5 ++++- 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/columnPinning/useGridColumnPinningPreProcessors.ts b/packages/x-data-grid-pro/src/hooks/features/columnPinning/useGridColumnPinningPreProcessors.ts index 9e5295b05c3a5..9bfeac7bd88e3 100644 --- a/packages/x-data-grid-pro/src/hooks/features/columnPinning/useGridColumnPinningPreProcessors.ts +++ b/packages/x-data-grid-pro/src/hooks/features/columnPinning/useGridColumnPinningPreProcessors.ts @@ -103,6 +103,10 @@ export const useGridColumnPinningPreProcessors = ( apiRef.current.caches.columnPinning.orderedFieldsBeforePinningColumns = newOrderedFieldsBeforePinningColumns; } else { + if (allPinnedColumns.length === 0) { + prevAllPinnedColumns.current = allPinnedColumns; + return columnsState; + } newOrderedFields = [...columnsState.orderedFields]; apiRef.current.caches.columnPinning.orderedFieldsBeforePinningColumns = [ ...columnsState.orderedFields, diff --git a/packages/x-data-grid-pro/src/tests/detailPanel.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/detailPanel.DataGridPro.test.tsx index 5dce5f5426270..a093718f20249 100644 --- a/packages/x-data-grid-pro/src/tests/detailPanel.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/detailPanel.DataGridPro.test.tsx @@ -326,7 +326,7 @@ describe(' - Detail panel', () => { // from React 19 it is: // 2x during state initialization // + 2x when sortedRowsSet is fired - const expectedCallCount = reactMajor >= 19 ? 8 : 12; + const expectedCallCount = reactMajor >= 19 ? 6 : 10; expect(getDetailPanelContent.callCount).to.equal(expectedCallCount); await user.click(screen.getByRole('button', { name: 'Expand' })); @@ -368,7 +368,7 @@ describe(' - Detail panel', () => { // from React 19 it is: // 2x during state initialization // + 2x when sortedRowsSet is fired - const expectedCallCount = reactMajor >= 19 ? 8 : 12; + const expectedCallCount = reactMajor >= 19 ? 6 : 10; expect(getDetailPanelHeight.callCount).to.equal(expectedCallCount); await user.click(screen.getByRole('button', { name: 'Expand' })); @@ -441,7 +441,7 @@ describe(' - Detail panel', () => { // from React 19 it is: // 1x during state initialization // + 1x when sortedRowsSet is fired - const expectedCallCount = reactMajor >= 19 ? 4 : 6; + const expectedCallCount = reactMajor >= 19 ? 3 : 5; expect(getDetailPanelHeight.callCount).to.equal(expectedCallCount); expect(getDetailPanelHeight.lastCall.args[0].id).to.equal(0); diff --git a/packages/x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx index 47f81e57e94bd..ed21e2eb64871 100644 --- a/packages/x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx @@ -811,7 +811,7 @@ describe(' - Row selection', () => { />, ); - expect(onRowSelectionModelChange.callCount).to.equal(4); + expect(onRowSelectionModelChange.callCount).to.equal(3); expect(onRowSelectionModelChange.lastCall.args[0]).to.deep.equal( includeRowSelection([2, 3, 4, 5, 6, 7, 1]), ); diff --git a/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx b/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx index 4d774bfcbf924..9f5498f6f55f7 100644 --- a/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx +++ b/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx @@ -94,6 +94,7 @@ export const useGridFilter = ( | 'disableColumnFilter' | 'disableEval' | 'ignoreDiacritics' + | 'signature' >, configuration: GridConfiguration, ): void => { @@ -546,7 +547,9 @@ export const useGridFilter = ( * 1ST RENDER */ useFirstRender(() => { - updateFilteredRows(); + if (props.signature === 'DataGrid') { + updateFilteredRows(); + } }); /** diff --git a/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts b/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts index a40c01f7bd4b1..62d79e2a864ac 100644 --- a/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts +++ b/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts @@ -64,6 +64,7 @@ export const useGridSorting = ( | 'disableColumnSorting' | 'disableMultipleColumnsSorting' | 'multipleColumnsSortingMode' + | 'signature' >, ) => { const logger = useGridLogger(apiRef, 'useGridSorting'); @@ -373,7 +374,9 @@ export const useGridSorting = ( * 1ST RENDER */ useFirstRender(() => { - apiRef.current.applySorting(); + if (props.signature === 'DataGrid') { + apiRef.current.applySorting(); + } }); /** From 12c7e97712ac82974a088457f9ce38a99e03f851 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Wed, 15 Oct 2025 01:17:38 +0200 Subject: [PATCH 51/60] update call count --- .../src/tests/detailPanel.DataGridPro.test.tsx | 6 +++--- .../src/tests/rowSelection.DataGridPro.test.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/x-data-grid-pro/src/tests/detailPanel.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/detailPanel.DataGridPro.test.tsx index a093718f20249..bfde54c96ea37 100644 --- a/packages/x-data-grid-pro/src/tests/detailPanel.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/detailPanel.DataGridPro.test.tsx @@ -326,7 +326,7 @@ describe(' - Detail panel', () => { // from React 19 it is: // 2x during state initialization // + 2x when sortedRowsSet is fired - const expectedCallCount = reactMajor >= 19 ? 6 : 10; + const expectedCallCount = reactMajor >= 19 ? 6 : 8; expect(getDetailPanelContent.callCount).to.equal(expectedCallCount); await user.click(screen.getByRole('button', { name: 'Expand' })); @@ -368,7 +368,7 @@ describe(' - Detail panel', () => { // from React 19 it is: // 2x during state initialization // + 2x when sortedRowsSet is fired - const expectedCallCount = reactMajor >= 19 ? 6 : 10; + const expectedCallCount = reactMajor >= 19 ? 6 : 8; expect(getDetailPanelHeight.callCount).to.equal(expectedCallCount); await user.click(screen.getByRole('button', { name: 'Expand' })); @@ -441,7 +441,7 @@ describe(' - Detail panel', () => { // from React 19 it is: // 1x during state initialization // + 1x when sortedRowsSet is fired - const expectedCallCount = reactMajor >= 19 ? 3 : 5; + const expectedCallCount = reactMajor >= 19 ? 3 : 4; expect(getDetailPanelHeight.callCount).to.equal(expectedCallCount); expect(getDetailPanelHeight.lastCall.args[0].id).to.equal(0); diff --git a/packages/x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx index ed21e2eb64871..47f81e57e94bd 100644 --- a/packages/x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx @@ -811,7 +811,7 @@ describe(' - Row selection', () => { />, ); - expect(onRowSelectionModelChange.callCount).to.equal(3); + expect(onRowSelectionModelChange.callCount).to.equal(4); expect(onRowSelectionModelChange.lastCall.args[0]).to.deep.equal( includeRowSelection([2, 3, 4, 5, 6, 7, 1]), ); From beb0a5752609fc6a476309bab35aa6f8aa8cd881 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Wed, 15 Oct 2025 01:25:56 +0200 Subject: [PATCH 52/60] fix --- .../src/DataGridPro/useDataGridProComponent.tsx | 4 ++++ packages/x-data-grid/src/DataGrid/useDataGridComponent.tsx | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx index fbbe06fc1079c..bbe70c85aadeb 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx @@ -54,6 +54,7 @@ import { listViewStateInitializer, propsStateInitializer, GridConfiguration, + useFirstRender, } from '@mui/x-data-grid/internals'; import { GridPrivateApiPro } from '../models/gridApiPro'; import { DataGridProProcessedProps } from '../models/dataGridProProps'; @@ -191,6 +192,9 @@ export const useDataGridProComponent = ( useGridListView(apiRef, props); // Should be the last thing to run, because all pre-processors should have been registered by now. + useFirstRender(() => { + apiRef.current.runAppliersForPendingProcessors(); + }); React.useEffect(() => { apiRef.current.runAppliersForPendingProcessors(); }); diff --git a/packages/x-data-grid/src/DataGrid/useDataGridComponent.tsx b/packages/x-data-grid/src/DataGrid/useDataGridComponent.tsx index a666bfdde0b8a..af935855192ec 100644 --- a/packages/x-data-grid/src/DataGrid/useDataGridComponent.tsx +++ b/packages/x-data-grid/src/DataGrid/useDataGridComponent.tsx @@ -67,6 +67,7 @@ import { import { propsStateInitializer } from '../hooks/core/useGridProps'; import { useGridDataSource } from '../hooks/features/dataSource/useGridDataSource'; import { GridConfiguration } from '../models/configuration/gridConfiguration'; +import { useFirstRender } from '@mui/x-internals/useFirstRender'; export const useDataGridComponent = ( apiRef: RefObject, @@ -133,6 +134,9 @@ export const useDataGridComponent = ( useGridDataSource(apiRef, props); // Should be the last thing to run, because all pre-processors should have been registered by now. + useFirstRender(() => { + apiRef.current.runAppliersForPendingProcessors(); + }); React.useEffect(() => { apiRef.current.runAppliersForPendingProcessors(); }); From 610a270606f09be97ae57d54bfe40714f1808a19 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Wed, 15 Oct 2025 01:35:19 +0200 Subject: [PATCH 53/60] update call counts --- .../src/tests/detailPanel.DataGridPro.test.tsx | 6 +++--- .../src/tests/rowSelection.DataGridPro.test.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/x-data-grid-pro/src/tests/detailPanel.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/detailPanel.DataGridPro.test.tsx index bfde54c96ea37..a093718f20249 100644 --- a/packages/x-data-grid-pro/src/tests/detailPanel.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/detailPanel.DataGridPro.test.tsx @@ -326,7 +326,7 @@ describe(' - Detail panel', () => { // from React 19 it is: // 2x during state initialization // + 2x when sortedRowsSet is fired - const expectedCallCount = reactMajor >= 19 ? 6 : 8; + const expectedCallCount = reactMajor >= 19 ? 6 : 10; expect(getDetailPanelContent.callCount).to.equal(expectedCallCount); await user.click(screen.getByRole('button', { name: 'Expand' })); @@ -368,7 +368,7 @@ describe(' - Detail panel', () => { // from React 19 it is: // 2x during state initialization // + 2x when sortedRowsSet is fired - const expectedCallCount = reactMajor >= 19 ? 6 : 8; + const expectedCallCount = reactMajor >= 19 ? 6 : 10; expect(getDetailPanelHeight.callCount).to.equal(expectedCallCount); await user.click(screen.getByRole('button', { name: 'Expand' })); @@ -441,7 +441,7 @@ describe(' - Detail panel', () => { // from React 19 it is: // 1x during state initialization // + 1x when sortedRowsSet is fired - const expectedCallCount = reactMajor >= 19 ? 3 : 4; + const expectedCallCount = reactMajor >= 19 ? 3 : 5; expect(getDetailPanelHeight.callCount).to.equal(expectedCallCount); expect(getDetailPanelHeight.lastCall.args[0].id).to.equal(0); diff --git a/packages/x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx index 47f81e57e94bd..ed21e2eb64871 100644 --- a/packages/x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx @@ -811,7 +811,7 @@ describe(' - Row selection', () => { />, ); - expect(onRowSelectionModelChange.callCount).to.equal(4); + expect(onRowSelectionModelChange.callCount).to.equal(3); expect(onRowSelectionModelChange.lastCall.args[0]).to.deep.equal( includeRowSelection([2, 3, 4, 5, 6, 7, 1]), ); From d60b3988e0f6433a5cbdb306d3a23b5d69052650 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Wed, 15 Oct 2025 02:02:58 +0200 Subject: [PATCH 54/60] lint --- packages/x-data-grid/src/DataGrid/useDataGridComponent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-data-grid/src/DataGrid/useDataGridComponent.tsx b/packages/x-data-grid/src/DataGrid/useDataGridComponent.tsx index af935855192ec..41b1f58805b5c 100644 --- a/packages/x-data-grid/src/DataGrid/useDataGridComponent.tsx +++ b/packages/x-data-grid/src/DataGrid/useDataGridComponent.tsx @@ -1,6 +1,7 @@ 'use client'; import * as React from 'react'; import { RefObject } from '@mui/x-internals/types'; +import { useFirstRender } from '@mui/x-internals/useFirstRender'; import { DataGridProcessedProps } from '../models/props/DataGridProps'; import { GridPrivateApiCommunity } from '../models/api/gridApiCommunity'; import { useGridInitialization } from '../hooks/core/useGridInitialization'; @@ -67,7 +68,6 @@ import { import { propsStateInitializer } from '../hooks/core/useGridProps'; import { useGridDataSource } from '../hooks/features/dataSource/useGridDataSource'; import { GridConfiguration } from '../models/configuration/gridConfiguration'; -import { useFirstRender } from '@mui/x-internals/useFirstRender'; export const useDataGridComponent = ( apiRef: RefObject, From e8a5d852741acb5d32ff0ec7fa73c8936b547858 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Wed, 15 Oct 2025 02:22:50 +0200 Subject: [PATCH 55/60] fix --- .../features/aggregation/useGridAggregation.ts | 2 +- .../src/hooks/features/rows/useGridRowSpanning.ts | 8 ++++++-- .../src/hooks/utils/useRunOncePerLoop.ts | 15 +++++++++++++-- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts index ee2412c3fb4be..0b833f515abe8 100644 --- a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts +++ b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts @@ -222,7 +222,7 @@ export const useGridAggregation = ( }; }, []); - const deferredApplyAggregation = useRunOncePerLoop(applyAggregation); + const { schedule: deferredApplyAggregation } = useRunOncePerLoop(applyAggregation); const aggregationApi: GridAggregationApi = { setAggregationModel, 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 a47a7c1db9f76..c392b27297aed 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts @@ -300,7 +300,8 @@ export const useGridRowSpanning = ( // - The sorting is applied // - The `paginationModel` is updated // - The rows are updated - const deferredUpdateRowSpawnningState = useRunOncePerLoop(updateRowSpanningState); + const { schedule: deferredUpdateRowSpawnningState, cancel } = + useRunOncePerLoop(updateRowSpanningState); const resetRowSpanningState = React.useCallback(() => { const renderContext = gridRenderContextSelector(apiRef); @@ -313,7 +314,10 @@ export const useGridRowSpanning = ( useGridEvent( apiRef, 'renderedRowsIntervalChange', - runIf(props.rowSpanning, resetRowSpanningState), + runIf(props.rowSpanning, (renderContext: GridRenderContext) => { + cancel(); + updateRowSpanningState(renderContext); + }), ); useGridEvent(apiRef, 'sortedRowsSet', runIf(props.rowSpanning, resetRowSpanningState)); diff --git a/packages/x-data-grid/src/hooks/utils/useRunOncePerLoop.ts b/packages/x-data-grid/src/hooks/utils/useRunOncePerLoop.ts index cd987f0676cc3..a1c6b47b1ca39 100644 --- a/packages/x-data-grid/src/hooks/utils/useRunOncePerLoop.ts +++ b/packages/x-data-grid/src/hooks/utils/useRunOncePerLoop.ts @@ -1,11 +1,17 @@ 'use client'; import * as React from 'react'; -export function useRunOncePerLoop void>(callback: T) { +export function useRunOncePerLoop void>( + callback: T, + firstCall?: boolean, +) { const scheduledCallbackRef = React.useRef<(...args: any) => void>(null); const schedule = React.useCallback( (...args: Parameters) => { + if (scheduledCallbackRef.current && firstCall) { + return; + } scheduledCallbackRef.current = () => { scheduledCallbackRef.current = null; callback(...args); @@ -20,5 +26,10 @@ export function useRunOncePerLoop void>(callback: } }); - return schedule; + return { + schedule, + cancel: () => { + scheduledCallbackRef.current = null; + }, + }; } From 249c56715995f9c29a6977b6f9cd3684b1cb5df0 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Wed, 15 Oct 2025 02:23:23 +0200 Subject: [PATCH 56/60] fix --- packages/x-data-grid/src/hooks/utils/useRunOncePerLoop.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/x-data-grid/src/hooks/utils/useRunOncePerLoop.ts b/packages/x-data-grid/src/hooks/utils/useRunOncePerLoop.ts index a1c6b47b1ca39..68e18050e313a 100644 --- a/packages/x-data-grid/src/hooks/utils/useRunOncePerLoop.ts +++ b/packages/x-data-grid/src/hooks/utils/useRunOncePerLoop.ts @@ -1,17 +1,11 @@ 'use client'; import * as React from 'react'; -export function useRunOncePerLoop void>( - callback: T, - firstCall?: boolean, -) { +export function useRunOncePerLoop void>(callback: T) { const scheduledCallbackRef = React.useRef<(...args: any) => void>(null); const schedule = React.useCallback( (...args: Parameters) => { - if (scheduledCallbackRef.current && firstCall) { - return; - } scheduledCallbackRef.current = () => { scheduledCallbackRef.current = null; callback(...args); From c87bede0bb978013642d0806befa1441653f0d65 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Wed, 15 Oct 2025 09:16:33 +0200 Subject: [PATCH 57/60] fix rowspan tests --- .../x-data-grid/src/components/GridRow.tsx | 9 +- .../hooks/features/rows/useGridRowSpanning.ts | 85 +------------------ .../src/hooks/utils/useRunOncePerLoop.ts | 6 +- 3 files changed, 17 insertions(+), 83 deletions(-) diff --git a/packages/x-data-grid/src/components/GridRow.tsx b/packages/x-data-grid/src/components/GridRow.tsx index 93b11e2f93839..a35849acd5d30 100644 --- a/packages/x-data-grid/src/components/GridRow.tsx +++ b/packages/x-data-grid/src/components/GridRow.tsx @@ -278,7 +278,14 @@ const GridRow = forwardRef(function GridRow(props, } return rowStyle; - }, [isNotVisible, rowHeight, styleProp, heightEntry, rootProps.rowSpacingType]); + }, [ + isNotVisible, + rowHeight, + styleProp, + heightEntry.spacingTop, + heightEntry.spacingBottom, + rootProps.rowSpacingType, + ]); // HACK: Sometimes, the rowNode has already been removed from the state but the row is still rendered. if (!rowNode) { 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 c392b27297aed..d0f635598b9b1 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts @@ -15,8 +15,6 @@ import type { GridStateInitializer } from '../../utils/useGridInitializeState'; import { getUnprocessedRange, isRowContextInitialized, getCellValue } from './gridRowSpanningUtils'; import { useGridEvent } from '../../utils/useGridEvent'; import { runIf } from '../../../utils/utils'; -import { gridPageSizeSelector } from '../pagination'; -import { gridDataRowIdsSelector } from './gridRowsSelector'; import { useRunOncePerLoop } from '../../utils/useRunOncePerLoop'; export interface GridRowSpanningState extends RowSpanningState {} @@ -31,13 +29,6 @@ const EMPTY_CACHES: RowSpanningState['caches'] = { const EMPTY_RANGE: RowRange = { firstRowIndex: 0, lastRowIndex: 0 }; const EMPTY_STATE = { caches: EMPTY_CACHES, processedRange: EMPTY_RANGE }; -/** - * Default number of rows to process during state initialization to avoid flickering. - * Number `20` is arbitrarily chosen to be large enough to cover most of the cases without - * compromising performance. - */ -const DEFAULT_ROWS_TO_PROCESS = 20; - const computeRowSpanningState = ( apiRef: RefObject, colDefs: GridColDef[], @@ -151,83 +142,15 @@ const computeRowSpanningState = ( return { caches: { spannedCells, hiddenCells, hiddenCellOriginMap }, processedRange }; }; -const getInitialRangeToProcess = ( - props: Pick, - apiRef: React.RefObject, -) => { - const rowCount = gridDataRowIdsSelector(apiRef).length; - - if (props.pagination) { - const pageSize = gridPageSizeSelector(apiRef); - let paginationLastRowIndex = DEFAULT_ROWS_TO_PROCESS; - if (pageSize > 0) { - paginationLastRowIndex = pageSize - 1; - } - return { - firstRowIndex: 0, - lastRowIndex: Math.min(paginationLastRowIndex, rowCount), - }; - } - - return { - firstRowIndex: 0, - lastRowIndex: Math.min(DEFAULT_ROWS_TO_PROCESS, rowCount), - }; -}; - /** * @requires columnsStateInitializer (method) - should be initialized before * @requires rowsStateInitializer (method) - should be initialized before * @requires filterStateInitializer (method) - should be initialized before */ -export const rowSpanningStateInitializer: GridStateInitializer = (state, props, apiRef) => { - if (!props.rowSpanning) { - return { - ...state, - rowSpanning: EMPTY_STATE, - }; - } - - 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: EMPTY_STATE, - }; - } - - const rangeToProcess = getInitialRangeToProcess(props, apiRef); - const rows = rowIds.map((id) => ({ - id, - model: dataRowIdToModelLookup[id!], - })) as GridRowEntry[]; - const colDefs = orderedFields.map((field) => columnsLookup[field!]) as GridColDef[]; - - const rowSpanning = computeRowSpanningState( - apiRef, - colDefs, - rows, - rangeToProcess, - rangeToProcess, - true, - ); - +export const rowSpanningStateInitializer: GridStateInitializer = (state) => { return { ...state, - rowSpanning, + rowSpanning: EMPTY_STATE, }; }; @@ -315,8 +238,8 @@ export const useGridRowSpanning = ( apiRef, 'renderedRowsIntervalChange', runIf(props.rowSpanning, (renderContext: GridRenderContext) => { - cancel(); - updateRowSpanningState(renderContext); + const didHavePendingReset = cancel(); + updateRowSpanningState(renderContext, didHavePendingReset); }), ); diff --git a/packages/x-data-grid/src/hooks/utils/useRunOncePerLoop.ts b/packages/x-data-grid/src/hooks/utils/useRunOncePerLoop.ts index 68e18050e313a..d10c6c302152b 100644 --- a/packages/x-data-grid/src/hooks/utils/useRunOncePerLoop.ts +++ b/packages/x-data-grid/src/hooks/utils/useRunOncePerLoop.ts @@ -23,7 +23,11 @@ export function useRunOncePerLoop void>(callback: return { schedule, cancel: () => { - scheduledCallbackRef.current = null; + if (scheduledCallbackRef.current) { + scheduledCallbackRef.current = null; + return true; + } + return false; }, }; } From 18bd398a2084232ee06df82fdead8d22160f11f1 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Wed, 15 Oct 2025 12:14:53 +0200 Subject: [PATCH 58/60] fix error when toggling back from pivot --- .../src/hooks/features/columnGrouping/useGridColumnGrouping.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/x-data-grid/src/hooks/features/columnGrouping/useGridColumnGrouping.ts b/packages/x-data-grid/src/hooks/features/columnGrouping/useGridColumnGrouping.ts index ae170fa9d3f52..fa8bf27c50dab 100644 --- a/packages/x-data-grid/src/hooks/features/columnGrouping/useGridColumnGrouping.ts +++ b/packages/x-data-grid/src/hooks/features/columnGrouping/useGridColumnGrouping.ts @@ -24,7 +24,8 @@ export const columnGroupsStateInitializer: GridStateInitializer< Pick > = (state, props, apiRef) => { apiRef.current.caches.columnGrouping = { - lastColumnGroupingModel: props.columnGroupingModel, + lastColumnGroupingModel: + props.columnGroupingModel ?? apiRef.current.caches.columnGrouping?.lastColumnGroupingModel, }; if (!props.columnGroupingModel) { return state; From 10853a07b2d48a0310ce12d80caab181615124e4 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Wed, 15 Oct 2025 12:25:29 +0200 Subject: [PATCH 59/60] better fix --- .../columnGrouping/useGridColumnGrouping.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/x-data-grid/src/hooks/features/columnGrouping/useGridColumnGrouping.ts b/packages/x-data-grid/src/hooks/features/columnGrouping/useGridColumnGrouping.ts index fa8bf27c50dab..fd4720ffef800 100644 --- a/packages/x-data-grid/src/hooks/features/columnGrouping/useGridColumnGrouping.ts +++ b/packages/x-data-grid/src/hooks/features/columnGrouping/useGridColumnGrouping.ts @@ -24,11 +24,18 @@ export const columnGroupsStateInitializer: GridStateInitializer< Pick > = (state, props, apiRef) => { apiRef.current.caches.columnGrouping = { - lastColumnGroupingModel: - props.columnGroupingModel ?? apiRef.current.caches.columnGrouping?.lastColumnGroupingModel, + lastColumnGroupingModel: props.columnGroupingModel, }; if (!props.columnGroupingModel) { - return state; + return { + ...state, + columnGrouping: { + lookup: undefined, + unwrappedGroupingModel: undefined, + headerStructure: undefined, + maxDepth: undefined, + }, + }; } const columnFields = gridColumnFieldsSelector(apiRef); From e1383554bfb0a958802426fa8488a011cc76cd44 Mon Sep 17 00:00:00 2001 From: lauri865 Date: Wed, 15 Oct 2025 12:26:00 +0200 Subject: [PATCH 60/60] or even --- .../hooks/features/columnGrouping/useGridColumnGrouping.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/x-data-grid/src/hooks/features/columnGrouping/useGridColumnGrouping.ts b/packages/x-data-grid/src/hooks/features/columnGrouping/useGridColumnGrouping.ts index fd4720ffef800..d698864ceee6b 100644 --- a/packages/x-data-grid/src/hooks/features/columnGrouping/useGridColumnGrouping.ts +++ b/packages/x-data-grid/src/hooks/features/columnGrouping/useGridColumnGrouping.ts @@ -29,12 +29,7 @@ export const columnGroupsStateInitializer: GridStateInitializer< if (!props.columnGroupingModel) { return { ...state, - columnGrouping: { - lookup: undefined, - unwrappedGroupingModel: undefined, - headerStructure: undefined, - maxDepth: undefined, - }, + columnGrouping: undefined, }; }