diff --git a/packages/x-data-grid-generator/src/hooks/useMockServer.ts b/packages/x-data-grid-generator/src/hooks/useMockServer.ts index 3f877d8fa1f66..9d7c06a599a7a 100644 --- a/packages/x-data-grid-generator/src/hooks/useMockServer.ts +++ b/packages/x-data-grid-generator/src/hooks/useMockServer.ts @@ -42,6 +42,7 @@ type UseMockServerResponse = { getChildrenCount?: (row: GridRowModel) => number; fetchRows: (url: string) => Promise; loadNewData: () => void; + isReady: boolean; }; type DataSet = 'Commodity' | 'Employee' | 'Movies'; @@ -367,5 +368,6 @@ export const useMockServer = ( loadNewData: () => { setIndex((oldIndex) => oldIndex + 1); }, + isReady: Boolean(data?.rows?.length), }; }; diff --git a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx index 440379f1033d1..c11e362499975 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx @@ -153,8 +153,8 @@ export const useDataGridPremiumComponent = ( useGridRowGrouping(apiRef, props); useGridHeaderFiltering(apiRef, props); useGridTreeData(apiRef, props); - useGridDataSource(apiRef, props); useGridAggregation(apiRef, props); + useGridDataSource(apiRef, props); useGridKeyboardNavigation(apiRef, props); useGridRowSelection(apiRef, props); useGridCellSelection(apiRef, props); diff --git a/packages/x-data-grid-premium/src/tests/dataSourceAggregation.DataGridPremium.test.tsx b/packages/x-data-grid-premium/src/tests/dataSourceAggregation.DataGridPremium.test.tsx index 8a67f0acf0957..a46c1cd88128d 100644 --- a/packages/x-data-grid-premium/src/tests/dataSourceAggregation.DataGridPremium.test.tsx +++ b/packages/x-data-grid-premium/src/tests/dataSourceAggregation.DataGridPremium.test.tsx @@ -13,7 +13,7 @@ import { GRID_AGGREGATION_ROOT_FOOTER_ROW_ID, GRID_ROOT_GROUP_ID, } from '@mui/x-data-grid-premium'; -import { SinonSpy, spy } from 'sinon'; +import { spy } from 'sinon'; import { getColumnHeaderCell } from 'test/utils/helperFn'; const isJSDOM = /jsdom/.test(window.navigator.userAgent); @@ -22,9 +22,17 @@ describe(' - Data source aggregation', () => { const { render } = createRenderer(); let apiRef: RefObject; - let getRowsSpy: SinonSpy; + const fetchRowsSpy = spy(); let mockServer: ReturnType; + // TODO: Resets strictmode calls, need to find a better fix for this, maybe an AbortController? + function Reset() { + React.useLayoutEffect(() => { + fetchRowsSpy.resetHistory(); + }, []); + return null; + } + function TestDataSourceAggregation( props: Partial & { getAggregatedValue?: GridDataSource['getAggregatedValue']; @@ -39,8 +47,8 @@ describe(' - Data source aggregation', () => { const { fetchRows } = mockServer; - const dataSource: GridDataSource = React.useMemo( - () => ({ + const dataSource: GridDataSource = React.useMemo(() => { + return { getRows: async (params: GridGetRowsParams) => { const urlParams = new URLSearchParams({ filterModel: JSON.stringify(params.filterModel), @@ -49,6 +57,8 @@ describe(' - Data source aggregation', () => { aggregationModel: JSON.stringify(params.aggregationModel), }); + fetchRowsSpy(params); + const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, ); @@ -64,12 +74,8 @@ describe(' - Data source aggregation', () => { ((row, field) => { return row[`${field}Aggregate`]; }), - }), - [fetchRows, getAggregatedValueProp], - ); - - getRowsSpy?.restore(); - getRowsSpy = spy(dataSource, 'getRows'); + }; + }, [fetchRows, getAggregatedValueProp]); const baselineProps = { unstable_dataSource: dataSource, @@ -84,8 +90,13 @@ describe(' - Data source aggregation', () => { }, }; + if (!mockServer.isReady) { + return null; + } + return (
+
); @@ -100,7 +111,7 @@ describe(' - Data source aggregation', () => { it('should show aggregation option in the column menu', async () => { const { user } = render(); await waitFor(() => { - expect(getRowsSpy.callCount).to.be.greaterThan(0); + expect(fetchRowsSpy.callCount).to.be.greaterThan(0); }); await user.click(within(getColumnHeaderCell(0)).getByLabelText('Menu')); expect(screen.queryByLabelText('Aggregation')).not.to.equal(null); @@ -109,7 +120,7 @@ describe(' - Data source aggregation', () => { it('should not show aggregation option in the column menu when no aggregation function is defined', async () => { const { user } = render(); await waitFor(() => { - expect(getRowsSpy.callCount).to.be.greaterThan(0); + expect(fetchRowsSpy.callCount).to.be.greaterThan(0); }); await user.click(within(getColumnHeaderCell(0)).getByLabelText('Menu')); expect(screen.queryByLabelText('Aggregation')).to.equal(null); @@ -124,9 +135,10 @@ describe(' - Data source aggregation', () => { />, ); await waitFor(() => { - expect(getRowsSpy.callCount).to.be.greaterThan(0); + expect(fetchRowsSpy.callCount).to.be.greaterThan(0); }); - expect(getRowsSpy.args[0][0].aggregationModel).to.deep.equal({ id: 'size' }); + + expect(fetchRowsSpy.lastCall.args[0].aggregationModel).to.deep.equal({ id: 'size' }); }); it('should show the aggregation footer row when aggregation is enabled', async () => { @@ -146,12 +158,13 @@ describe(' - Data source aggregation', () => { }); it('should derive the aggregation values using `dataSource.getAggregatedValue`', async () => { + const getAggregatedValue = () => 'Agg value'; render( 'Agg value'} + getAggregatedValue={getAggregatedValue} />, ); await waitFor(() => { diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts index 9bb5eae0ce7fd..a9abd20fb713e 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts @@ -4,7 +4,6 @@ import { throttle } from '@mui/x-internals/throttle'; import { unstable_debounce as debounce } from '@mui/utils'; import { useGridApiEventHandler, - useGridSelector, gridSortModelSelector, gridFilterModelSelector, GridEventListener, @@ -70,11 +69,6 @@ export const useGridDataSourceLazyLoader = ( const [lazyLoadingRowsUpdateStrategyActive, setLazyLoadingRowsUpdateStrategyActive] = React.useState(false); - const sortModel = useGridSelector(privateApiRef, gridSortModelSelector); - const filterModel = useGridSelector(privateApiRef, gridFilterModelSelector); - const paginationModel = useGridSelector(privateApiRef, gridPaginationModelSelector); - const filteredSortedRowIds = useGridSelector(privateApiRef, gridFilteredSortedRowIdsSelector); - const dimensions = useGridSelector(privateApiRef, gridDimensionsSelector); const renderedRowsIntervalCache = React.useRef(INTERVAL_CACHE_INITIAL_STATE); const previousLastRowIndex = React.useRef(0); const loadingTrigger = React.useRef(null); @@ -97,13 +91,15 @@ export const useGridDataSourceLazyLoader = ( return params; } + const paginationModel = gridPaginationModelSelector(privateApiRef); + return { ...params, start: params.start - (params.start % paginationModel.pageSize), end: params.end + paginationModel.pageSize - (params.end % paginationModel.pageSize) - 1, }; }, - [paginationModel], + [privateApiRef], ); const resetGrid = React.useCallback(() => { @@ -111,6 +107,9 @@ export const useGridDataSourceLazyLoader = ( privateApiRef.current.unstable_dataSource.cache.clear(); rowsStale.current = true; previousLastRowIndex.current = 0; + const paginationModel = gridPaginationModelSelector(privateApiRef); + const sortModel = gridSortModelSelector(privateApiRef); + const filterModel = gridFilterModelSelector(privateApiRef); const getRowsParams: GridGetRowsParams = { start: 0, end: paginationModel.pageSize - 1, @@ -119,7 +118,7 @@ export const useGridDataSourceLazyLoader = ( }; fetchRows(getRowsParams); - }, [privateApiRef, sortModel, filterModel, paginationModel.pageSize, fetchRows]); + }, [privateApiRef, fetchRows]); const ensureValidRowCount = React.useCallback( (previousLoadingTrigger: LoadingTrigger, newLoadingTrigger: LoadingTrigger) => { @@ -280,6 +279,8 @@ export const useGridDataSourceLazyLoader = ( rebuildSkeletonRows(); } + const filteredSortedRowIds = gridFilteredSortedRowIdsSelector(privateApiRef); + const startingIndex = typeof fetchParams.start === 'string' ? Math.max(filteredSortedRowIds.indexOf(fetchParams.start), 0) @@ -303,13 +304,7 @@ export const useGridDataSourceLazyLoader = ( ); privateApiRef.current.requestPipeProcessorsApplication('hydrateRows'); }, - [ - privateApiRef, - filteredSortedRowIds, - updateLoadingTrigger, - rebuildSkeletonRows, - addSkeletonRows, - ], + [privateApiRef, updateLoadingTrigger, rebuildSkeletonRows, addSkeletonRows], ); const handleRowCountChange = React.useCallback(() => { @@ -333,12 +328,16 @@ export const useGridDataSourceLazyLoader = ( return; } + const dimensions = gridDimensionsSelector(privateApiRef.current.state); const position = newScrollPosition.top + dimensions.viewportInnerSize.height; const target = dimensions.contentSize.height - props.scrollEndThreshold; if (position >= target) { previousLastRowIndex.current = renderContext.lastRowIndex; + const paginationModel = gridPaginationModelSelector(privateApiRef); + const sortModel = gridSortModelSelector(privateApiRef); + const filterModel = gridFilterModelSelector(privateApiRef); const getRowsParams: GridGetRowsParams = { start: renderContext.lastRowIndex, end: renderContext.lastRowIndex + paginationModel.pageSize - 1, @@ -347,19 +346,11 @@ export const useGridDataSourceLazyLoader = ( }; privateApiRef.current.setLoading(true); + fetchRows(adjustRowParams(getRowsParams)); } }, - [ - privateApiRef, - props.scrollEndThreshold, - sortModel, - filterModel, - dimensions, - paginationModel.pageSize, - adjustRowParams, - fetchRows, - ], + [privateApiRef, props.scrollEndThreshold, adjustRowParams, fetchRows], ); const handleRenderedRowsIntervalChange = React.useCallback< @@ -370,6 +361,8 @@ export const useGridDataSourceLazyLoader = ( return; } + const sortModel = gridSortModelSelector(privateApiRef); + const filterModel = gridFilterModelSelector(privateApiRef); const getRowsParams: GridGetRowsParams = { start: params.firstRowIndex, end: params.lastRowIndex, @@ -389,10 +382,7 @@ export const useGridDataSourceLazyLoader = ( lastRowToRender: params.lastRowIndex, }; - const currentVisibleRows = getVisibleRows(privateApiRef, { - pagination: props.pagination, - paginationMode: props.paginationMode, - }); + const currentVisibleRows = getVisibleRows(privateApiRef); const skeletonRowsSection = findSkeletonRowsSection({ apiRef: privateApiRef, @@ -412,27 +402,26 @@ export const useGridDataSourceLazyLoader = ( fetchRows(adjustRowParams(getRowsParams)); }, - [ - privateApiRef, - props.pagination, - props.paginationMode, - sortModel, - filterModel, - adjustRowParams, - fetchRows, - ], + [privateApiRef, adjustRowParams, fetchRows], ); const throttledHandleRenderedRowsIntervalChange = React.useMemo( () => throttle(handleRenderedRowsIntervalChange, props.unstable_lazyLoadingRequestThrottleMs), [props.unstable_lazyLoadingRequestThrottleMs, handleRenderedRowsIntervalChange], ); + React.useEffect(() => { + return () => { + throttledHandleRenderedRowsIntervalChange.clear(); + }; + }, [throttledHandleRenderedRowsIntervalChange]); const handleGridSortModelChange = React.useCallback>( (newSortModel) => { rowsStale.current = true; + throttledHandleRenderedRowsIntervalChange.clear(); previousLastRowIndex.current = 0; const renderContext = gridRenderContextSelector(privateApiRef); + const paginationModel = gridPaginationModelSelector(privateApiRef); const rangeParams = loadingTrigger.current === LoadingTrigger.VIEWPORT ? { @@ -444,6 +433,8 @@ export const useGridDataSourceLazyLoader = ( end: paginationModel.pageSize - 1, }; + const filterModel = gridFilterModelSelector(privateApiRef); + const getRowsParams: GridGetRowsParams = { ...rangeParams, sortModel: newSortModel, @@ -453,13 +444,17 @@ export const useGridDataSourceLazyLoader = ( privateApiRef.current.setLoading(true); debouncedFetchRows(adjustRowParams(getRowsParams)); }, - [privateApiRef, filterModel, paginationModel.pageSize, adjustRowParams, debouncedFetchRows], + [privateApiRef, adjustRowParams, debouncedFetchRows, throttledHandleRenderedRowsIntervalChange], ); const handleGridFilterModelChange = React.useCallback>( (newFilterModel) => { rowsStale.current = true; + throttledHandleRenderedRowsIntervalChange.clear(); previousLastRowIndex.current = 0; + + const paginationModel = gridPaginationModelSelector(privateApiRef); + const sortModel = gridSortModelSelector(privateApiRef); const getRowsParams: GridGetRowsParams = { start: 0, end: paginationModel.pageSize - 1, @@ -470,7 +465,7 @@ export const useGridDataSourceLazyLoader = ( privateApiRef.current.setLoading(true); debouncedFetchRows(getRowsParams); }, - [privateApiRef, sortModel, paginationModel.pageSize, debouncedFetchRows], + [privateApiRef, debouncedFetchRows, throttledHandleRenderedRowsIntervalChange], ); const handleStrategyActivityChange = React.useCallback< diff --git a/packages/x-data-grid-pro/src/tests/dataSource.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/dataSource.DataGridPro.test.tsx index c5ef6d7cb2fcc..8ebd00c077291 100644 --- a/packages/x-data-grid-pro/src/tests/dataSource.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/dataSource.DataGridPro.test.tsx @@ -51,6 +51,14 @@ describeSkipIf(isJSDOM)(' - Data source', () => { let apiRef: RefObject; let mockServer: ReturnType; + // TODO: Resets strictmode calls, need to find a better fix for this, maybe an AbortController? + function Reset() { + React.useLayoutEffect(() => { + fetchRowsSpy.resetHistory(); + }, []); + return null; + } + function TestDataSource(props: Partial & { shouldRequestsFail?: boolean }) { apiRef = useGridApiRef(); mockServer = useMockServer( @@ -62,7 +70,6 @@ describeSkipIf(isJSDOM)(' - Data source', () => { const { fetchRows } = mockServer; const dataSource: GridDataSource = React.useMemo(() => { - fetchRowsSpy.resetHistory(); return { getRows: async (params: GridGetRowsParams) => { const urlParams = new URLSearchParams({ @@ -84,8 +91,13 @@ describeSkipIf(isJSDOM)(' - Data source', () => { }; }, [fetchRows]); + if (!mockServer.isReady) { + return null; + } + return (
+ - Data source lazy loader', () => { let apiRef: RefObject; let mockServer: ReturnType; + // TODO: Resets strictmode calls, need to find a better fix for this, maybe an AbortController? + function Reset() { + React.useLayoutEffect(() => { + fetchRowsSpy.resetHistory(); + }, []); + return null; + } + function TestDataSourceLazyLoader(props: Partial) { apiRef = useGridApiRef(); mockServer = useMockServer( @@ -36,7 +44,6 @@ describeSkipIf(isJSDOM)(' - Data source lazy loader', () => { const { fetchRows } = mockServer; const dataSource: GridDataSource = React.useMemo(() => { - fetchRowsSpy.resetHistory(); return { getRows: async (params: GridGetRowsParams) => { const urlParams = new URLSearchParams({ @@ -59,8 +66,13 @@ describeSkipIf(isJSDOM)(' - Data source lazy loader', () => { }; }, [fetchRows]); + if (!mockServer.isReady) { + return null; + } + return (
+ - Data source tree data', () => { let apiRef: RefObject; let mockServer: ReturnType; + // TODO: Resets strictmode calls, need to find a better fix for this, maybe an AbortController? + function Reset() { + React.useLayoutEffect(() => { + fetchRowsSpy.resetHistory(); + }, []); + return null; + } function TestDataSource(props: Partial & { shouldRequestsFail?: boolean }) { apiRef = useGridApiRef(); mockServer = useMockServer(dataSetOptions, serverOptions, props.shouldRequestsFail ?? false); @@ -43,7 +50,6 @@ describeSkipIf(isJSDOM)(' - Data source tree data', () => { const { fetchRows } = mockServer; const dataSource: GridDataSource = React.useMemo(() => { - fetchRowsSpy.resetHistory(); return { getRows: async (params: GridGetRowsParams) => { const urlParams = new URLSearchParams({ @@ -67,8 +73,13 @@ describeSkipIf(isJSDOM)(' - Data source tree data', () => { }; }, [fetchRows]); + if (!mockServer.isReady) { + return null; + } + return (
+