From 8348972e52cd03f86038c01f136ca04409f40285 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 18 Jul 2024 09:26:55 +0200 Subject: [PATCH 001/121] row indexes query params take precedence over pagination --- packages/x-data-grid-generator/src/hooks/serverUtils.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/x-data-grid-generator/src/hooks/serverUtils.ts b/packages/x-data-grid-generator/src/hooks/serverUtils.ts index 80651b133af5e..3b8d0eede1d59 100644 --- a/packages/x-data-grid-generator/src/hooks/serverUtils.ts +++ b/packages/x-data-grid-generator/src/hooks/serverUtils.ts @@ -277,7 +277,7 @@ export const loadServerRows = ( } const delay = randomInt(minDelay, maxDelay); - const { cursor, page = 0, pageSize } = queryOptions; + const { cursor, page = 0, pageSize, firstRowToRender, lastRowToRender } = queryOptions; let nextCursor; let firstRowIndex; @@ -289,7 +289,10 @@ export const loadServerRows = ( filteredRows = [...filteredRows].sort(rowComparator); const totalRowCount = filteredRows.length; - if (!pageSize) { + if (firstRowToRender !== undefined && lastRowToRender !== undefined) { + firstRowIndex = firstRowToRender; + lastRowIndex = lastRowToRender; + } else if (!pageSize) { firstRowIndex = 0; lastRowIndex = filteredRows.length; } else if (useCursorPagination) { From 1fb4c12e665e49d99e488da834fb1f912e962155 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 18 Jul 2024 09:27:46 +0200 Subject: [PATCH 002/121] Add basic (dev) demo for server side lazy loading --- .../server-side-data/ServerSideLazyLoading.js | 41 +++++++++++++++++ .../ServerSideLazyLoading.tsx | 45 +++++++++++++++++++ .../server-side-data/lazy-loading.md | 10 +---- docs/data/pages.ts | 18 +------- 4 files changed, 90 insertions(+), 24 deletions(-) create mode 100644 docs/data/data-grid/server-side-data/ServerSideLazyLoading.js create mode 100644 docs/data/data-grid/server-side-data/ServerSideLazyLoading.tsx diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoading.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoading.js new file mode 100644 index 0000000000000..0221c85e6e74a --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoading.js @@ -0,0 +1,41 @@ +import * as React from 'react'; +import { DataGridPro } from '@mui/x-data-grid-pro'; +import { useMockServer } from '@mui/x-data-grid-generator'; + +function ServerSideLazyLoading() { + const { columns, fetchRows } = useMockServer( + {}, + { useCursorPagination: false, minDelay: 300, maxDelay: 800 }, + ); + + const dataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + firstRowToRender: encodeURIComponent(JSON.stringify(params.start)), + lastRowToRender: encodeURIComponent(JSON.stringify(params.end)), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + lazyLoaded: true, + }), + [fetchRows], + ); + + return ( +
+ +
+ ); +} + +export default ServerSideLazyLoading; diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoading.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoading.tsx new file mode 100644 index 0000000000000..82ea65ae6cfac --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoading.tsx @@ -0,0 +1,45 @@ +import * as React from 'react'; +import { + DataGridPro, + GridDataSource, + GridGetRowsParams, +} from '@mui/x-data-grid-pro'; +import { useMockServer } from '@mui/x-data-grid-generator'; + +function ServerSideLazyLoading() { + const { columns, fetchRows } = useMockServer( + {}, + { useCursorPagination: false, minDelay: 300, maxDelay: 800 }, + ); + + const dataSource: GridDataSource = React.useMemo( + () => ({ + getRows: async (params: GridGetRowsParams) => { + const urlParams = new URLSearchParams({ + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + firstRowToRender: encodeURIComponent(JSON.stringify(params.start)), + lastRowToRender: encodeURIComponent(JSON.stringify(params.end)), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + lazyLoaded: true, + }), + [fetchRows], + ); + + return ( +
+ +
+ ); +} + +export default ServerSideLazyLoading; diff --git a/docs/data/data-grid/server-side-data/lazy-loading.md b/docs/data/data-grid/server-side-data/lazy-loading.md index 123fb3d6733c6..ed2616983fc4b 100644 --- a/docs/data/data-grid/server-side-data/lazy-loading.md +++ b/docs/data/data-grid/server-side-data/lazy-loading.md @@ -2,14 +2,8 @@ title: React Data Grid - Server-side lazy loading --- -# Data Grid - Server-side lazy loading [](/x/introduction/licensing/#pro-plan 'Pro plan')🚧 +# Data Grid - Server-side lazy loading [](/x/introduction/licensing/#pro-plan 'Pro plan')

Row lazy-loading with server-side data source.

-:::warning -This feature isn't implemented yet. It's coming. - -đź‘Ť Upvote [issue #10857](https://github.com/mui/mui-x/issues/10857) if you want to see it land faster. - -Don't hesitate to leave a comment on the same issue to influence what gets built. Especially if you already have a use case for this component, or if you are facing a pain point with the [current solution](https://mui.com/x/react-data-grid/row-updates/#lazy-loading). -::: +{{"demo": "ServerSideLazyLoading.js", "bg": "inline"}} diff --git a/docs/data/pages.ts b/docs/data/pages.ts index 1dac291fa82dc..6178f3eab502c 100644 --- a/docs/data/pages.ts +++ b/docs/data/pages.ts @@ -113,16 +113,8 @@ const pages: MuiPage[] = [ children: [ { pathname: '/x/react-data-grid/tree-data', plan: 'pro' }, { - pathname: '/x/react-data-grid/row-grouping-group', - title: 'Row grouping', - plan: 'premium', - children: [ - { pathname: '/x/react-data-grid/row-grouping', title: 'Overview' }, - { - pathname: '/x/react-data-grid/recipes-row-grouping', - title: 'Recipes', - }, - ], + pathname: '/x/react-data-grid/server-side-data/lazy-loading', + plan: 'pro', }, { pathname: '/x/react-data-grid/aggregation', plan: 'premium' }, { pathname: '/x/react-data-grid/pivoting', plan: 'premium', planned: true }, @@ -154,12 +146,6 @@ const pages: MuiPage[] = [ { pathname: '/x/react-data-grid/server-side-data/lazy-loading', plan: 'pro', - planned: true, - }, - { - pathname: '/x/react-data-grid/server-side-data/infinite-loading', - plan: 'pro', - planned: true, }, { pathname: '/x/react-data-grid/server-side-data/row-grouping', From 502aa0e58f1d3c2a799f516369c5accddfbfb9ba Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 18 Jul 2024 09:28:31 +0200 Subject: [PATCH 003/121] Extend data source model --- packages/x-data-grid/src/models/gridDataSource.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/x-data-grid/src/models/gridDataSource.ts b/packages/x-data-grid/src/models/gridDataSource.ts index 2df17b84cf2dc..ca02aef92fe4b 100644 --- a/packages/x-data-grid/src/models/gridDataSource.ts +++ b/packages/x-data-grid/src/models/gridDataSource.ts @@ -12,7 +12,7 @@ export interface GridGetRowsParams { /** * Alternate to `start` and `end`, maps to `GridPaginationModel` interface. */ - paginationModel: GridPaginationModel; + paginationModel?: GridPaginationModel; /** * First row index to fetch (number) or cursor information (number | string). */ @@ -20,7 +20,7 @@ export interface GridGetRowsParams { /** * Last row index to fetch. */ - end: number; // last row index to fetch + end: number; /** * List of grouped columns (only applicable with `rowGrouping`). */ @@ -76,6 +76,11 @@ export interface GridDataSource { * If the children count is not available for some reason, but there are some children, `getChildrenCount` should return `-1`. */ getChildrenCount?: (row: GridRowModel) => number; + /** + * If enabled, the grid will send `start` and `end` instead of `paginationModel` to `getRows` and a new request will be made + * whenever the user scrolls to the area that has skeleton rows + */ + lazyLoaded?: boolean; } export interface GridDataSourceCache { From 177aa27534e4ad0faf1dc69aac6742d87ad99086 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 18 Jul 2024 09:29:30 +0200 Subject: [PATCH 004/121] Update API docs --- .../x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx | 1 + packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx index feaef4ba95479..dc1d6de414473 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx @@ -1090,6 +1090,7 @@ DataGridPremiumRaw.propTypes = { getChildrenCount: PropTypes.func, getGroupKey: PropTypes.func, getRows: PropTypes.func.isRequired, + lazyLoaded: PropTypes.bool, updateRow: PropTypes.func, }), unstable_dataSourceCache: PropTypes.shape({ diff --git a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx index 2f7225467c205..c2f17091f4e76 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx @@ -990,6 +990,7 @@ DataGridProRaw.propTypes = { getChildrenCount: PropTypes.func, getGroupKey: PropTypes.func, getRows: PropTypes.func.isRequired, + lazyLoaded: PropTypes.bool, updateRow: PropTypes.func, }), unstable_dataSourceCache: PropTypes.shape({ From 9d9e5e0550a78207f989441f31e56eb2e0bca30e Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 18 Jul 2024 09:30:27 +0200 Subject: [PATCH 005/121] Add new event to be sent when new batch of rows should be loaded from the data source --- docs/data/data-grid/events/events.json | 7 +++++++ packages/x-data-grid-pro/src/typeOverloads/modules.ts | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/docs/data/data-grid/events/events.json b/docs/data/data-grid/events/events.json index a2831d2974c28..b5532855ed777 100644 --- a/docs/data/data-grid/events/events.json +++ b/docs/data/data-grid/events/events.json @@ -239,6 +239,13 @@ "params": "GridFilterModel", "event": "MuiEvent<{}>" }, + { + "projects": ["x-data-grid-pro", "x-data-grid-premium"], + "name": "getRows", + "description": "Fired when the grid needs to fetch a new batch of rows from the data source. Called with a GridGetRowsParams object.", + "params": "GridGetRowsParams", + "event": "MuiEvent<{}>" + }, { "projects": ["x-data-grid", "x-data-grid-pro", "x-data-grid-premium"], "name": "headerSelectionCheckboxChange", diff --git a/packages/x-data-grid-pro/src/typeOverloads/modules.ts b/packages/x-data-grid-pro/src/typeOverloads/modules.ts index 1d0fe7a306e60..82575431b8d28 100644 --- a/packages/x-data-grid-pro/src/typeOverloads/modules.ts +++ b/packages/x-data-grid-pro/src/typeOverloads/modules.ts @@ -3,6 +3,7 @@ import type { GridRowScrollEndParams, GridRowOrderChangeParams, GridFetchRowsParams, + GridGetRowsParams, } from '../models'; import type { GridRenderHeaderFilterProps } from '../components/headerFiltering/GridHeaderFilterCell'; import type { GridColumnPinningInternalCache } from '../hooks/features/columnPinning/gridColumnPinningInterface'; @@ -44,6 +45,10 @@ export interface GridEventLookupPro { * Fired when a new batch of rows is requested to be loaded. Called with a [[GridFetchRowsParams]] object. */ fetchRows: { params: GridFetchRowsParams }; + /** + * Fired when the grid needs to fetch a new batch of rows from the data source. Called with a [[GridGetRowsParams]] object. + */ + getRows: { params: GridGetRowsParams }; } export interface GridPipeProcessingLookupPro { From 3c820b301904b90213744282d223be0dda6651ad Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 18 Jul 2024 09:31:06 +0200 Subject: [PATCH 006/121] Data source cache takes start and end params into account --- packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts index 5645235abf019..daea7e9a5e5a6 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts @@ -16,6 +16,8 @@ function getKey(params: GridGetRowsParams) { params.sortModel, params.groupKeys, params.groupFields, + params.start, + params.end, ]); } From 857023deabdc803344811bf7e5ebf8b872c9d039 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 18 Jul 2024 09:32:28 +0200 Subject: [PATCH 007/121] Add server side version of lazy loading pre-processor and hook --- .../DataGridPro/useDataGridProComponent.tsx | 4 + .../features/lazyLoader/useGridLazyLoader.ts | 46 +----- .../src/hooks/features/lazyLoader/utils.ts | 46 ++++++ .../useGridDataSourceLazyLoader.ts | 144 ++++++++++++++++++ ...eGridDataSourceLazyLoaderPreProcessors.tsx | 53 +++++++ 5 files changed, 248 insertions(+), 45 deletions(-) create mode 100644 packages/x-data-grid-pro/src/hooks/features/lazyLoader/utils.ts create mode 100644 packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts create mode 100644 packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoaderPreProcessors.tsx diff --git a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx index 0240d83802fcc..9a92ba1137edc 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx @@ -86,6 +86,8 @@ import { useGridDataSource, dataSourceStateInitializer, } from '../hooks/features/dataSource/useGridDataSource'; +import { useGridDataSourceLazyLoaderPreProcessors } from '../hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoaderPreProcessors'; +import { useGridDataSourceLazyLoader } from '../hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader'; export const useDataGridProComponent = ( inputApiRef: React.MutableRefObject | undefined, @@ -101,6 +103,7 @@ export const useDataGridProComponent = ( useGridTreeDataPreProcessors(apiRef, props); useGridDataSourceTreeDataPreProcessors(apiRef, props); useGridLazyLoaderPreProcessors(apiRef, props); + useGridDataSourceLazyLoaderPreProcessors(apiRef, props); useGridRowPinningPreProcessors(apiRef); useGridDetailPanelPreProcessors(apiRef, props); // The column pinning `hydrateColumns` pre-processor must be after every other `hydrateColumns` pre-processors @@ -163,6 +166,7 @@ export const useDataGridProComponent = ( useGridScroll(apiRef, props); useGridInfiniteLoader(apiRef, props); useGridLazyLoader(apiRef, props); + useGridDataSourceLazyLoader(apiRef, props); useGridColumnMenu(apiRef); useGridCsvExport(apiRef, props); useGridPrintExport(apiRef, props); diff --git a/packages/x-data-grid-pro/src/hooks/features/lazyLoader/useGridLazyLoader.ts b/packages/x-data-grid-pro/src/hooks/features/lazyLoader/useGridLazyLoader.ts index 0ced8d346ae75..2041a6f6b4854 100644 --- a/packages/x-data-grid-pro/src/hooks/features/lazyLoader/useGridLazyLoader.ts +++ b/packages/x-data-grid-pro/src/hooks/features/lazyLoader/useGridLazyLoader.ts @@ -7,56 +7,12 @@ import { gridRenderContextSelector, useGridApiOptionHandler, GridEventListener, - GridRowEntry, } from '@mui/x-data-grid'; import { getVisibleRows } from '@mui/x-data-grid/internals'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; import { GridFetchRowsParams } from '../../../models/gridFetchRowsParams'; - -function findSkeletonRowsSection({ - apiRef, - visibleRows, - range, -}: { - apiRef: React.MutableRefObject; - visibleRows: GridRowEntry[]; - range: { firstRowIndex: number; lastRowIndex: number }; -}) { - let { firstRowIndex, lastRowIndex } = range; - const visibleRowsSection = visibleRows.slice(range.firstRowIndex, range.lastRowIndex); - let startIndex = 0; - let endIndex = visibleRowsSection.length - 1; - let isSkeletonSectionFound = false; - - while (!isSkeletonSectionFound && firstRowIndex < lastRowIndex) { - const isStartingWithASkeletonRow = - apiRef.current.getRowNode(visibleRowsSection[startIndex].id)?.type === 'skeletonRow'; - const isEndingWithASkeletonRow = - apiRef.current.getRowNode(visibleRowsSection[endIndex].id)?.type === 'skeletonRow'; - - if (isStartingWithASkeletonRow && isEndingWithASkeletonRow) { - isSkeletonSectionFound = true; - } - - if (!isStartingWithASkeletonRow) { - startIndex += 1; - firstRowIndex += 1; - } - - if (!isEndingWithASkeletonRow) { - endIndex -= 1; - lastRowIndex -= 1; - } - } - - return isSkeletonSectionFound - ? { - firstRowIndex, - lastRowIndex, - } - : undefined; -} +import { findSkeletonRowsSection } from './utils'; /** * @requires useGridRows (state) diff --git a/packages/x-data-grid-pro/src/hooks/features/lazyLoader/utils.ts b/packages/x-data-grid-pro/src/hooks/features/lazyLoader/utils.ts new file mode 100644 index 0000000000000..584d3696d17ae --- /dev/null +++ b/packages/x-data-grid-pro/src/hooks/features/lazyLoader/utils.ts @@ -0,0 +1,46 @@ +import { GridRowEntry } from '@mui/x-data-grid'; +import { GridPrivateApiPro } from '../../../models/gridApiPro'; + +export const findSkeletonRowsSection = ({ + apiRef, + visibleRows, + range, +}: { + apiRef: React.MutableRefObject; + visibleRows: GridRowEntry[]; + range: { firstRowIndex: number; lastRowIndex: number }; +}) => { + let { firstRowIndex, lastRowIndex } = range; + const visibleRowsSection = visibleRows.slice(range.firstRowIndex, range.lastRowIndex); + let startIndex = 0; + let endIndex = visibleRowsSection.length - 1; + let isSkeletonSectionFound = false; + + while (!isSkeletonSectionFound && firstRowIndex < lastRowIndex) { + const isStartingWithASkeletonRow = + apiRef.current.getRowNode(visibleRowsSection[startIndex].id)?.type === 'skeletonRow'; + const isEndingWithASkeletonRow = + apiRef.current.getRowNode(visibleRowsSection[endIndex].id)?.type === 'skeletonRow'; + + if (isStartingWithASkeletonRow && isEndingWithASkeletonRow) { + isSkeletonSectionFound = true; + } + + if (!isStartingWithASkeletonRow) { + startIndex += 1; + firstRowIndex += 1; + } + + if (!isEndingWithASkeletonRow) { + endIndex -= 1; + lastRowIndex -= 1; + } + } + + return isSkeletonSectionFound + ? { + firstRowIndex, + lastRowIndex, + } + : undefined; +}; 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 new file mode 100644 index 0000000000000..de86ec3642a69 --- /dev/null +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts @@ -0,0 +1,144 @@ +import * as React from 'react'; +import { + useGridApiEventHandler, + useGridSelector, + gridSortModelSelector, + gridFilterModelSelector, + GridEventListener, +} from '@mui/x-data-grid'; +import { + getVisibleRows, + GridGetRowsParams, + gridRenderContextSelector, +} from '@mui/x-data-grid/internals'; +import { GridPrivateApiPro } from '../../../models/gridApiPro'; +import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; +import { findSkeletonRowsSection } from '../lazyLoader/utils'; + +const INTERVAL_CACHE_INITIAL_STATE = { + firstRowToRender: 0, + lastRowToRender: 0, +}; + +/** + * @requires useGridRows (state) + * @requires useGridPagination (state) + * @requires useGridDimensions (method) - can be after + * @requires useGridScroll (method + */ +export const useGridDataSourceLazyLoader = ( + privateApiRef: React.MutableRefObject, + props: Pick, +): void => { + const sortModel = useGridSelector(privateApiRef, gridSortModelSelector); + const filterModel = useGridSelector(privateApiRef, gridFilterModelSelector); + const renderedRowsIntervalCache = React.useRef(INTERVAL_CACHE_INITIAL_STATE); + const isDisabled = props.unstable_dataSource?.lazyLoaded !== true; + + const handleRenderedRowsIntervalChange = React.useCallback< + GridEventListener<'renderedRowsIntervalChange'> + >( + (params) => { + if (isDisabled) { + return; + } + + const fetchRowsParams: GridGetRowsParams = { + start: params.firstRowIndex, + end: params.lastRowIndex, + sortModel, + filterModel, + }; + + if ( + renderedRowsIntervalCache.current.firstRowToRender === params.firstRowIndex && + renderedRowsIntervalCache.current.lastRowToRender === params.lastRowIndex + ) { + return; + } + + renderedRowsIntervalCache.current = { + firstRowToRender: params.firstRowIndex, + lastRowToRender: params.lastRowIndex, + }; + + if (sortModel.length === 0 && filterModel.items.length === 0) { + const currentVisibleRows = getVisibleRows(privateApiRef, { + pagination: props.pagination, + paginationMode: props.paginationMode, + }); + const skeletonRowsSection = findSkeletonRowsSection({ + apiRef: privateApiRef, + visibleRows: currentVisibleRows.rows, + range: { + firstRowIndex: params.firstRowIndex, + lastRowIndex: params.lastRowIndex, + }, + }); + + if (!skeletonRowsSection) { + return; + } + + fetchRowsParams.start = skeletonRowsSection.firstRowIndex; + fetchRowsParams.end = skeletonRowsSection.lastRowIndex; + } + + privateApiRef.current.publishEvent('getRows', fetchRowsParams); + }, + [privateApiRef, isDisabled, props.pagination, props.paginationMode, sortModel, filterModel], + ); + + const handleGridSortModelChange = React.useCallback>( + (newSortModel) => { + if (isDisabled) { + return; + } + + privateApiRef.current.setRows([]); + renderedRowsIntervalCache.current = INTERVAL_CACHE_INITIAL_STATE; + + const renderContext = gridRenderContextSelector(privateApiRef); + const fetchRowsParams: GridGetRowsParams = { + start: renderContext.firstRowIndex, + end: renderContext.lastRowIndex, + sortModel: newSortModel, + filterModel, + }; + + privateApiRef.current.publishEvent('getRows', fetchRowsParams); + }, + [privateApiRef, isDisabled, filterModel], + ); + + const handleGridFilterModelChange = React.useCallback>( + (newFilterModel) => { + if (isDisabled) { + return; + } + + privateApiRef.current.setRows([]); + renderedRowsIntervalCache.current = INTERVAL_CACHE_INITIAL_STATE; + + const renderContext = gridRenderContextSelector(privateApiRef); + const fetchRowsParams: GridGetRowsParams = { + start: renderContext.firstRowIndex, + end: renderContext.lastRowIndex, + sortModel, + filterModel: newFilterModel, + }; + + privateApiRef.current.publishEvent('getRows', fetchRowsParams); + }, + [privateApiRef, isDisabled, sortModel], + ); + + useGridApiEventHandler( + privateApiRef, + 'renderedRowsIntervalChange', + handleRenderedRowsIntervalChange, + ); + // TODO: if sorting/filtering happens firther away from the top, sometimes one skeleton row is left + useGridApiEventHandler(privateApiRef, 'sortModelChange', handleGridSortModelChange); + useGridApiEventHandler(privateApiRef, 'filterModelChange', handleGridFilterModelChange); +}; diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoaderPreProcessors.tsx b/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoaderPreProcessors.tsx new file mode 100644 index 0000000000000..3c39231910ddf --- /dev/null +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoaderPreProcessors.tsx @@ -0,0 +1,53 @@ +import * as React from 'react'; +import { GridPipeProcessor, useGridRegisterPipeProcessor } from '@mui/x-data-grid/internals'; +import { GRID_ROOT_GROUP_ID, GridGroupNode, GridSkeletonRowNode } from '@mui/x-data-grid'; +import { GridPrivateApiPro } from '../../../models/gridApiPro'; +import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; + +export const GRID_SKELETON_ROW_ROOT_ID = 'auto-generated-skeleton-row-root'; + +const getSkeletonRowId = (index: number) => `${GRID_SKELETON_ROW_ROOT_ID}-${index}`; + +export const useGridDataSourceLazyLoaderPreProcessors = ( + privateApiRef: React.MutableRefObject, + props: Pick, +) => { + const addSkeletonRows = React.useCallback>( + (groupingParams) => { + const rootGroup = groupingParams.tree[GRID_ROOT_GROUP_ID] as GridGroupNode; + + if (props.unstable_dataSource?.lazyLoaded !== true) { + return groupingParams; + } + + const rowCount = 100; // TODO: get the initial count from the viewport and later from the server + const tree = { ...groupingParams.tree }; + const rootGroupChildren = [...rootGroup.children]; + + for (let i = 0; i < rowCount - rootGroup.children.length; i += 1) { + const skeletonId = getSkeletonRowId(i); + + rootGroupChildren.push(skeletonId); + + const skeletonRowNode: GridSkeletonRowNode = { + type: 'skeletonRow', + id: skeletonId, + parent: GRID_ROOT_GROUP_ID, + depth: 0, + }; + + tree[skeletonId] = skeletonRowNode; + } + + tree[GRID_ROOT_GROUP_ID] = { ...rootGroup, children: rootGroupChildren }; + + return { + ...groupingParams, + tree, + }; + }, + [props.unstable_dataSource?.lazyLoaded], + ); + + useGridRegisterPipeProcessor(privateApiRef, 'hydrateRows', addSkeletonRows); +}; From 24d6e65461e283890dfae000f491cb8907071aff Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 18 Jul 2024 09:32:52 +0200 Subject: [PATCH 008/121] Prvent both client and server side lazy loading setup --- packages/x-data-grid-pro/src/internals/propValidation.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/x-data-grid-pro/src/internals/propValidation.ts b/packages/x-data-grid-pro/src/internals/propValidation.ts index 13f138529d47b..efd2c26029ad7 100644 --- a/packages/x-data-grid-pro/src/internals/propValidation.ts +++ b/packages/x-data-grid-pro/src/internals/propValidation.ts @@ -31,4 +31,10 @@ export const propValidatorsDataGridPro: PropValidator isNumber(props.rowCount) && 'MUI X: Usage of the `rowCount` prop with client side pagination (`paginationMode="client"`) has no effect. `rowCount` is only meant to be used with `paginationMode="server"`.') || undefined, + (props) => + (props.signature !== GridSignature.DataGrid && + props.rowsLoadingMode === 'server' && + props.unstable_dataSource?.lazyLoaded === true && + 'MUI X: Usage of the client side lazy loading (`rowsLoadingMode="server"`) cannot be used together with server side lazy loading `unstable_dataSource="{ ..., lazyLoaded: true}"`.') || + undefined, ]; From 94e71ee7dab45b514a337f6b79e5405d9db36dd3 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 18 Jul 2024 09:33:30 +0200 Subject: [PATCH 009/121] Data source hook supports new way of loading data and replaces lazy loaded rows --- .../features/dataSource/useGridDataSource.ts | 44 ++++++++++++++++--- .../src/hooks/features/rows/useGridRows.ts | 4 +- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index 92b19b032d030..141e145e8ee81 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -8,7 +8,11 @@ import { useGridSelector, GridRowId, } from '@mui/x-data-grid'; -import { gridRowGroupsToFetchSelector, GridStateInitializer } from '@mui/x-data-grid/internals'; +import { + GridGetRowsParams, + gridRowGroupsToFetchSelector, + GridStateInitializer, +} from '@mui/x-data-grid/internals'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; import { gridGetRowsParamsSelector, gridDataSourceErrorsSelector } from './gridDataSourceSelector'; @@ -60,6 +64,9 @@ export const useGridDataSource = ( ).current; const groupsToAutoFetch = useGridSelector(apiRef, gridRowGroupsToFetchSelector); const scheduledGroups = React.useRef(0); + const rowFetchSlice = React.useRef( + props.unstable_dataSource?.lazyLoaded ? { start: 0, end: 10 } : {}, // TODO: predict the initial `end` from the viewport + ); const onError = props.unstable_onDataSourceError; const [cache, setCache] = React.useState(() => @@ -88,14 +95,19 @@ export const useGridDataSource = ( const fetchParams = { ...gridGetRowsParamsSelector(apiRef), ...apiRef.current.unstable_applyPipeProcessors('getRowsParams', {}), + ...rowFetchSlice.current, }; const cachedData = apiRef.current.unstable_dataSource.cache.get(fetchParams); if (cachedData !== undefined) { const rows = cachedData.rows; - apiRef.current.setRows(rows); - if (cachedData.rowCount !== undefined) { + if (props.unstable_dataSource?.lazyLoaded === true) { + apiRef.current.unstable_replaceRows(fetchParams.start, rows); + } else { + apiRef.current.setRows(rows); + } + if (cachedData.rowCount) { apiRef.current.setRowCount(cachedData.rowCount); } return; @@ -112,7 +124,11 @@ export const useGridDataSource = ( if (getRowsResponse.rowCount !== undefined) { apiRef.current.setRowCount(getRowsResponse.rowCount); } - apiRef.current.setRows(getRowsResponse.rows); + if (props.unstable_dataSource?.lazyLoaded === true) { + apiRef.current.unstable_replaceRows(fetchParams.start, getRowsResponse.rows); + } else { + apiRef.current.setRows(getRowsResponse.rows); + } apiRef.current.setLoading(false); } catch (error) { apiRef.current.setRows([]); @@ -120,7 +136,24 @@ export const useGridDataSource = ( onError?.(error as Error, fetchParams); } }, - [nestedDataManager, apiRef, props.unstable_dataSource?.getRows, onError], + [ + nestedDataManager, + apiRef, + props.unstable_dataSource?.getRows, + props.unstable_dataSource?.lazyLoaded, + onError, + rowFetchSlice, + ], + ); + + const fetchRowBatch = React.useCallback( + (fetchParams: GridGetRowsParams) => { + if (props.unstable_dataSource?.lazyLoaded && fetchParams.start && fetchParams.end) { + rowFetchSlice.current = { start: Number(fetchParams.start), end: fetchParams.end }; + } + return fetchRows(); + }, + [props.unstable_dataSource?.lazyLoaded, fetchRows], ); const fetchRowChildren = React.useCallback( @@ -275,6 +308,7 @@ export const useGridDataSource = ( 'paginationModelChange', runIfServerMode(props.paginationMode, fetchRows), ); + useGridApiEventHandler(apiRef, 'getRows', fetchRowBatch); const isFirstRender = React.useRef(true); React.useEffect(() => { diff --git a/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts b/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts index bd89c281f7292..7dda6304335c1 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts @@ -464,6 +464,8 @@ export const useGridRows = ( ...state, rows: { ...state.rows, + loading: props.loading, + totalRowCount: Math.max(props.rowCount || 0, rootGroupChildren.length), dataRowIdToModelLookup, dataRowIdToIdLookup, dataRowIds, @@ -472,7 +474,7 @@ export const useGridRows = ( })); apiRef.current.publishEvent('rowsSet'); }, - [apiRef, props.signature, props.getRowId], + [apiRef, props.signature, props.getRowId, props.loading, props.rowCount], ); const rowApi: GridRowApi = { From cd01a63b821a21cd9262e0d0ebefaff318b35333 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Mon, 22 Jul 2024 11:22:38 +0200 Subject: [PATCH 010/121] Lazy loading flag moved to the root props instead of it being a part of the data source model --- .../pages/x/api/data-grid/data-grid-premium.json | 1 + docs/pages/x/api/data-grid/data-grid-pro.json | 1 + .../data-grid-premium/data-grid-premium.json | 3 +++ .../data-grid/data-grid-pro/data-grid-pro.json | 3 +++ .../src/DataGridPremium/DataGridPremium.tsx | 8 +++++++- .../src/DataGridPro/DataGridPro.tsx | 8 +++++++- .../features/dataSource/useGridDataSource.ts | 16 ++++++++++------ .../useGridDataSourceLazyLoader.ts | 7 +++++-- .../useGridDataSourceLazyLoaderPreProcessors.tsx | 7 ++++--- .../src/internals/propValidation.ts | 4 ++-- .../src/models/dataGridProProps.ts | 7 +++++++ .../x-data-grid/src/models/gridDataSource.ts | 5 ----- 12 files changed, 50 insertions(+), 20 deletions(-) diff --git a/docs/pages/x/api/data-grid/data-grid-premium.json b/docs/pages/x/api/data-grid/data-grid-premium.json index 5663a6235bb17..afbb2c23605f1 100644 --- a/docs/pages/x/api/data-grid/data-grid-premium.json +++ b/docs/pages/x/api/data-grid/data-grid-premium.json @@ -211,6 +211,7 @@ }, "keepColumnPositionIfDraggedOutside": { "type": { "name": "bool" }, "default": "false" }, "keepNonExistentRowsSelected": { "type": { "name": "bool" }, "default": "false" }, + "lazyLoading": { "type": { "name": "bool" }, "default": "false" }, "loading": { "type": { "name": "bool" }, "default": "false" }, "localeText": { "type": { "name": "object" } }, "logger": { diff --git a/docs/pages/x/api/data-grid/data-grid-pro.json b/docs/pages/x/api/data-grid/data-grid-pro.json index bc21892c67b09..fdd7e14d72550 100644 --- a/docs/pages/x/api/data-grid/data-grid-pro.json +++ b/docs/pages/x/api/data-grid/data-grid-pro.json @@ -188,6 +188,7 @@ }, "keepColumnPositionIfDraggedOutside": { "type": { "name": "bool" }, "default": "false" }, "keepNonExistentRowsSelected": { "type": { "name": "bool" }, "default": "false" }, + "lazyLoading": { "type": { "name": "bool" }, "default": "false" }, "loading": { "type": { "name": "bool" }, "default": "false" }, "localeText": { "type": { "name": "object" } }, "logger": { diff --git a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json index 31694d362767c..c76ad08d5d6fb 100644 --- a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json +++ b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json @@ -235,6 +235,9 @@ "keepNonExistentRowsSelected": { "description": "If true, the selection model will retain selected rows that do not exist. Useful when using server side pagination and row selections need to be retained when changing pages." }, + "lazyLoading": { + "description": "Used together with unstable_dataSource to enable lazy loading. If enabled, the grid will stop adding paginationModel to the data requests (getRows) and start sending start and end values depending on the scroll position. A new request will be made whenever the user scrolls to the area that has skeleton rows." + }, "loading": { "description": "If true, a loading overlay is displayed." }, "localeText": { "description": "Set the locale text of the Data Grid. You can find all the translation keys supported in the source in the GitHub repository." diff --git a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json index dbac4fd0c9af5..e5e02f0cb3a10 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json +++ b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json @@ -216,6 +216,9 @@ "keepNonExistentRowsSelected": { "description": "If true, the selection model will retain selected rows that do not exist. Useful when using server side pagination and row selections need to be retained when changing pages." }, + "lazyLoading": { + "description": "Used together with unstable_dataSource to enable lazy loading. If enabled, the grid will stop adding paginationModel to the data requests (getRows) and start sending start and end values depending on the scroll position. A new request will be made whenever the user scrolls to the area that has skeleton rows." + }, "loading": { "description": "If true, a loading overlay is displayed." }, "localeText": { "description": "Set the locale text of the Data Grid. You can find all the translation keys supported in the source in the GitHub repository." diff --git a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx index dc1d6de414473..d68cbac0d2564 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx @@ -519,6 +519,13 @@ DataGridPremiumRaw.propTypes = { * @default false */ keepNonExistentRowsSelected: PropTypes.bool, + /** + * Used together with `unstable_dataSource` to enable lazy loading. + * If enabled, the grid will stop adding `paginationModel` to the data requests (`getRows`) and start sending `start` and `end` values depending on the scroll position. + * A new request will be made whenever the user scrolls to the area that has skeleton rows. + * @default false + */ + lazyLoading: PropTypes.bool, /** * If `true`, a loading overlay is displayed. * @default false @@ -1090,7 +1097,6 @@ DataGridPremiumRaw.propTypes = { getChildrenCount: PropTypes.func, getGroupKey: PropTypes.func, getRows: PropTypes.func.isRequired, - lazyLoaded: PropTypes.bool, updateRow: PropTypes.func, }), unstable_dataSourceCache: PropTypes.shape({ diff --git a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx index c2f17091f4e76..37186a3b16888 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx @@ -475,6 +475,13 @@ DataGridProRaw.propTypes = { * @default false */ keepNonExistentRowsSelected: PropTypes.bool, + /** + * Used together with `unstable_dataSource` to enable lazy loading. + * If enabled, the grid will stop adding `paginationModel` to the data requests (`getRows`) and start sending `start` and `end` values depending on the scroll position. + * A new request will be made whenever the user scrolls to the area that has skeleton rows. + * @default false + */ + lazyLoading: PropTypes.bool, /** * If `true`, a loading overlay is displayed. * @default false @@ -990,7 +997,6 @@ DataGridProRaw.propTypes = { getChildrenCount: PropTypes.func, getGroupKey: PropTypes.func, getRows: PropTypes.func.isRequired, - lazyLoaded: PropTypes.bool, updateRow: PropTypes.func, }), unstable_dataSourceCache: PropTypes.shape({ diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index 141e145e8ee81..307959f2e5940 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -57,6 +57,7 @@ export const useGridDataSource = ( | 'filterMode' | 'paginationMode' | 'treeData' + | 'lazyLoading' >, ) => { const nestedDataManager = useLazyRef( @@ -64,9 +65,12 @@ export const useGridDataSource = ( ).current; const groupsToAutoFetch = useGridSelector(apiRef, gridRowGroupsToFetchSelector); const scheduledGroups = React.useRef(0); + + const isLazyLoaded = !!props.unstable_dataSource && props.lazyLoading; const rowFetchSlice = React.useRef( - props.unstable_dataSource?.lazyLoaded ? { start: 0, end: 10 } : {}, // TODO: predict the initial `end` from the viewport + isLazyLoaded ? { start: 0, end: 10 } : {}, // TODO: predict the initial `end` from the viewport ); + const onError = props.unstable_onDataSourceError; const [cache, setCache] = React.useState(() => @@ -102,7 +106,7 @@ export const useGridDataSource = ( if (cachedData !== undefined) { const rows = cachedData.rows; - if (props.unstable_dataSource?.lazyLoaded === true) { + if (isLazyLoaded) { apiRef.current.unstable_replaceRows(fetchParams.start, rows); } else { apiRef.current.setRows(rows); @@ -124,7 +128,7 @@ export const useGridDataSource = ( if (getRowsResponse.rowCount !== undefined) { apiRef.current.setRowCount(getRowsResponse.rowCount); } - if (props.unstable_dataSource?.lazyLoaded === true) { + if (isLazyLoaded) { apiRef.current.unstable_replaceRows(fetchParams.start, getRowsResponse.rows); } else { apiRef.current.setRows(getRowsResponse.rows); @@ -140,7 +144,7 @@ export const useGridDataSource = ( nestedDataManager, apiRef, props.unstable_dataSource?.getRows, - props.unstable_dataSource?.lazyLoaded, + isLazyLoaded, onError, rowFetchSlice, ], @@ -148,12 +152,12 @@ export const useGridDataSource = ( const fetchRowBatch = React.useCallback( (fetchParams: GridGetRowsParams) => { - if (props.unstable_dataSource?.lazyLoaded && fetchParams.start && fetchParams.end) { + if (isLazyLoaded && fetchParams.start && fetchParams.end) { rowFetchSlice.current = { start: Number(fetchParams.start), end: fetchParams.end }; } return fetchRows(); }, - [props.unstable_dataSource?.lazyLoaded, fetchRows], + [isLazyLoaded, fetchRows], ); const fetchRowChildren = React.useCallback( 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 de86ec3642a69..338b535e06f9c 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 @@ -28,12 +28,15 @@ const INTERVAL_CACHE_INITIAL_STATE = { */ export const useGridDataSourceLazyLoader = ( privateApiRef: React.MutableRefObject, - props: Pick, + props: Pick< + DataGridProProcessedProps, + 'pagination' | 'paginationMode' | 'unstable_dataSource' | 'lazyLoading' + >, ): void => { const sortModel = useGridSelector(privateApiRef, gridSortModelSelector); const filterModel = useGridSelector(privateApiRef, gridFilterModelSelector); const renderedRowsIntervalCache = React.useRef(INTERVAL_CACHE_INITIAL_STATE); - const isDisabled = props.unstable_dataSource?.lazyLoaded !== true; + const isDisabled = !props.unstable_dataSource || props.lazyLoading !== true; const handleRenderedRowsIntervalChange = React.useCallback< GridEventListener<'renderedRowsIntervalChange'> diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoaderPreProcessors.tsx b/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoaderPreProcessors.tsx index 3c39231910ddf..305f46a464629 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoaderPreProcessors.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoaderPreProcessors.tsx @@ -10,13 +10,14 @@ const getSkeletonRowId = (index: number) => `${GRID_SKELETON_ROW_ROOT_ID}-${inde export const useGridDataSourceLazyLoaderPreProcessors = ( privateApiRef: React.MutableRefObject, - props: Pick, + props: Pick, ) => { const addSkeletonRows = React.useCallback>( (groupingParams) => { const rootGroup = groupingParams.tree[GRID_ROOT_GROUP_ID] as GridGroupNode; + const isDisabled = !props.unstable_dataSource || props.lazyLoading !== true; - if (props.unstable_dataSource?.lazyLoaded !== true) { + if (isDisabled) { return groupingParams; } @@ -46,7 +47,7 @@ export const useGridDataSourceLazyLoaderPreProcessors = ( tree, }; }, - [props.unstable_dataSource?.lazyLoaded], + [props.unstable_dataSource, props.lazyLoading], ); useGridRegisterPipeProcessor(privateApiRef, 'hydrateRows', addSkeletonRows); diff --git a/packages/x-data-grid-pro/src/internals/propValidation.ts b/packages/x-data-grid-pro/src/internals/propValidation.ts index efd2c26029ad7..e4cf9b6572b42 100644 --- a/packages/x-data-grid-pro/src/internals/propValidation.ts +++ b/packages/x-data-grid-pro/src/internals/propValidation.ts @@ -34,7 +34,7 @@ export const propValidatorsDataGridPro: PropValidator (props) => (props.signature !== GridSignature.DataGrid && props.rowsLoadingMode === 'server' && - props.unstable_dataSource?.lazyLoaded === true && - 'MUI X: Usage of the client side lazy loading (`rowsLoadingMode="server"`) cannot be used together with server side lazy loading `unstable_dataSource="{ ..., lazyLoaded: true}"`.') || + props.lazyLoading && + 'MUI X: Usage of the client side lazy loading (`rowsLoadingMode="server"`) cannot be used together with server side lazy loading `lazyLoading="true"`.') || undefined, ]; diff --git a/packages/x-data-grid-pro/src/models/dataGridProProps.ts b/packages/x-data-grid-pro/src/models/dataGridProProps.ts index d2e2c490600d3..4503fee8bfb3f 100644 --- a/packages/x-data-grid-pro/src/models/dataGridProProps.ts +++ b/packages/x-data-grid-pro/src/models/dataGridProProps.ts @@ -143,6 +143,13 @@ export interface DataGridProPropsWithDefaultValue number; - /** - * If enabled, the grid will send `start` and `end` instead of `paginationModel` to `getRows` and a new request will be made - * whenever the user scrolls to the area that has skeleton rows - */ - lazyLoaded?: boolean; } export interface GridDataSourceCache { From 446b47b82220130dd078ed6127ace3da6ea35a73 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Mon, 22 Jul 2024 11:29:06 +0200 Subject: [PATCH 011/121] Update docs --- docs/data/data-grid/server-side-data/ServerSideLazyLoading.js | 3 +-- docs/data/data-grid/server-side-data/ServerSideLazyLoading.tsx | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoading.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoading.js index 0221c85e6e74a..28bde31782ebb 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoading.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoading.js @@ -26,14 +26,13 @@ function ServerSideLazyLoading() { rowCount: getRowsResponse.rowCount, }; }, - lazyLoaded: true, }), [fetchRows], ); return (
- +
); } diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoading.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoading.tsx index 82ea65ae6cfac..82d4553ba3a20 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoading.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoading.tsx @@ -30,14 +30,13 @@ function ServerSideLazyLoading() { rowCount: getRowsResponse.rowCount, }; }, - lazyLoaded: true, }), [fetchRows], ); return (
- +
); } From 3be08cf95a88efa2dde84ffb282da75ddb8f3c48 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Fri, 2 Aug 2024 19:38:14 +0200 Subject: [PATCH 012/121] Check for skeleton rows with the sorting and filtering enabled --- .../useGridDataSourceLazyLoader.ts | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) 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 338b535e06f9c..4cdd9ef8e5522 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 @@ -65,28 +65,27 @@ export const useGridDataSourceLazyLoader = ( lastRowToRender: params.lastRowIndex, }; - if (sortModel.length === 0 && filterModel.items.length === 0) { - const currentVisibleRows = getVisibleRows(privateApiRef, { - pagination: props.pagination, - paginationMode: props.paginationMode, - }); - const skeletonRowsSection = findSkeletonRowsSection({ - apiRef: privateApiRef, - visibleRows: currentVisibleRows.rows, - range: { - firstRowIndex: params.firstRowIndex, - lastRowIndex: params.lastRowIndex, - }, - }); - - if (!skeletonRowsSection) { - return; - } - - fetchRowsParams.start = skeletonRowsSection.firstRowIndex; - fetchRowsParams.end = skeletonRowsSection.lastRowIndex; + const currentVisibleRows = getVisibleRows(privateApiRef, { + pagination: props.pagination, + paginationMode: props.paginationMode, + }); + + const skeletonRowsSection = findSkeletonRowsSection({ + apiRef: privateApiRef, + visibleRows: currentVisibleRows.rows, + range: { + firstRowIndex: params.firstRowIndex, + lastRowIndex: params.lastRowIndex, + }, + }); + + if (!skeletonRowsSection) { + return; } + fetchRowsParams.start = skeletonRowsSection.firstRowIndex; + fetchRowsParams.end = skeletonRowsSection.lastRowIndex; + privateApiRef.current.publishEvent('getRows', fetchRowsParams); }, [privateApiRef, isDisabled, props.pagination, props.paginationMode, sortModel, filterModel], @@ -141,7 +140,6 @@ export const useGridDataSourceLazyLoader = ( 'renderedRowsIntervalChange', handleRenderedRowsIntervalChange, ); - // TODO: if sorting/filtering happens firther away from the top, sometimes one skeleton row is left useGridApiEventHandler(privateApiRef, 'sortModelChange', handleGridSortModelChange); useGridApiEventHandler(privateApiRef, 'filterModelChange', handleGridFilterModelChange); }; From 5ed0151d35f6817a7c815952545e763b098329d2 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Fri, 2 Aug 2024 19:39:05 +0200 Subject: [PATCH 013/121] Loading overlay for the initial load only --- .../src/hooks/features/dataSource/useGridDataSource.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index 307959f2e5940..eae4cd3eb79d4 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -117,8 +117,10 @@ export const useGridDataSource = ( return; } + // with lazy loading, only the initial load should show the loading overlay + const useLoadingIndicator = !isLazyLoaded || apiRef.current.getRowsCount() === 0; const isLoading = gridRowsLoadingSelector(apiRef); - if (!isLoading) { + if (!isLoading && useLoadingIndicator) { apiRef.current.setLoading(true); } @@ -152,7 +154,7 @@ export const useGridDataSource = ( const fetchRowBatch = React.useCallback( (fetchParams: GridGetRowsParams) => { - if (isLazyLoaded && fetchParams.start && fetchParams.end) { + if (isLazyLoaded) { rowFetchSlice.current = { start: Number(fetchParams.start), end: fetchParams.end }; } return fetchRows(); From 8dfc7fba82b40d6dba787d833ce4435cd3077987 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 7 Aug 2024 15:12:09 +0200 Subject: [PATCH 014/121] Skeleton rows should be added after data load to be able to know the total row count. Disable row fetch from the data source hook if lazy loading is enabled. Add new events to support the new flow --- .../DataGridPro/useDataGridProComponent.tsx | 2 - .../features/dataSource/useGridDataSource.ts | 39 +++++++------ .../src/hooks/features/dataSource/utils.ts | 6 +- .../useGridDataSourceLazyLoader.ts | 56 +++++++++++++++++-- ...eGridDataSourceLazyLoaderPreProcessors.tsx | 54 ------------------ .../src/models/events/gridEventLookup.ts | 8 +++ 6 files changed, 85 insertions(+), 80 deletions(-) delete mode 100644 packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoaderPreProcessors.tsx diff --git a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx index 9a92ba1137edc..f997b0c8d7f33 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx @@ -86,7 +86,6 @@ import { useGridDataSource, dataSourceStateInitializer, } from '../hooks/features/dataSource/useGridDataSource'; -import { useGridDataSourceLazyLoaderPreProcessors } from '../hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoaderPreProcessors'; import { useGridDataSourceLazyLoader } from '../hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader'; export const useDataGridProComponent = ( @@ -103,7 +102,6 @@ export const useDataGridProComponent = ( useGridTreeDataPreProcessors(apiRef, props); useGridDataSourceTreeDataPreProcessors(apiRef, props); useGridLazyLoaderPreProcessors(apiRef, props); - useGridDataSourceLazyLoaderPreProcessors(apiRef, props); useGridRowPinningPreProcessors(apiRef); useGridDetailPanelPreProcessors(apiRef, props); // The column pinning `hydrateColumns` pre-processor must be after every other `hydrateColumns` pre-processors diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index eae4cd3eb79d4..0d883e3857edf 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -17,7 +17,7 @@ import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; import { gridGetRowsParamsSelector, gridDataSourceErrorsSelector } from './gridDataSourceSelector'; import { GridDataSourceApi, GridDataSourceApiBase, GridDataSourcePrivateApi } from './interfaces'; -import { runIfServerMode, NestedDataManager, RequestStatus } from './utils'; +import { NestedDataManager, RequestStatus, runIf } from './utils'; import { GridDataSourceCache } from '../../../models'; import { GridDataSourceCacheDefault } from './cache'; @@ -67,9 +67,7 @@ export const useGridDataSource = ( const scheduledGroups = React.useRef(0); const isLazyLoaded = !!props.unstable_dataSource && props.lazyLoading; - const rowFetchSlice = React.useRef( - isLazyLoaded ? { start: 0, end: 10 } : {}, // TODO: predict the initial `end` from the viewport - ); + const rowFetchSlice = React.useRef({}); const onError = props.unstable_onDataSourceError; @@ -106,14 +104,14 @@ export const useGridDataSource = ( if (cachedData !== undefined) { const rows = cachedData.rows; + if (cachedData.rowCount !== undefined) { + apiRef.current.setRowCount(cachedData.rowCount); + } if (isLazyLoaded) { apiRef.current.unstable_replaceRows(fetchParams.start, rows); } else { apiRef.current.setRows(rows); } - if (cachedData.rowCount) { - apiRef.current.setRowCount(cachedData.rowCount); - } return; } @@ -136,6 +134,7 @@ export const useGridDataSource = ( apiRef.current.setRows(getRowsResponse.rows); } apiRef.current.setLoading(false); + apiRef.current.publishEvent('rowResponseLoaded'); } catch (error) { apiRef.current.setRows([]); apiRef.current.setLoading(false); @@ -154,12 +153,10 @@ export const useGridDataSource = ( const fetchRowBatch = React.useCallback( (fetchParams: GridGetRowsParams) => { - if (isLazyLoaded) { - rowFetchSlice.current = { start: Number(fetchParams.start), end: fetchParams.end }; - } + rowFetchSlice.current = { start: Number(fetchParams.start), end: fetchParams.end }; return fetchRows(); }, - [isLazyLoaded, fetchRows], + [fetchRows], ); const fetchRowChildren = React.useCallback( @@ -193,7 +190,7 @@ export const useGridDataSource = ( const rows = cachedData.rows; nestedDataManager.setRequestSettled(id); apiRef.current.updateServerRows(rows, rowNode.path); - if (cachedData.rowCount) { + if (cachedData.rowCount !== undefined) { apiRef.current.setRowCount(cachedData.rowCount); } apiRef.current.setRowChildrenExpansion(id, true); @@ -219,7 +216,7 @@ export const useGridDataSource = ( } nestedDataManager.setRequestSettled(id); apiRef.current.unstable_dataSource.cache.set(fetchParams, getRowsResponse); - if (getRowsResponse.rowCount) { + if (getRowsResponse.rowCount !== undefined) { apiRef.current.setRowCount(getRowsResponse.rowCount); } apiRef.current.updateServerRows(getRowsResponse.rows, rowNode.path); @@ -307,14 +304,22 @@ export const useGridDataSource = ( useGridApiMethod(apiRef, dataSourceApi, 'public'); useGridApiMethod(apiRef, dataSourcePrivateApi, 'private'); - useGridApiEventHandler(apiRef, 'sortModelChange', runIfServerMode(props.sortingMode, fetchRows)); - useGridApiEventHandler(apiRef, 'filterModelChange', runIfServerMode(props.filterMode, fetchRows)); + useGridApiEventHandler( + apiRef, + 'sortModelChange', + runIf(props.sortingMode === 'server' && !isLazyLoaded, fetchRows), + ); + useGridApiEventHandler( + apiRef, + 'filterModelChange', + runIf(props.filterMode === 'server' && !isLazyLoaded, fetchRows), + ); useGridApiEventHandler( apiRef, 'paginationModelChange', - runIfServerMode(props.paginationMode, fetchRows), + runIf(props.paginationMode === 'server' && !isLazyLoaded, fetchRows), ); - useGridApiEventHandler(apiRef, 'getRows', fetchRowBatch); + useGridApiEventHandler(apiRef, 'getRows', runIf(isLazyLoaded, fetchRowBatch)); const isFirstRender = React.useRef(true); React.useEffect(() => { diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts index dafc6d9783f21..978f7828bd155 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts @@ -3,9 +3,9 @@ import { GridPrivateApiPro } from '../../../models/gridApiPro'; const MAX_CONCURRENT_REQUESTS = Infinity; -export const runIfServerMode = (modeProp: 'server' | 'client', fn: Function) => () => { - if (modeProp === 'server') { - fn(); +export const runIf = (condition: boolean, fn: Function) => (params: unknown) => { + if (condition) { + fn(params); } }; 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 4cdd9ef8e5522..f1f9d05ff614d 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 @@ -5,6 +5,9 @@ import { gridSortModelSelector, gridFilterModelSelector, GridEventListener, + GRID_ROOT_GROUP_ID, + GridGroupNode, + GridSkeletonRowNode, } from '@mui/x-data-grid'; import { getVisibleRows, @@ -14,12 +17,15 @@ import { import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; import { findSkeletonRowsSection } from '../lazyLoader/utils'; +import { GRID_SKELETON_ROW_ROOT_ID } from '../lazyLoader/useGridLazyLoaderPreProcessors'; const INTERVAL_CACHE_INITIAL_STATE = { firstRowToRender: 0, lastRowToRender: 0, }; +const getSkeletonRowId = (index: number) => `${GRID_SKELETON_ROW_ROOT_ID}-${index}`; + /** * @requires useGridRows (state) * @requires useGridPagination (state) @@ -38,6 +44,48 @@ export const useGridDataSourceLazyLoader = ( const renderedRowsIntervalCache = React.useRef(INTERVAL_CACHE_INITIAL_STATE); const isDisabled = !props.unstable_dataSource || props.lazyLoading !== true; + const addSkeletonRows = React.useCallback(() => { + if (isDisabled) { + return; + } + + const tree = privateApiRef.current.state.rows.tree; + const rootGroup = tree[GRID_ROOT_GROUP_ID] as GridGroupNode; + const rootGroupChildren = [...rootGroup.children]; + + const pageRowCount = privateApiRef.current.state.pagination.rowCount; + const rowCount = privateApiRef.current.getRowsCount(); + + for (let i = 0; i < pageRowCount - rowCount; i += 1) { + const skeletonId = getSkeletonRowId(i); + + rootGroupChildren.push(skeletonId); + + const skeletonRowNode: GridSkeletonRowNode = { + type: 'skeletonRow', + id: skeletonId, + parent: GRID_ROOT_GROUP_ID, + depth: 0, + }; + + tree[skeletonId] = skeletonRowNode; + } + + tree[GRID_ROOT_GROUP_ID] = { ...rootGroup, children: rootGroupChildren }; + + privateApiRef.current.setState( + (state) => ({ + ...state, + rows: { + ...state.rows, + tree, + }, + }), + 'addSkeletonRows', + ); + privateApiRef.current.requestPipeProcessorsApplication('hydrateRows'); + }, [privateApiRef, isDisabled]); + const handleRenderedRowsIntervalChange = React.useCallback< GridEventListener<'renderedRowsIntervalChange'> >( @@ -97,10 +145,10 @@ export const useGridDataSourceLazyLoader = ( return; } + const renderContext = gridRenderContextSelector(privateApiRef); privateApiRef.current.setRows([]); renderedRowsIntervalCache.current = INTERVAL_CACHE_INITIAL_STATE; - const renderContext = gridRenderContextSelector(privateApiRef); const fetchRowsParams: GridGetRowsParams = { start: renderContext.firstRowIndex, end: renderContext.lastRowIndex, @@ -122,10 +170,9 @@ export const useGridDataSourceLazyLoader = ( privateApiRef.current.setRows([]); renderedRowsIntervalCache.current = INTERVAL_CACHE_INITIAL_STATE; - const renderContext = gridRenderContextSelector(privateApiRef); const fetchRowsParams: GridGetRowsParams = { - start: renderContext.firstRowIndex, - end: renderContext.lastRowIndex, + start: 0, + end: privateApiRef.current.state.pagination.paginationModel.pageSize, sortModel, filterModel: newFilterModel, }; @@ -135,6 +182,7 @@ export const useGridDataSourceLazyLoader = ( [privateApiRef, isDisabled, sortModel], ); + useGridApiEventHandler(privateApiRef, 'rowResponseLoaded', addSkeletonRows); useGridApiEventHandler( privateApiRef, 'renderedRowsIntervalChange', diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoaderPreProcessors.tsx b/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoaderPreProcessors.tsx deleted file mode 100644 index 305f46a464629..0000000000000 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoaderPreProcessors.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import * as React from 'react'; -import { GridPipeProcessor, useGridRegisterPipeProcessor } from '@mui/x-data-grid/internals'; -import { GRID_ROOT_GROUP_ID, GridGroupNode, GridSkeletonRowNode } from '@mui/x-data-grid'; -import { GridPrivateApiPro } from '../../../models/gridApiPro'; -import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; - -export const GRID_SKELETON_ROW_ROOT_ID = 'auto-generated-skeleton-row-root'; - -const getSkeletonRowId = (index: number) => `${GRID_SKELETON_ROW_ROOT_ID}-${index}`; - -export const useGridDataSourceLazyLoaderPreProcessors = ( - privateApiRef: React.MutableRefObject, - props: Pick, -) => { - const addSkeletonRows = React.useCallback>( - (groupingParams) => { - const rootGroup = groupingParams.tree[GRID_ROOT_GROUP_ID] as GridGroupNode; - const isDisabled = !props.unstable_dataSource || props.lazyLoading !== true; - - if (isDisabled) { - return groupingParams; - } - - const rowCount = 100; // TODO: get the initial count from the viewport and later from the server - const tree = { ...groupingParams.tree }; - const rootGroupChildren = [...rootGroup.children]; - - for (let i = 0; i < rowCount - rootGroup.children.length; i += 1) { - const skeletonId = getSkeletonRowId(i); - - rootGroupChildren.push(skeletonId); - - const skeletonRowNode: GridSkeletonRowNode = { - type: 'skeletonRow', - id: skeletonId, - parent: GRID_ROOT_GROUP_ID, - depth: 0, - }; - - tree[skeletonId] = skeletonRowNode; - } - - tree[GRID_ROOT_GROUP_ID] = { ...rootGroup, children: rootGroupChildren }; - - return { - ...groupingParams, - tree, - }; - }, - [props.unstable_dataSource, props.lazyLoading], - ); - - useGridRegisterPipeProcessor(privateApiRef, 'hydrateRows', addSkeletonRows); -}; diff --git a/packages/x-data-grid/src/models/events/gridEventLookup.ts b/packages/x-data-grid/src/models/events/gridEventLookup.ts index b323518b8963b..cf45712236eab 100644 --- a/packages/x-data-grid/src/models/events/gridEventLookup.ts +++ b/packages/x-data-grid/src/models/events/gridEventLookup.ts @@ -389,6 +389,7 @@ export interface GridControlledStateReasonLookup { | 'restoreState' | 'removeAllFilterItems'; pagination: 'setPaginationModel' | 'stateRestorePreProcessing'; + rows: 'addSkeletonRows'; } export interface GridEventLookup @@ -431,6 +432,13 @@ export interface GridEventLookup */ strategyAvailabilityChange: {}; + // Data source + /** + * Fired when the new data is successfully added to the grid + * @ignore - do not document. + */ + rowResponseLoaded: {}; + // Columns /** * Fired when the columns state is changed. From 6b9e0adb1b364037222a69ee33133e9ab9379990 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 7 Aug 2024 22:25:03 +0200 Subject: [PATCH 015/121] Rename and repurpose the event --- packages/x-data-grid/src/internals/index.ts | 1 + packages/x-data-grid/src/models/events/gridEventLookup.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/x-data-grid/src/internals/index.ts b/packages/x-data-grid/src/internals/index.ts index f12ed42b1a35c..56a4b5d9eb8fc 100644 --- a/packages/x-data-grid/src/internals/index.ts +++ b/packages/x-data-grid/src/internals/index.ts @@ -141,6 +141,7 @@ export { } from '../hooks/features/listView/useGridListView'; export { useTimeout } from '../hooks/utils/useTimeout'; +export { throttle } from '../utils/throttle'; export { useGridVisibleRows, getVisibleRows } from '../hooks/utils/useGridVisibleRows'; export { useGridInitializeState } from '../hooks/utils/useGridInitializeState'; export type { GridStateInitializer } from '../hooks/utils/useGridInitializeState'; diff --git a/packages/x-data-grid/src/models/events/gridEventLookup.ts b/packages/x-data-grid/src/models/events/gridEventLookup.ts index cf45712236eab..c44a93e18c87e 100644 --- a/packages/x-data-grid/src/models/events/gridEventLookup.ts +++ b/packages/x-data-grid/src/models/events/gridEventLookup.ts @@ -434,10 +434,10 @@ export interface GridEventLookup // Data source /** - * Fired when the new data is successfully added to the grid + * Fired when the new data is successfully added to the grid either directly from the data source or from the cache * @ignore - do not document. */ - rowResponseLoaded: {}; + rowsFetched: {}; // Columns /** From 0755671096918585adec7c0f570d52e2980708a1 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 7 Aug 2024 22:25:36 +0200 Subject: [PATCH 016/121] Publish event even for data source updates from cache --- .../src/hooks/features/dataSource/useGridDataSource.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index 0d883e3857edf..e4398c603efd8 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -99,7 +99,7 @@ export const useGridDataSource = ( ...apiRef.current.unstable_applyPipeProcessors('getRowsParams', {}), ...rowFetchSlice.current, }; - + const startingIndex = fetchParams.start; const cachedData = apiRef.current.unstable_dataSource.cache.get(fetchParams); if (cachedData !== undefined) { @@ -108,10 +108,11 @@ export const useGridDataSource = ( apiRef.current.setRowCount(cachedData.rowCount); } if (isLazyLoaded) { - apiRef.current.unstable_replaceRows(fetchParams.start, rows); + apiRef.current.unstable_replaceRows(startingIndex, rows); } else { apiRef.current.setRows(rows); } + apiRef.current.publishEvent('rowsFetched'); return; } @@ -129,12 +130,12 @@ export const useGridDataSource = ( apiRef.current.setRowCount(getRowsResponse.rowCount); } if (isLazyLoaded) { - apiRef.current.unstable_replaceRows(fetchParams.start, getRowsResponse.rows); + apiRef.current.unstable_replaceRows(startingIndex, getRowsResponse.rows); } else { apiRef.current.setRows(getRowsResponse.rows); } apiRef.current.setLoading(false); - apiRef.current.publishEvent('rowResponseLoaded'); + apiRef.current.publishEvent('rowsFetched'); } catch (error) { apiRef.current.setRows([]); apiRef.current.setLoading(false); From 4262116c13ce1c2e60265b01bd362eb93e946890 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 7 Aug 2024 22:26:06 +0200 Subject: [PATCH 017/121] Fix duplicate key issues. Add initial throttling --- .../useGridDataSourceLazyLoader.ts | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) 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 f1f9d05ff614d..804158271babb 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 @@ -8,11 +8,13 @@ import { GRID_ROOT_GROUP_ID, GridGroupNode, GridSkeletonRowNode, + gridPaginationModelSelector, } from '@mui/x-data-grid'; import { getVisibleRows, GridGetRowsParams, gridRenderContextSelector, + throttle, } from '@mui/x-data-grid/internals'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; @@ -41,6 +43,7 @@ export const useGridDataSourceLazyLoader = ( ): void => { const sortModel = useGridSelector(privateApiRef, gridSortModelSelector); const filterModel = useGridSelector(privateApiRef, gridFilterModelSelector); + const paginationModel = useGridSelector(privateApiRef, gridPaginationModelSelector); const renderedRowsIntervalCache = React.useRef(INTERVAL_CACHE_INITIAL_STATE); const isDisabled = !props.unstable_dataSource || props.lazyLoading !== true; @@ -94,7 +97,7 @@ export const useGridDataSourceLazyLoader = ( return; } - const fetchRowsParams: GridGetRowsParams = { + const getRowsParams: GridGetRowsParams = { start: params.firstRowIndex, end: params.lastRowIndex, sortModel, @@ -131,14 +134,19 @@ export const useGridDataSourceLazyLoader = ( return; } - fetchRowsParams.start = skeletonRowsSection.firstRowIndex; - fetchRowsParams.end = skeletonRowsSection.lastRowIndex; + getRowsParams.start = skeletonRowsSection.firstRowIndex; + getRowsParams.end = skeletonRowsSection.lastRowIndex; - privateApiRef.current.publishEvent('getRows', fetchRowsParams); + privateApiRef.current.publishEvent('getRows', getRowsParams); }, [privateApiRef, isDisabled, props.pagination, props.paginationMode, sortModel, filterModel], ); + const throttledHandleRenderedRowsIntervalChange = React.useMemo( + () => throttle(handleRenderedRowsIntervalChange, 300), // TODO: make it configurable + [handleRenderedRowsIntervalChange], + ); + const handleGridSortModelChange = React.useCallback>( (newSortModel) => { if (isDisabled) { @@ -146,19 +154,22 @@ export const useGridDataSourceLazyLoader = ( } const renderContext = gridRenderContextSelector(privateApiRef); + // replace all rows with skeletons to maintain the same scroll position privateApiRef.current.setRows([]); + addSkeletonRows(); + renderedRowsIntervalCache.current = INTERVAL_CACHE_INITIAL_STATE; - const fetchRowsParams: GridGetRowsParams = { + const getRowsParams: GridGetRowsParams = { start: renderContext.firstRowIndex, end: renderContext.lastRowIndex, sortModel: newSortModel, filterModel, }; - privateApiRef.current.publishEvent('getRows', fetchRowsParams); + privateApiRef.current.publishEvent('getRows', getRowsParams); }, - [privateApiRef, isDisabled, filterModel], + [privateApiRef, isDisabled, filterModel, addSkeletonRows], ); const handleGridFilterModelChange = React.useCallback>( @@ -170,23 +181,23 @@ export const useGridDataSourceLazyLoader = ( privateApiRef.current.setRows([]); renderedRowsIntervalCache.current = INTERVAL_CACHE_INITIAL_STATE; - const fetchRowsParams: GridGetRowsParams = { + const getRowsParams: GridGetRowsParams = { start: 0, - end: privateApiRef.current.state.pagination.paginationModel.pageSize, + end: paginationModel.pageSize, sortModel, filterModel: newFilterModel, }; - privateApiRef.current.publishEvent('getRows', fetchRowsParams); + privateApiRef.current.publishEvent('getRows', getRowsParams); }, - [privateApiRef, isDisabled, sortModel], + [privateApiRef, isDisabled, sortModel, paginationModel.pageSize], ); - useGridApiEventHandler(privateApiRef, 'rowResponseLoaded', addSkeletonRows); + useGridApiEventHandler(privateApiRef, 'rowsFetched', addSkeletonRows); useGridApiEventHandler( privateApiRef, 'renderedRowsIntervalChange', - handleRenderedRowsIntervalChange, + throttledHandleRenderedRowsIntervalChange, ); useGridApiEventHandler(privateApiRef, 'sortModelChange', handleGridSortModelChange); useGridApiEventHandler(privateApiRef, 'filterModelChange', handleGridFilterModelChange); From 6e431f338fb829a12e07557a1dcbb8560bbe481b Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 8 Aug 2024 08:40:19 +0200 Subject: [PATCH 018/121] Move events to pro lookup. Do not document --- docs/data/data-grid/events/events.json | 7 ------- packages/x-data-grid-pro/src/typeOverloads/modules.ts | 9 ++++++++- .../x-data-grid/src/models/events/gridEventLookup.ts | 7 ------- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/docs/data/data-grid/events/events.json b/docs/data/data-grid/events/events.json index b5532855ed777..a2831d2974c28 100644 --- a/docs/data/data-grid/events/events.json +++ b/docs/data/data-grid/events/events.json @@ -239,13 +239,6 @@ "params": "GridFilterModel", "event": "MuiEvent<{}>" }, - { - "projects": ["x-data-grid-pro", "x-data-grid-premium"], - "name": "getRows", - "description": "Fired when the grid needs to fetch a new batch of rows from the data source. Called with a GridGetRowsParams object.", - "params": "GridGetRowsParams", - "event": "MuiEvent<{}>" - }, { "projects": ["x-data-grid", "x-data-grid-pro", "x-data-grid-premium"], "name": "headerSelectionCheckboxChange", diff --git a/packages/x-data-grid-pro/src/typeOverloads/modules.ts b/packages/x-data-grid-pro/src/typeOverloads/modules.ts index 82575431b8d28..36cd489dfea19 100644 --- a/packages/x-data-grid-pro/src/typeOverloads/modules.ts +++ b/packages/x-data-grid-pro/src/typeOverloads/modules.ts @@ -45,10 +45,17 @@ export interface GridEventLookupPro { * Fired when a new batch of rows is requested to be loaded. Called with a [[GridFetchRowsParams]] object. */ fetchRows: { params: GridFetchRowsParams }; + // Data source /** - * Fired when the grid needs to fetch a new batch of rows from the data source. Called with a [[GridGetRowsParams]] object. + * Fired when the grid needs to fetch a new batch of rows from the data source. + * @ignore - do not document. */ getRows: { params: GridGetRowsParams }; + /** + * Fired when the new data is successfully added to the grid either directly from the data source or from the cache + * @ignore - do not document. + */ + rowsFetched: {}; } export interface GridPipeProcessingLookupPro { diff --git a/packages/x-data-grid/src/models/events/gridEventLookup.ts b/packages/x-data-grid/src/models/events/gridEventLookup.ts index c44a93e18c87e..fd6628cfc0716 100644 --- a/packages/x-data-grid/src/models/events/gridEventLookup.ts +++ b/packages/x-data-grid/src/models/events/gridEventLookup.ts @@ -432,13 +432,6 @@ export interface GridEventLookup */ strategyAvailabilityChange: {}; - // Data source - /** - * Fired when the new data is successfully added to the grid either directly from the data source or from the cache - * @ignore - do not document. - */ - rowsFetched: {}; - // Columns /** * Fired when the columns state is changed. From 74109be6acb054af869150fcd86b0f87da844da9 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 8 Aug 2024 08:40:37 +0200 Subject: [PATCH 019/121] Add default value --- packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts index 295702230804c..4724876fa8590 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts +++ b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts @@ -56,6 +56,7 @@ export const DATA_GRID_PRO_PROPS_DEFAULT_VALUES: DataGridProPropsWithDefaultValu scrollEndThreshold: 80, treeData: false, unstable_listView: false, + lazyLoading: false, }; const defaultSlots = DATA_GRID_PRO_DEFAULT_SLOTS_COMPONENTS; From b16d9c609e65fa980e7992a1b67bbc0d90330f4d Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 8 Aug 2024 08:40:57 +0200 Subject: [PATCH 020/121] Add hook to the premium package --- .../src/DataGridPremium/useDataGridPremiumComponent.tsx | 2 ++ packages/x-data-grid-pro/src/internals/index.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx index 46c76914dda78..5e3f20c6ea49d 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx @@ -61,6 +61,7 @@ import { columnGroupsStateInitializer, useGridLazyLoader, useGridLazyLoaderPreProcessors, + useGridDataSourceLazyLoader, headerFilteringStateInitializer, useGridHeaderFiltering, virtualizationStateInitializer, @@ -180,6 +181,7 @@ export const useDataGridPremiumComponent = ( useGridScroll(apiRef, props); useGridInfiniteLoader(apiRef, props); useGridLazyLoader(apiRef, props); + useGridDataSourceLazyLoader(apiRef, props); useGridColumnMenu(apiRef); useGridCsvExport(apiRef, props); useGridPrintExport(apiRef, props); diff --git a/packages/x-data-grid-pro/src/internals/index.ts b/packages/x-data-grid-pro/src/internals/index.ts index ed619bb0cc9e4..f821f34170111 100644 --- a/packages/x-data-grid-pro/src/internals/index.ts +++ b/packages/x-data-grid-pro/src/internals/index.ts @@ -43,6 +43,7 @@ export { } from '../hooks/features/rowPinning/useGridRowPinningPreProcessors'; export { useGridLazyLoader } from '../hooks/features/lazyLoader/useGridLazyLoader'; export { useGridLazyLoaderPreProcessors } from '../hooks/features/lazyLoader/useGridLazyLoaderPreProcessors'; +export { useGridDataSourceLazyLoader } from '../hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader'; export { useGridDataSource, dataSourceStateInitializer, From 95413f1c79669171f2c3d4553dfbc59ebd509605 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 8 Aug 2024 08:56:10 +0200 Subject: [PATCH 021/121] Update demo --- .../server-side-data/ServerSideLazyLoading.js | 12 +++++++++--- .../server-side-data/ServerSideLazyLoading.tsx | 11 +++++++++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoading.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoading.js index 28bde31782ebb..ce521230b5ee3 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoading.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoading.js @@ -1,10 +1,10 @@ import * as React from 'react'; -import { DataGridPro } from '@mui/x-data-grid-pro'; +import { DataGridPro, GridToolbar } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; function ServerSideLazyLoading() { const { columns, fetchRows } = useMockServer( - {}, + { rowLength: 120 }, { useCursorPagination: false, minDelay: 300, maxDelay: 800 }, ); @@ -32,7 +32,13 @@ function ServerSideLazyLoading() { return (
- +
); } diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoading.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoading.tsx index 82d4553ba3a20..4e957b5076da1 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoading.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoading.tsx @@ -3,12 +3,13 @@ import { DataGridPro, GridDataSource, GridGetRowsParams, + GridToolbar, } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; function ServerSideLazyLoading() { const { columns, fetchRows } = useMockServer( - {}, + { rowLength: 120 }, { useCursorPagination: false, minDelay: 300, maxDelay: 800 }, ); @@ -36,7 +37,13 @@ function ServerSideLazyLoading() { return (
- +
); } From 4c06201df40ff63f4891bc32f6e0e6f34cdb9db3 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 14 Aug 2024 13:23:31 +0200 Subject: [PATCH 022/121] Remove encoding --- .../data-grid/server-side-data/ServerSideLazyLoading.js | 8 ++++---- .../data-grid/server-side-data/ServerSideLazyLoading.tsx | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoading.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoading.js index ce521230b5ee3..576f647a5a021 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoading.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoading.js @@ -12,10 +12,10 @@ function ServerSideLazyLoading() { () => ({ getRows: async (params) => { const urlParams = new URLSearchParams({ - filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), - sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), - firstRowToRender: encodeURIComponent(JSON.stringify(params.start)), - lastRowToRender: encodeURIComponent(JSON.stringify(params.end)), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + firstRowToRender: `${params.start}`, + lastRowToRender: `${params.end}`, }); const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoading.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoading.tsx index 4e957b5076da1..b0001d369ff4a 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoading.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoading.tsx @@ -17,10 +17,10 @@ function ServerSideLazyLoading() { () => ({ getRows: async (params: GridGetRowsParams) => { const urlParams = new URLSearchParams({ - filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), - sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), - firstRowToRender: encodeURIComponent(JSON.stringify(params.start)), - lastRowToRender: encodeURIComponent(JSON.stringify(params.end)), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + firstRowToRender: `${params.start}`, + lastRowToRender: `${params.end}`, }); const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, From 17cc7b346139fd0b0f6e4d5f4beb3ecdff54c37a Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Fri, 16 Aug 2024 16:26:43 +0200 Subject: [PATCH 023/121] Remove non-existing import --- packages/x-data-grid/src/internals/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/x-data-grid/src/internals/index.ts b/packages/x-data-grid/src/internals/index.ts index 56a4b5d9eb8fc..f12ed42b1a35c 100644 --- a/packages/x-data-grid/src/internals/index.ts +++ b/packages/x-data-grid/src/internals/index.ts @@ -141,7 +141,6 @@ export { } from '../hooks/features/listView/useGridListView'; export { useTimeout } from '../hooks/utils/useTimeout'; -export { throttle } from '../utils/throttle'; export { useGridVisibleRows, getVisibleRows } from '../hooks/utils/useGridVisibleRows'; export { useGridInitializeState } from '../hooks/utils/useGridInitializeState'; export type { GridStateInitializer } from '../hooks/utils/useGridInitializeState'; From 3292704eafb8f966b03a546abaf591d11af1c441 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Fri, 16 Aug 2024 16:29:03 +0200 Subject: [PATCH 024/121] Fix the mock data slice calculation --- .../x-data-grid-generator/src/hooks/serverUtils.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/x-data-grid-generator/src/hooks/serverUtils.ts b/packages/x-data-grid-generator/src/hooks/serverUtils.ts index 3b8d0eede1d59..30418ea503806 100644 --- a/packages/x-data-grid-generator/src/hooks/serverUtils.ts +++ b/packages/x-data-grid-generator/src/hooks/serverUtils.ts @@ -294,20 +294,20 @@ export const loadServerRows = ( lastRowIndex = lastRowToRender; } else if (!pageSize) { firstRowIndex = 0; - lastRowIndex = filteredRows.length; + lastRowIndex = filteredRows.length - 1; } else if (useCursorPagination) { firstRowIndex = cursor ? filteredRows.findIndex(({ id }) => id === cursor) : 0; firstRowIndex = Math.max(firstRowIndex, 0); // if cursor not found return 0 - lastRowIndex = firstRowIndex + pageSize; + lastRowIndex = firstRowIndex + pageSize - 1; - nextCursor = lastRowIndex >= filteredRows.length ? undefined : filteredRows[lastRowIndex].id; + nextCursor = filteredRows[lastRowIndex + 1]?.id; } else { firstRowIndex = page * pageSize; - lastRowIndex = (page + 1) * pageSize; + lastRowIndex = (page + 1) * pageSize - 1; } const hasNextPage = lastRowIndex < filteredRows.length - 1; const response: FakeServerResponse = { - returnedRows: filteredRows.slice(firstRowIndex, lastRowIndex), + returnedRows: filteredRows.slice(firstRowIndex, lastRowIndex + 1), hasNextPage, nextCursor, totalRowCount, From c6dec3b3d5893efff334495e4d8c69d01dc21ba9 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Fri, 16 Aug 2024 16:29:44 +0200 Subject: [PATCH 025/121] Import throttle from internals package. Do not reset rendering context when sorting model is updated --- .../serverSideLazyLoader/useGridDataSourceLazyLoader.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) 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 804158271babb..72c302f421208 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 @@ -1,4 +1,5 @@ import * as React from 'react'; +import { throttle } from '@mui/x-internals/throttle'; import { useGridApiEventHandler, useGridSelector, @@ -14,7 +15,6 @@ import { getVisibleRows, GridGetRowsParams, gridRenderContextSelector, - throttle, } from '@mui/x-data-grid/internals'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; @@ -143,7 +143,7 @@ export const useGridDataSourceLazyLoader = ( ); const throttledHandleRenderedRowsIntervalChange = React.useMemo( - () => throttle(handleRenderedRowsIntervalChange, 300), // TODO: make it configurable + () => throttle(handleRenderedRowsIntervalChange, 500), // TODO: make it configurable [handleRenderedRowsIntervalChange], ); @@ -158,8 +158,6 @@ export const useGridDataSourceLazyLoader = ( privateApiRef.current.setRows([]); addSkeletonRows(); - renderedRowsIntervalCache.current = INTERVAL_CACHE_INITIAL_STATE; - const getRowsParams: GridGetRowsParams = { start: renderContext.firstRowIndex, end: renderContext.lastRowIndex, From 834c05e89638ff4e829622a4ba83e440835d401b Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Fri, 16 Aug 2024 16:31:00 +0200 Subject: [PATCH 026/121] Adjust start and end params to cover whole page(s). Cache responses in chunks based on the pagination model and props. Combine cache entries when needed --- .../src/hooks/features/dataSource/cache.ts | 104 ++++++++++++++++-- .../features/dataSource/useGridDataSource.ts | 54 +++++++-- 2 files changed, 138 insertions(+), 20 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts index daea7e9a5e5a6..59e2581a0c71d 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts @@ -1,17 +1,23 @@ import { GridGetRowsParams, GridGetRowsResponse } from '../../../models'; -type GridDataSourceCacheDefaultConfig = { +export type GridDataSourceCacheDefaultConfig = { /** * Time To Live for each cache entry in milliseconds. * After this time the cache entry will become stale and the next query will result in cache miss. * @default 300000 (5 minutes) */ ttl?: number; + /** + * The number of rows to store in each cache entry. If not set, the whole array will be stored in a single cache entry. + * Setting this value to smallest page size will result in better cache hit rate. + * Has no effect if cursor pagination is used. + * @default undefined + */ + chunkSize?: number; }; function getKey(params: GridGetRowsParams) { return JSON.stringify([ - params.paginationModel, params.filterModel, params.sortModel, params.groupKeys, @@ -22,32 +28,106 @@ function getKey(params: GridGetRowsParams) { } export class GridDataSourceCacheDefault { - private cache: Record; + private cache: Record< + string, + { + value: GridGetRowsResponse; + expiry: number; + chunk: { startIndex: string | number; endIndex: number }; + } + >; private ttl: number; - constructor({ ttl = 300000 }: GridDataSourceCacheDefaultConfig) { + private chunkSize: number; + + private getChunkRanges = (params: GridGetRowsParams) => { + if (this.chunkSize < 1 || typeof params.start !== 'number') { + return [{ startIndex: params.start, endIndex: params.end }]; + } + + // split the range into chunks + const chunkRanges = []; + for (let i = params.start; i < params.end; i += this.chunkSize) { + const endIndex = Math.min(i + this.chunkSize - 1, params.end); + chunkRanges.push({ startIndex: i, endIndex }); + } + + return chunkRanges; + }; + + constructor({ chunkSize, ttl = 300000 }: GridDataSourceCacheDefaultConfig) { this.cache = {}; this.ttl = ttl; + this.chunkSize = chunkSize || 0; } set(key: GridGetRowsParams, value: GridGetRowsResponse) { - const keyString = getKey(key); + const chunks = this.getChunkRanges(key); const expiry = Date.now() + this.ttl; - this.cache[keyString] = { value, expiry }; + + chunks.forEach((chunk) => { + const isLastChunk = chunk.endIndex === key.end; + const keyString = getKey({ ...key, start: chunk.startIndex, end: chunk.endIndex }); + const chunkValue: GridGetRowsResponse = { + ...value, + pageInfo: { + ...value.pageInfo, + // If the original response had page info, update that information for all but last chunk and keep the original value for the last chunk + hasNextPage: + (value.pageInfo?.hasNextPage !== undefined && !isLastChunk) || + value.pageInfo?.hasNextPage, + nextCursor: + value.pageInfo?.nextCursor !== undefined && !isLastChunk + ? value.rows[chunk.endIndex + 1].id + : value.pageInfo?.nextCursor, + }, + rows: + typeof chunk.startIndex !== 'number' || typeof key.start !== 'number' + ? value.rows + : value.rows.slice(chunk.startIndex - key.start, chunk.endIndex - key.start + 1), + }; + + this.cache[keyString] = { value: chunkValue, expiry, chunk }; + }); } get(key: GridGetRowsParams): GridGetRowsResponse | undefined { - const keyString = getKey(key); - const entry = this.cache[keyString]; - if (!entry) { + const chunks = this.getChunkRanges(key); + + const startChunk = chunks.findIndex((chunk) => chunk.startIndex === key.start); + const endChunk = chunks.findIndex((chunk) => chunk.endIndex === key.end); + + // If desired range cannot fit completely in chunks, then it is a cache miss + if (startChunk === -1 || endChunk === -1) { return undefined; } - if (Date.now() > entry.expiry) { - delete this.cache[keyString]; + + const cachedResponses = []; + + for (let i = startChunk; i <= endChunk; i += 1) { + const keyString = getKey({ ...key, start: chunks[i].startIndex, end: chunks[i].endIndex }); + const entry = this.cache[keyString]; + const isCacheValid = entry?.value && Date.now() < entry.expiry; + cachedResponses.push(isCacheValid ? entry?.value : null); + } + + // If any of the chunks is missing, then it is a cache miss + if (cachedResponses.some((response) => response === null)) { return undefined; } - return entry.value; + + // Merge the chunks into a single response + return (cachedResponses as GridGetRowsResponse[]).reduce( + (acc: GridGetRowsResponse, response) => { + return { + rows: [...acc.rows, ...response.rows], + rowCount: response.rowCount, + pageInfo: response.pageInfo, + }; + }, + { rows: [], rowCount: 0, pageInfo: {} }, + ); } clear() { diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index e4398c603efd8..1f7b30e829338 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -7,6 +7,7 @@ import { GridDataSourceGroupNode, useGridSelector, GridRowId, + gridPaginationModelSelector, } from '@mui/x-data-grid'; import { GridGetRowsParams, @@ -19,7 +20,7 @@ import { gridGetRowsParamsSelector, gridDataSourceErrorsSelector } from './gridD import { GridDataSourceApi, GridDataSourceApiBase, GridDataSourcePrivateApi } from './interfaces'; import { NestedDataManager, RequestStatus, runIf } from './utils'; import { GridDataSourceCache } from '../../../models'; -import { GridDataSourceCacheDefault } from './cache'; +import { GridDataSourceCacheDefault, GridDataSourceCacheDefaultConfig } from './cache'; const INITIAL_STATE = { loading: {}, @@ -32,11 +33,14 @@ const noopCache: GridDataSourceCache = { set: () => {}, }; -function getCache(cacheProp?: GridDataSourceCache | null) { +function getCache( + cacheProp?: GridDataSourceCache | null, + options: GridDataSourceCacheDefaultConfig = {}, +) { if (cacheProp === null) { return noopCache; } - return cacheProp ?? new GridDataSourceCacheDefault({}); + return cacheProp ?? new GridDataSourceCacheDefault(options); } export const dataSourceStateInitializer: GridStateInitializer = (state) => { @@ -56,6 +60,7 @@ export const useGridDataSource = ( | 'sortingMode' | 'filterMode' | 'paginationMode' + | 'pageSizeOptions' | 'treeData' | 'lazyLoading' >, @@ -63,6 +68,7 @@ export const useGridDataSource = ( const nestedDataManager = useLazyRef( () => new NestedDataManager(apiRef), ).current; + const paginationModel = useGridSelector(apiRef, gridPaginationModelSelector); const groupsToAutoFetch = useGridSelector(apiRef, gridRowGroupsToFetchSelector); const scheduledGroups = React.useRef(0); @@ -71,8 +77,38 @@ export const useGridDataSource = ( const onError = props.unstable_onDataSourceError; + const cacheChunkSize = React.useMemo(() => { + const sortedPageSizeOptions = props.pageSizeOptions + .map((option) => (typeof option === 'number' ? option : option.value)) + .sort((a, b) => a - b); + + return Math.min(paginationModel.pageSize, sortedPageSizeOptions[0]); + }, [paginationModel.pageSize, props.pageSizeOptions]); + const [cache, setCache] = React.useState(() => - getCache(props.unstable_dataSourceCache), + getCache(props.unstable_dataSourceCache, { + chunkSize: cacheChunkSize, + }), + ); + + // Adjust the render context range to fit the pagination model's page size + // First row index should be decreased to the start of the page, end row index should be increased to the end of the page or the last row + const adjustRowParams = React.useCallback( + (params: Pick) => { + if (typeof params.start !== 'number') { + return params; + } + + const rowCount = apiRef.current.state.pagination.rowCount; + return { + start: params.start - (params.start % paginationModel.pageSize), + end: Math.min( + params.end + paginationModel.pageSize - (params.end % paginationModel.pageSize) - 1, + rowCount - 1, + ), + }; + }, + [apiRef, paginationModel], ); const fetchRows = React.useCallback( @@ -154,10 +190,10 @@ export const useGridDataSource = ( const fetchRowBatch = React.useCallback( (fetchParams: GridGetRowsParams) => { - rowFetchSlice.current = { start: Number(fetchParams.start), end: fetchParams.end }; + rowFetchSlice.current = adjustRowParams(fetchParams); return fetchRows(); }, - [fetchRows], + [adjustRowParams, fetchRows], ); const fetchRowChildren = React.useCallback( @@ -328,9 +364,11 @@ export const useGridDataSource = ( isFirstRender.current = false; return; } - const newCache = getCache(props.unstable_dataSourceCache); + const newCache = getCache(props.unstable_dataSourceCache, { + chunkSize: cacheChunkSize, + }); setCache((prevCache) => (prevCache !== newCache ? newCache : prevCache)); - }, [props.unstable_dataSourceCache]); + }, [props.unstable_dataSourceCache, cacheChunkSize]); React.useEffect(() => { if (props.unstable_dataSource) { From ed89679ea0bb0c1190e52145fde927789a7cabc4 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Fri, 16 Aug 2024 22:26:40 +0200 Subject: [PATCH 027/121] Do not reset render context on filtering --- .../serverSideLazyLoader/useGridDataSourceLazyLoader.ts | 2 -- 1 file changed, 2 deletions(-) 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 72c302f421208..3bbcc65742275 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 @@ -177,8 +177,6 @@ export const useGridDataSourceLazyLoader = ( } privateApiRef.current.setRows([]); - renderedRowsIntervalCache.current = INTERVAL_CACHE_INITIAL_STATE; - const getRowsParams: GridGetRowsParams = { start: 0, end: paginationModel.pageSize, From fb6fe268089b5f65d25d79dbe9c48c1f13f384bf Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Fri, 16 Aug 2024 22:28:45 +0200 Subject: [PATCH 028/121] Params should always be adjusted to the whole page to allow proper expansion while filtering. If there are no rows, use setRows API instead of replacement --- .../hooks/features/dataSource/useGridDataSource.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index 1f7b30e829338..a81953734b346 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -92,23 +92,19 @@ export const useGridDataSource = ( ); // Adjust the render context range to fit the pagination model's page size - // First row index should be decreased to the start of the page, end row index should be increased to the end of the page or the last row + // First row index should be decreased to the start of the page, end row index should be increased to the end of the page const adjustRowParams = React.useCallback( (params: Pick) => { if (typeof params.start !== 'number') { return params; } - const rowCount = apiRef.current.state.pagination.rowCount; return { start: params.start - (params.start % paginationModel.pageSize), - end: Math.min( - params.end + paginationModel.pageSize - (params.end % paginationModel.pageSize) - 1, - rowCount - 1, - ), + end: params.end + paginationModel.pageSize - (params.end % paginationModel.pageSize) - 1, }; }, - [apiRef, paginationModel], + [paginationModel], ); const fetchRows = React.useCallback( @@ -143,7 +139,7 @@ export const useGridDataSource = ( if (cachedData.rowCount !== undefined) { apiRef.current.setRowCount(cachedData.rowCount); } - if (isLazyLoaded) { + if (isLazyLoaded && !!cachedData.rowCount) { apiRef.current.unstable_replaceRows(startingIndex, rows); } else { apiRef.current.setRows(rows); @@ -165,7 +161,7 @@ export const useGridDataSource = ( if (getRowsResponse.rowCount !== undefined) { apiRef.current.setRowCount(getRowsResponse.rowCount); } - if (isLazyLoaded) { + if (isLazyLoaded && !!getRowsResponse.rowCount) { apiRef.current.unstable_replaceRows(startingIndex, getRowsResponse.rows); } else { apiRef.current.setRows(getRowsResponse.rows); From 46fe91d560c9b5515db1a9931343398db9f76739 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Fri, 6 Sep 2024 12:06:18 +0200 Subject: [PATCH 029/121] Add support for infinite loading --- .../features/dataSource/useGridDataSource.ts | 12 +- .../useGridDataSourceLazyLoader.ts | 125 ++++++++++++++++-- 2 files changed, 117 insertions(+), 20 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index a81953734b346..6446426e9e2b0 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -136,10 +136,10 @@ export const useGridDataSource = ( if (cachedData !== undefined) { const rows = cachedData.rows; - if (cachedData.rowCount !== undefined) { + if (cachedData.rowCount && cachedData.rowCount >= 0) { apiRef.current.setRowCount(cachedData.rowCount); } - if (isLazyLoaded && !!cachedData.rowCount) { + if (isLazyLoaded) { apiRef.current.unstable_replaceRows(startingIndex, rows); } else { apiRef.current.setRows(rows); @@ -158,10 +158,10 @@ export const useGridDataSource = ( try { const getRowsResponse = await getRows(fetchParams); apiRef.current.unstable_dataSource.cache.set(fetchParams, getRowsResponse); - if (getRowsResponse.rowCount !== undefined) { + if (getRowsResponse.rowCount && getRowsResponse.rowCount >= 0) { apiRef.current.setRowCount(getRowsResponse.rowCount); } - if (isLazyLoaded && !!getRowsResponse.rowCount) { + if (isLazyLoaded) { apiRef.current.unstable_replaceRows(startingIndex, getRowsResponse.rows); } else { apiRef.current.setRows(getRowsResponse.rows); @@ -223,7 +223,7 @@ export const useGridDataSource = ( const rows = cachedData.rows; nestedDataManager.setRequestSettled(id); apiRef.current.updateServerRows(rows, rowNode.path); - if (cachedData.rowCount !== undefined) { + if (cachedData.rowCount && cachedData.rowCount >= 0) { apiRef.current.setRowCount(cachedData.rowCount); } apiRef.current.setRowChildrenExpansion(id, true); @@ -249,7 +249,7 @@ export const useGridDataSource = ( } nestedDataManager.setRequestSettled(id); apiRef.current.unstable_dataSource.cache.set(fetchParams, getRowsResponse); - if (getRowsResponse.rowCount !== undefined) { + if (getRowsResponse.rowCount && getRowsResponse.rowCount >= 0) { apiRef.current.setRowCount(getRowsResponse.rowCount); } apiRef.current.updateServerRows(getRowsResponse.rows, rowNode.path); 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 3bbcc65742275..53f8ce842ee0a 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 @@ -10,6 +10,7 @@ import { GridGroupNode, GridSkeletonRowNode, gridPaginationModelSelector, + gridDimensionsSelector, } from '@mui/x-data-grid'; import { getVisibleRows, @@ -21,6 +22,11 @@ import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; import { findSkeletonRowsSection } from '../lazyLoader/utils'; import { GRID_SKELETON_ROW_ROOT_ID } from '../lazyLoader/useGridLazyLoaderPreProcessors'; +enum LoadingTrigger { + VIEWPORT, + SCROLL_END, +} + const INTERVAL_CACHE_INITIAL_STATE = { firstRowToRender: 0, lastRowToRender: 0, @@ -38,20 +44,28 @@ export const useGridDataSourceLazyLoader = ( privateApiRef: React.MutableRefObject, props: Pick< DataGridProProcessedProps, - 'pagination' | 'paginationMode' | 'unstable_dataSource' | 'lazyLoading' + 'pagination' | 'paginationMode' | 'unstable_dataSource' | 'lazyLoading' | 'scrollEndThreshold' >, ): void => { const sortModel = useGridSelector(privateApiRef, gridSortModelSelector); const filterModel = useGridSelector(privateApiRef, gridFilterModelSelector); const paginationModel = useGridSelector(privateApiRef, gridPaginationModelSelector); + const dimensions = useGridSelector(privateApiRef, gridDimensionsSelector); + const renderContext = useGridSelector(privateApiRef, gridRenderContextSelector); const renderedRowsIntervalCache = React.useRef(INTERVAL_CACHE_INITIAL_STATE); + const previousLastRowIndex = React.useRef(0); const isDisabled = !props.unstable_dataSource || props.lazyLoading !== true; + const [loadingMode, setLoadingMode] = React.useState(null); - const addSkeletonRows = React.useCallback(() => { - if (isDisabled) { - return; - } + const heights = React.useMemo( + () => ({ + viewport: dimensions.viewportInnerSize.height, + content: dimensions.contentSize.height, + }), + [dimensions.viewportInnerSize.height, dimensions.contentSize.height], + ); + const addSkeletonRows = React.useCallback(() => { const tree = privateApiRef.current.state.rows.tree; const rootGroup = tree[GRID_ROOT_GROUP_ID] as GridGroupNode; const rootGroupChildren = [...rootGroup.children]; @@ -59,6 +73,10 @@ export const useGridDataSourceLazyLoader = ( const pageRowCount = privateApiRef.current.state.pagination.rowCount; const rowCount = privateApiRef.current.getRowsCount(); + if (pageRowCount === undefined || rowCount >= pageRowCount) { + return; + } + for (let i = 0; i < pageRowCount - rowCount; i += 1) { const skeletonId = getSkeletonRowId(i); @@ -86,8 +104,65 @@ export const useGridDataSourceLazyLoader = ( }), 'addSkeletonRows', ); + }, [privateApiRef]); + + const handleScrolling: GridEventListener<'scrollPositionChange'> = React.useCallback( + (newScrollPosition) => { + if ( + isDisabled || + loadingMode !== LoadingTrigger.SCROLL_END || + previousLastRowIndex.current >= renderContext.lastRowIndex + ) { + return; + } + + const position = newScrollPosition.top + heights.viewport; + const target = heights.content - props.scrollEndThreshold; + + if (position >= target) { + previousLastRowIndex.current = renderContext.lastRowIndex; + + const getRowsParams: GridGetRowsParams = { + start: renderContext.lastRowIndex, + end: renderContext.lastRowIndex + paginationModel.pageSize - 1, + sortModel, + filterModel, + }; + + privateApiRef.current.setLoading(true); + privateApiRef.current.publishEvent('getRows', getRowsParams); + } + }, + [ + privateApiRef, + props.scrollEndThreshold, + isDisabled, + loadingMode, + sortModel, + filterModel, + heights, + paginationModel.pageSize, + renderContext.lastRowIndex, + ], + ); + + const handleDataUpdate = React.useCallback(() => { + if (isDisabled) { + return; + } + + const pageRowCount = privateApiRef.current.state.pagination.rowCount; + const newLoadingMode = + pageRowCount === undefined || pageRowCount === -1 + ? LoadingTrigger.SCROLL_END + : LoadingTrigger.VIEWPORT; + if (loadingMode !== newLoadingMode) { + setLoadingMode(newLoadingMode); + } + + addSkeletonRows(); privateApiRef.current.requestPipeProcessorsApplication('hydrateRows'); - }, [privateApiRef, isDisabled]); + }, [privateApiRef, isDisabled, loadingMode, addSkeletonRows]); const handleRenderedRowsIntervalChange = React.useCallback< GridEventListener<'renderedRowsIntervalChange'> @@ -153,21 +228,41 @@ export const useGridDataSourceLazyLoader = ( return; } - const renderContext = gridRenderContextSelector(privateApiRef); - // replace all rows with skeletons to maintain the same scroll position privateApiRef.current.setRows([]); - addSkeletonRows(); + previousLastRowIndex.current = 0; + if (loadingMode === LoadingTrigger.VIEWPORT) { + // replace all rows with skeletons to maintain the same scroll position + addSkeletonRows(); + } + + const rangeParams = + loadingMode === LoadingTrigger.VIEWPORT + ? { + start: renderContext.firstRowIndex, + end: renderContext.lastRowIndex, + } + : { + start: 0, + end: paginationModel.pageSize - 1, + }; const getRowsParams: GridGetRowsParams = { - start: renderContext.firstRowIndex, - end: renderContext.lastRowIndex, + ...rangeParams, sortModel: newSortModel, filterModel, }; privateApiRef.current.publishEvent('getRows', getRowsParams); }, - [privateApiRef, isDisabled, filterModel, addSkeletonRows], + [ + privateApiRef, + isDisabled, + loadingMode, + filterModel, + paginationModel.pageSize, + renderContext, + addSkeletonRows, + ], ); const handleGridFilterModelChange = React.useCallback>( @@ -177,9 +272,10 @@ export const useGridDataSourceLazyLoader = ( } privateApiRef.current.setRows([]); + previousLastRowIndex.current = 0; const getRowsParams: GridGetRowsParams = { start: 0, - end: paginationModel.pageSize, + end: paginationModel.pageSize - 1, sortModel, filterModel: newFilterModel, }; @@ -189,7 +285,8 @@ export const useGridDataSourceLazyLoader = ( [privateApiRef, isDisabled, sortModel, paginationModel.pageSize], ); - useGridApiEventHandler(privateApiRef, 'rowsFetched', addSkeletonRows); + useGridApiEventHandler(privateApiRef, 'rowsFetched', handleDataUpdate); + useGridApiEventHandler(privateApiRef, 'scrollPositionChange', handleScrolling); useGridApiEventHandler( privateApiRef, 'renderedRowsIntervalChange', From 9f26c2d6408d604f9a3cf1ead5454cb102f3f3de Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Fri, 6 Sep 2024 12:27:40 +0200 Subject: [PATCH 030/121] Update docs --- .../ServerSideLazyLoadingInfinite.js | 45 +++++++++++++++++ .../ServerSideLazyLoadingInfinite.tsx | 50 +++++++++++++++++++ ...ng.js => ServerSideLazyLoadingViewport.js} | 0 ....tsx => ServerSideLazyLoadingViewport.tsx} | 0 .../server-side-data/lazy-loading.md | 12 ++++- docs/data/pages.ts | 7 ++- 6 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js create mode 100644 docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.tsx rename docs/data/data-grid/server-side-data/{ServerSideLazyLoading.js => ServerSideLazyLoadingViewport.js} (100%) rename docs/data/data-grid/server-side-data/{ServerSideLazyLoading.tsx => ServerSideLazyLoadingViewport.tsx} (100%) diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js new file mode 100644 index 0000000000000..9b9087a344875 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js @@ -0,0 +1,45 @@ +import * as React from 'react'; +import { DataGridPro, GridToolbar } from '@mui/x-data-grid-pro'; +import { useMockServer } from '@mui/x-data-grid-generator'; + +function ServerSideLazyLoading() { + const { columns, fetchRows } = useMockServer( + { rowLength: 120 }, + { useCursorPagination: false, minDelay: 300, maxDelay: 800 }, + ); + + const dataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + firstRowToRender: `${params.start}`, + lastRowToRender: `${params.end}`, + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + + return { + rows: getRowsResponse.rows, + }; + }, + }), + [fetchRows], + ); + + return ( +
+ +
+ ); +} + +export default ServerSideLazyLoading; diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.tsx new file mode 100644 index 0000000000000..922a16e5a297e --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.tsx @@ -0,0 +1,50 @@ +import * as React from 'react'; +import { + DataGridPro, + GridDataSource, + GridGetRowsParams, + GridToolbar, +} from '@mui/x-data-grid-pro'; +import { useMockServer } from '@mui/x-data-grid-generator'; + +function ServerSideLazyLoading() { + const { columns, fetchRows } = useMockServer( + { rowLength: 120 }, + { useCursorPagination: false, minDelay: 300, maxDelay: 800 }, + ); + + const dataSource: GridDataSource = React.useMemo( + () => ({ + getRows: async (params: GridGetRowsParams) => { + const urlParams = new URLSearchParams({ + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + firstRowToRender: `${params.start}`, + lastRowToRender: `${params.end}`, + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + + return { + rows: getRowsResponse.rows, + }; + }, + }), + [fetchRows], + ); + + return ( +
+ +
+ ); +} + +export default ServerSideLazyLoading; diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoading.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.js similarity index 100% rename from docs/data/data-grid/server-side-data/ServerSideLazyLoading.js rename to docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.js diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoading.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.tsx similarity index 100% rename from docs/data/data-grid/server-side-data/ServerSideLazyLoading.tsx rename to docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.tsx diff --git a/docs/data/data-grid/server-side-data/lazy-loading.md b/docs/data/data-grid/server-side-data/lazy-loading.md index ed2616983fc4b..107b9ce19af89 100644 --- a/docs/data/data-grid/server-side-data/lazy-loading.md +++ b/docs/data/data-grid/server-side-data/lazy-loading.md @@ -6,4 +6,14 @@ title: React Data Grid - Server-side lazy loading

Row lazy-loading with server-side data source.

-{{"demo": "ServerSideLazyLoading.js", "bg": "inline"}} +## Viewport loading mode + +The viewport loading mode is a lazy loading mode that loads new rows based on the viewport. It loads new rows in page size chunks as the user scrolls through the data grid and reveals empty rows. + +{{"demo": "ServerSideLazyLoadingViewport.js", "bg": "inline"}} + +## Infinite loading mode + +The infinite loading mode is a lazy loading mode that loads new rows when the scroll reaches the bottom of the viewport area. + +{{"demo": "ServerSideLazyLoadingInfinite.js", "bg": "inline"}} diff --git a/docs/data/pages.ts b/docs/data/pages.ts index 6178f3eab502c..21f40bd7b3323 100644 --- a/docs/data/pages.ts +++ b/docs/data/pages.ts @@ -115,6 +115,7 @@ const pages: MuiPage[] = [ { pathname: '/x/react-data-grid/server-side-data/lazy-loading', plan: 'pro', + newFeature: true, }, { pathname: '/x/react-data-grid/aggregation', plan: 'premium' }, { pathname: '/x/react-data-grid/pivoting', plan: 'premium', planned: true }, @@ -122,10 +123,8 @@ const pages: MuiPage[] = [ { pathname: '/x/react-data-grid/clipboard', title: 'Copy and paste' }, { pathname: '/x/react-data-grid/scrolling' }, { - pathname: '/x/react-data-grid/list-view', - title: 'List view', - plan: 'pro', - unstable: true, + pathname: '/x/react-data-grid/server-side-data/row-grouping', + plan: 'premium', }, { pathname: '/x/react-data-grid/server-side-data-group', From e12334aedf403e9de1a813a9892615639ceac97f Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Fri, 6 Sep 2024 13:06:26 +0200 Subject: [PATCH 031/121] Update docs and API. Add missing types --- .../ServerSideLazyLoadingInfinite.js | 4 ++-- .../ServerSideLazyLoadingInfinite.tsx | 4 ++-- .../ServerSideLazyLoadingViewport.js | 4 ++-- .../ServerSideLazyLoadingViewport.tsx | 4 ++-- .../server-side-data/infinite-loading.md | 15 --------------- .../src/hooks/features/dataSource/cache.ts | 4 ++-- scripts/x-data-grid-premium.exports.json | 1 + scripts/x-data-grid-pro.exports.json | 1 + 8 files changed, 12 insertions(+), 25 deletions(-) delete mode 100644 docs/data/data-grid/server-side-data/infinite-loading.md diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js index 9b9087a344875..05434bfbec683 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js @@ -2,7 +2,7 @@ import * as React from 'react'; import { DataGridPro, GridToolbar } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; -function ServerSideLazyLoading() { +function ServerSideLazyLoadingInfinite() { const { columns, fetchRows } = useMockServer( { rowLength: 120 }, { useCursorPagination: false, minDelay: 300, maxDelay: 800 }, @@ -42,4 +42,4 @@ function ServerSideLazyLoading() { ); } -export default ServerSideLazyLoading; +export default ServerSideLazyLoadingInfinite; diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.tsx index 922a16e5a297e..0cc3708f0daff 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.tsx @@ -7,7 +7,7 @@ import { } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; -function ServerSideLazyLoading() { +function ServerSideLazyLoadingInfinite() { const { columns, fetchRows } = useMockServer( { rowLength: 120 }, { useCursorPagination: false, minDelay: 300, maxDelay: 800 }, @@ -47,4 +47,4 @@ function ServerSideLazyLoading() { ); } -export default ServerSideLazyLoading; +export default ServerSideLazyLoadingInfinite; diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.js index 576f647a5a021..f3995b4be272b 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.js @@ -2,7 +2,7 @@ import * as React from 'react'; import { DataGridPro, GridToolbar } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; -function ServerSideLazyLoading() { +function ServerSideLazyLoadingViewport() { const { columns, fetchRows } = useMockServer( { rowLength: 120 }, { useCursorPagination: false, minDelay: 300, maxDelay: 800 }, @@ -43,4 +43,4 @@ function ServerSideLazyLoading() { ); } -export default ServerSideLazyLoading; +export default ServerSideLazyLoadingViewport; diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.tsx index b0001d369ff4a..e45785b76a36c 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.tsx @@ -7,7 +7,7 @@ import { } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; -function ServerSideLazyLoading() { +function ServerSideLazyLoadingViewport() { const { columns, fetchRows } = useMockServer( { rowLength: 120 }, { useCursorPagination: false, minDelay: 300, maxDelay: 800 }, @@ -48,4 +48,4 @@ function ServerSideLazyLoading() { ); } -export default ServerSideLazyLoading; +export default ServerSideLazyLoadingViewport; diff --git a/docs/data/data-grid/server-side-data/infinite-loading.md b/docs/data/data-grid/server-side-data/infinite-loading.md deleted file mode 100644 index ae4b15c5dffb6..0000000000000 --- a/docs/data/data-grid/server-side-data/infinite-loading.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: React Data Grid - Server-side infinite loading ---- - -# Data Grid - Server-side infinite loading [](/x/introduction/licensing/#pro-plan 'Pro plan')🚧 - -

Row infinite loading with server-side data source.

- -:::warning -This feature isn't implemented yet. It's coming. - -đź‘Ť Upvote [issue #10858](https://github.com/mui/mui-x/issues/10858) if you want to see it land faster. - -Don't hesitate to leave a comment on the same issue to influence what gets built. Especially if you already have a use case for this component, or if you are facing a pain point with the [current solution](https://mui.com/x/react-data-grid/row-updates/#infinite-loading). -::: diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts index 59e2581a0c71d..ada0a066a10df 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts @@ -47,7 +47,7 @@ export class GridDataSourceCacheDefault { } // split the range into chunks - const chunkRanges = []; + const chunkRanges: { startIndex: number; endIndex: number }[] = []; for (let i = params.start; i < params.end; i += this.chunkSize) { const endIndex = Math.min(i + this.chunkSize - 1, params.end); chunkRanges.push({ startIndex: i, endIndex }); @@ -103,7 +103,7 @@ export class GridDataSourceCacheDefault { return undefined; } - const cachedResponses = []; + const cachedResponses: (GridGetRowsResponse | null)[] = []; for (let i = startChunk; i <= endChunk; i += 1) { const keyString = getKey({ ...key, start: chunks[i].startIndex, end: chunks[i].endIndex }); diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 9f0316969cd44..4e6974be95b5f 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -255,6 +255,7 @@ { "name": "GridDataSourceApiBase", "kind": "Interface" }, { "name": "GridDataSourceCache", "kind": "Interface" }, { "name": "GridDataSourceCacheDefault", "kind": "Class" }, + { "name": "GridDataSourceCacheDefaultConfig", "kind": "TypeAlias" }, { "name": "GridDataSourceGroupNode", "kind": "Interface" }, { "name": "GridDataSourcePrivateApi", "kind": "Interface" }, { "name": "GridDataSourceState", "kind": "Interface" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index 33ec9624fe2c5..270fc6d38d597 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -228,6 +228,7 @@ { "name": "GridDataSourceApiBase", "kind": "Interface" }, { "name": "GridDataSourceCache", "kind": "Interface" }, { "name": "GridDataSourceCacheDefault", "kind": "Class" }, + { "name": "GridDataSourceCacheDefaultConfig", "kind": "TypeAlias" }, { "name": "GridDataSourceGroupNode", "kind": "Interface" }, { "name": "GridDataSourcePrivateApi", "kind": "Interface" }, { "name": "GridDataSourceState", "kind": "Interface" }, From 9a443e9363e98dfa61563e65875db163f7e77d6e Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Fri, 6 Sep 2024 13:14:41 +0200 Subject: [PATCH 032/121] Cleanup --- .../x/react-data-grid/server-side-data/infinite-loading.js | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 docs/pages/x/react-data-grid/server-side-data/infinite-loading.js diff --git a/docs/pages/x/react-data-grid/server-side-data/infinite-loading.js b/docs/pages/x/react-data-grid/server-side-data/infinite-loading.js deleted file mode 100644 index 8392e3a0416fe..0000000000000 --- a/docs/pages/x/react-data-grid/server-side-data/infinite-loading.js +++ /dev/null @@ -1,7 +0,0 @@ -import * as React from 'react'; -import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; -import * as pageProps from 'docsx/data/data-grid/server-side-data/infinite-loading.md?muiMarkdown'; - -export default function Page() { - return ; -} From 391eb6c724363ed4e9c1b760baa3f64d71aad827 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 12 Sep 2024 15:31:39 +0200 Subject: [PATCH 033/121] Change the way current row count is retrieved to support controlled row count updates. Handle switch from infinite to lazy loading --- .../useGridDataSourceLazyLoader.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) 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 53f8ce842ee0a..ec6ecff75cb71 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 @@ -71,13 +71,17 @@ export const useGridDataSourceLazyLoader = ( const rootGroupChildren = [...rootGroup.children]; const pageRowCount = privateApiRef.current.state.pagination.rowCount; - const rowCount = privateApiRef.current.getRowsCount(); + const rootChildrenCount = rootGroupChildren.length; - if (pageRowCount === undefined || rowCount >= pageRowCount) { + if ( + pageRowCount === undefined || + rootChildrenCount === 0 || + rootChildrenCount >= pageRowCount + ) { return; } - for (let i = 0; i < pageRowCount - rowCount; i += 1) { + for (let i = 0; i < pageRowCount - rootChildrenCount; i += 1) { const skeletonId = getSkeletonRowId(i); rootGroupChildren.push(skeletonId); @@ -286,6 +290,7 @@ export const useGridDataSourceLazyLoader = ( ); useGridApiEventHandler(privateApiRef, 'rowsFetched', handleDataUpdate); + useGridApiEventHandler(privateApiRef, 'rowCountChange', handleDataUpdate); useGridApiEventHandler(privateApiRef, 'scrollPositionChange', handleScrolling); useGridApiEventHandler( privateApiRef, From 307f932c8fc3132f2387bbe6acdf0c41c158b246 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 19 Sep 2024 12:39:05 +0200 Subject: [PATCH 034/121] Get current rows from the state instead of prop when data source is used --- .../x-data-grid/src/hooks/features/rows/useGridRows.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts b/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts index 7dda6304335c1..4e4653f9a172b 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts @@ -623,8 +623,11 @@ export const useGridRows = ( lastRowCount.current = props.rowCount; } + const currentRows = props.unstable_dataSource + ? Array.from(apiRef.current.getRowModels().values()) + : props.rows; const areNewRowsAlreadyInState = - apiRef.current.caches.rows.rowsBeforePartialUpdates === props.rows; + apiRef.current.caches.rows.rowsBeforePartialUpdates === currentRows; const isNewLoadingAlreadyInState = apiRef.current.caches.rows.loadingPropBeforePartialUpdates === props.loading; const isNewRowCountAlreadyInState = @@ -659,10 +662,10 @@ export const useGridRows = ( } } - logger.debug(`Updating all rows, new length ${props.rows?.length}`); + logger.debug(`Updating all rows, new length ${currentRows?.length}`); throttledRowsChange({ cache: createRowsInternalCache({ - rows: props.rows, + rows: currentRows, getRowId: props.getRowId, loading: props.loading, rowCount: props.rowCount, @@ -674,6 +677,7 @@ export const useGridRows = ( props.rowCount, props.getRowId, props.loading, + props.unstable_dataSource, logger, throttledRowsChange, apiRef, From 2c9f9be479ee8c4660bc270a5add5f7a4d193144 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 19 Sep 2024 16:08:28 +0200 Subject: [PATCH 035/121] Make sure row count is not undefined --- .../hooks/features/dataSource/useGridDataSource.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index 6446426e9e2b0..40bcde1302b71 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -158,9 +158,9 @@ export const useGridDataSource = ( try { const getRowsResponse = await getRows(fetchParams); apiRef.current.unstable_dataSource.cache.set(fetchParams, getRowsResponse); - if (getRowsResponse.rowCount && getRowsResponse.rowCount >= 0) { - apiRef.current.setRowCount(getRowsResponse.rowCount); - } + apiRef.current.setRowCount( + getRowsResponse.rowCount === undefined ? -1 : getRowsResponse.rowCount, + ); if (isLazyLoaded) { apiRef.current.unstable_replaceRows(startingIndex, getRowsResponse.rows); } else { @@ -249,9 +249,9 @@ export const useGridDataSource = ( } nestedDataManager.setRequestSettled(id); apiRef.current.unstable_dataSource.cache.set(fetchParams, getRowsResponse); - if (getRowsResponse.rowCount && getRowsResponse.rowCount >= 0) { - apiRef.current.setRowCount(getRowsResponse.rowCount); - } + apiRef.current.setRowCount( + getRowsResponse.rowCount === undefined ? -1 : getRowsResponse.rowCount, + ); apiRef.current.updateServerRows(getRowsResponse.rows, rowNode.path); apiRef.current.setRowChildrenExpansion(id, true); } catch (error) { From 35e44d627e1915a22796b359599c77b41684d110 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 19 Sep 2024 22:19:40 +0200 Subject: [PATCH 036/121] Support switching between lazy loading and infinite loading mode --- .../useGridDataSourceLazyLoader.ts | 128 +++++++++++++----- 1 file changed, 94 insertions(+), 34 deletions(-) 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 ec6ecff75cb71..1b362cda8e06b 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 @@ -54,8 +54,8 @@ export const useGridDataSourceLazyLoader = ( const renderContext = useGridSelector(privateApiRef, gridRenderContextSelector); const renderedRowsIntervalCache = React.useRef(INTERVAL_CACHE_INITIAL_STATE); const previousLastRowIndex = React.useRef(0); + const loadingTrigger = React.useRef(null); const isDisabled = !props.unstable_dataSource || props.lazyLoading !== true; - const [loadingMode, setLoadingMode] = React.useState(null); const heights = React.useMemo( () => ({ @@ -65,7 +65,49 @@ export const useGridDataSourceLazyLoader = ( [dimensions.viewportInnerSize.height, dimensions.contentSize.height], ); - const addSkeletonRows = React.useCallback(() => { + const resetGrid = React.useCallback(() => { + privateApiRef.current.setRows([]); + previousLastRowIndex.current = 0; + const getRowsParams: GridGetRowsParams = { + start: 0, + end: paginationModel.pageSize - 1, + sortModel, + filterModel, + }; + + privateApiRef.current.publishEvent('getRows', getRowsParams); + }, [privateApiRef, sortModel, filterModel, paginationModel.pageSize]); + + const ensureValidRowCount = React.useCallback( + (previousLoadingTrigger: LoadingTrigger, newLoadingTrigger: LoadingTrigger) => { + // switching from lazy loading to infinite loading should always reset the grid + // since there is no guarantee that the new data will be placed correctly + // there might be some skeleton rows in between the data or the data has changed (row count became unknown) + if ( + previousLoadingTrigger === LoadingTrigger.VIEWPORT && + newLoadingTrigger === LoadingTrigger.SCROLL_END + ) { + resetGrid(); + return; + } + + // switching from infinite loading to lazy loading should reset the grid only if the known row count + // is smaller than the amount of rows rendered + const tree = privateApiRef.current.state.rows.tree; + const rootGroup = tree[GRID_ROOT_GROUP_ID] as GridGroupNode; + const rootGroupChildren = [...rootGroup.children]; + + const pageRowCount = privateApiRef.current.state.pagination.rowCount; + const rootChildrenCount = rootGroupChildren.length; + + if (rootChildrenCount > pageRowCount) { + resetGrid(); + } + }, + [privateApiRef, resetGrid], + ); + + const adjustGridRows = React.useCallback(() => { const tree = privateApiRef.current.state.rows.tree; const rootGroup = tree[GRID_ROOT_GROUP_ID] as GridGroupNode; const rootGroupChildren = [...rootGroup.children]; @@ -73,14 +115,12 @@ export const useGridDataSourceLazyLoader = ( const pageRowCount = privateApiRef.current.state.pagination.rowCount; const rootChildrenCount = rootGroupChildren.length; - if ( - pageRowCount === undefined || - rootChildrenCount === 0 || - rootChildrenCount >= pageRowCount - ) { + // if row count cannot be determined or all rows are there, do nothing + if (pageRowCount === -1 || rootChildrenCount === 0 || rootChildrenCount === pageRowCount) { return; } + // fill the grid with skeleton rows for (let i = 0; i < pageRowCount - rootChildrenCount; i += 1) { const skeletonId = getSkeletonRowId(i); @@ -110,11 +150,51 @@ export const useGridDataSourceLazyLoader = ( ); }, [privateApiRef]); + const updateLoadingTrigger = React.useCallback( + (rowCount: number) => { + const newLoadingTrigger = + rowCount === -1 ? LoadingTrigger.SCROLL_END : LoadingTrigger.VIEWPORT; + + if (loadingTrigger.current !== newLoadingTrigger) { + loadingTrigger.current = newLoadingTrigger; + } + + if (loadingTrigger.current !== null) { + ensureValidRowCount(loadingTrigger.current, newLoadingTrigger); + } + }, + [ensureValidRowCount], + ); + + const handleDataUpdate = React.useCallback(() => { + if (isDisabled) { + return; + } + + if (loadingTrigger.current === null) { + updateLoadingTrigger(privateApiRef.current.state.pagination.rowCount); + } + + adjustGridRows(); + privateApiRef.current.state.rows.loading = false; + privateApiRef.current.requestPipeProcessorsApplication('hydrateRows'); + }, [privateApiRef, isDisabled, updateLoadingTrigger, adjustGridRows]); + + const handleRowCountChange = React.useCallback(() => { + if (isDisabled || loadingTrigger.current === null) { + return; + } + + updateLoadingTrigger(privateApiRef.current.state.pagination.rowCount); + adjustGridRows(); + privateApiRef.current.requestPipeProcessorsApplication('hydrateRows'); + }, [privateApiRef, isDisabled, updateLoadingTrigger, adjustGridRows]); + const handleScrolling: GridEventListener<'scrollPositionChange'> = React.useCallback( (newScrollPosition) => { if ( isDisabled || - loadingMode !== LoadingTrigger.SCROLL_END || + loadingTrigger.current !== LoadingTrigger.SCROLL_END || previousLastRowIndex.current >= renderContext.lastRowIndex ) { return; @@ -141,7 +221,6 @@ export const useGridDataSourceLazyLoader = ( privateApiRef, props.scrollEndThreshold, isDisabled, - loadingMode, sortModel, filterModel, heights, @@ -150,29 +229,11 @@ export const useGridDataSourceLazyLoader = ( ], ); - const handleDataUpdate = React.useCallback(() => { - if (isDisabled) { - return; - } - - const pageRowCount = privateApiRef.current.state.pagination.rowCount; - const newLoadingMode = - pageRowCount === undefined || pageRowCount === -1 - ? LoadingTrigger.SCROLL_END - : LoadingTrigger.VIEWPORT; - if (loadingMode !== newLoadingMode) { - setLoadingMode(newLoadingMode); - } - - addSkeletonRows(); - privateApiRef.current.requestPipeProcessorsApplication('hydrateRows'); - }, [privateApiRef, isDisabled, loadingMode, addSkeletonRows]); - const handleRenderedRowsIntervalChange = React.useCallback< GridEventListener<'renderedRowsIntervalChange'> >( (params) => { - if (isDisabled) { + if (isDisabled || loadingTrigger.current !== LoadingTrigger.VIEWPORT) { return; } @@ -234,13 +295,13 @@ export const useGridDataSourceLazyLoader = ( privateApiRef.current.setRows([]); previousLastRowIndex.current = 0; - if (loadingMode === LoadingTrigger.VIEWPORT) { + if (loadingTrigger.current === LoadingTrigger.VIEWPORT) { // replace all rows with skeletons to maintain the same scroll position - addSkeletonRows(); + adjustGridRows(); } const rangeParams = - loadingMode === LoadingTrigger.VIEWPORT + loadingTrigger.current === LoadingTrigger.VIEWPORT ? { start: renderContext.firstRowIndex, end: renderContext.lastRowIndex, @@ -261,11 +322,10 @@ export const useGridDataSourceLazyLoader = ( [ privateApiRef, isDisabled, - loadingMode, filterModel, paginationModel.pageSize, renderContext, - addSkeletonRows, + adjustGridRows, ], ); @@ -290,7 +350,7 @@ export const useGridDataSourceLazyLoader = ( ); useGridApiEventHandler(privateApiRef, 'rowsFetched', handleDataUpdate); - useGridApiEventHandler(privateApiRef, 'rowCountChange', handleDataUpdate); + useGridApiEventHandler(privateApiRef, 'rowCountChange', handleRowCountChange); useGridApiEventHandler(privateApiRef, 'scrollPositionChange', handleScrolling); useGridApiEventHandler( privateApiRef, From 7073989fc8b69529360bf405e9a3dbbdc78afcba Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Mon, 23 Sep 2024 12:08:42 +0200 Subject: [PATCH 037/121] Extend fetchRows API to enable request retries. Keep compatibility with the old signature --- .../GridDataSourceTreeDataGroupingCell.tsx | 2 +- .../hooks/features/dataSource/interfaces.ts | 14 ++++-- .../features/dataSource/useGridDataSource.ts | 50 +++++++++++++------ .../features/treeData/useGridTreeData.tsx | 2 +- scripts/x-data-grid-premium.exports.json | 1 + scripts/x-data-grid-pro.exports.json | 1 + 6 files changed, 48 insertions(+), 22 deletions(-) diff --git a/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx b/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx index d5ee7aa79ab5f..0dd7ce2473530 100644 --- a/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx +++ b/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx @@ -60,7 +60,7 @@ function GridTreeDataGroupingCellIcon(props: GridTreeDataGroupingCellIconProps) const handleClick = (event: React.MouseEvent) => { if (!rowNode.childrenExpanded) { // always fetch/get from cache the children when the node is expanded - apiRef.current.unstable_dataSource.fetchRows(id); + apiRef.current.unstable_dataSource.fetchRows({ parentId: id }); } else { apiRef.current.setRowChildrenExpansion(id, !rowNode.childrenExpanded); } diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/interfaces.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/interfaces.ts index 90bfc4ed39de2..073c324eaa704 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/interfaces.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/interfaces.ts @@ -6,6 +6,12 @@ export interface GridDataSourceState { errors: Record; } +export interface FetchRowsOptions { + parentId?: GridRowId; + start?: number | string; + end?: number; +} + /** * The base data source API interface that is available in the grid [[apiRef]]. */ @@ -23,11 +29,11 @@ export interface GridDataSourceApiBase { */ setChildrenFetchError: (parentId: GridRowId, error: Error | null) => void; /** - * Fetches the rows from the server for a given `parentId`. - * If no `parentId` is provided, it fetches the root rows. - * @param {string} parentId The id of the group to be fetched. + * Fetches the rows from the server for with given options. + * If no `parentId` option is provided, it fetches the root rows. + * @param {FetchRowsOptions} options Options that allow setting the specific request params. */ - fetchRows: (parentId?: GridRowId) => void; + fetchRows: (options?: GridRowId | FetchRowsOptions) => void; /** * The data source cache object. */ diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index 40bcde1302b71..0743be947c33d 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -8,6 +8,7 @@ import { useGridSelector, GridRowId, gridPaginationModelSelector, + gridFilteredSortedRowIdsSelector, } from '@mui/x-data-grid'; import { GridGetRowsParams, @@ -17,7 +18,12 @@ import { import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; import { gridGetRowsParamsSelector, gridDataSourceErrorsSelector } from './gridDataSourceSelector'; -import { GridDataSourceApi, GridDataSourceApiBase, GridDataSourcePrivateApi } from './interfaces'; +import { + FetchRowsOptions, + GridDataSourceApi, + GridDataSourceApiBase, + GridDataSourcePrivateApi, +} from './interfaces'; import { NestedDataManager, RequestStatus, runIf } from './utils'; import { GridDataSourceCache } from '../../../models'; import { GridDataSourceCacheDefault, GridDataSourceCacheDefaultConfig } from './cache'; @@ -70,11 +76,10 @@ export const useGridDataSource = ( ).current; const paginationModel = useGridSelector(apiRef, gridPaginationModelSelector); const groupsToAutoFetch = useGridSelector(apiRef, gridRowGroupsToFetchSelector); + const filteredSortedRowIds = useGridSelector(apiRef, gridFilteredSortedRowIdsSelector); const scheduledGroups = React.useRef(0); const isLazyLoaded = !!props.unstable_dataSource && props.lazyLoading; - const rowFetchSlice = React.useRef({}); - const onError = props.unstable_onDataSourceError; const cacheChunkSize = React.useMemo(() => { @@ -108,12 +113,25 @@ export const useGridDataSource = ( ); const fetchRows = React.useCallback( - async (parentId?: GridRowId) => { + async (options?: GridRowId | FetchRowsOptions) => { const getRows = props.unstable_dataSource?.getRows; if (!getRows) { return; } + const hasDeprecatedArgument = typeof options === 'string' || typeof options === 'number'; + if (hasDeprecatedArgument) { + console.warn( + '`fetchRows` argument should be an options object (`FetchRowsOptions`). `GridRowId` argument is deprecated.', + ); + } + + const parentId = hasDeprecatedArgument || !options ? options : options.parentId; + const fetchParamsOverride = + hasDeprecatedArgument || options?.start === undefined || options?.end === undefined + ? {} + : { start: options.start, end: options.end }; + if (parentId) { nestedDataManager.queue([parentId]); return; @@ -129,16 +147,17 @@ export const useGridDataSource = ( const fetchParams = { ...gridGetRowsParamsSelector(apiRef), ...apiRef.current.unstable_applyPipeProcessors('getRowsParams', {}), - ...rowFetchSlice.current, + ...fetchParamsOverride, }; - const startingIndex = fetchParams.start; + const startingIndex = + typeof fetchParams.start === 'string' + ? Math.max(filteredSortedRowIds.indexOf(fetchParams.start), 0) + : fetchParams.start; const cachedData = apiRef.current.unstable_dataSource.cache.get(fetchParams); if (cachedData !== undefined) { const rows = cachedData.rows; - if (cachedData.rowCount && cachedData.rowCount >= 0) { - apiRef.current.setRowCount(cachedData.rowCount); - } + apiRef.current.setRowCount(cachedData.rowCount === undefined ? -1 : cachedData.rowCount); if (isLazyLoaded) { apiRef.current.unstable_replaceRows(startingIndex, rows); } else { @@ -169,7 +188,9 @@ export const useGridDataSource = ( apiRef.current.setLoading(false); apiRef.current.publishEvent('rowsFetched'); } catch (error) { - apiRef.current.setRows([]); + if (!isLazyLoaded) { + apiRef.current.setRows([]); + } apiRef.current.setLoading(false); onError?.(error as Error, fetchParams); } @@ -179,15 +200,14 @@ export const useGridDataSource = ( apiRef, props.unstable_dataSource?.getRows, isLazyLoaded, + filteredSortedRowIds, onError, - rowFetchSlice, ], ); const fetchRowBatch = React.useCallback( (fetchParams: GridGetRowsParams) => { - rowFetchSlice.current = adjustRowParams(fetchParams); - return fetchRows(); + return fetchRows(adjustRowParams(fetchParams)); }, [adjustRowParams, fetchRows], ); @@ -223,9 +243,7 @@ export const useGridDataSource = ( const rows = cachedData.rows; nestedDataManager.setRequestSettled(id); apiRef.current.updateServerRows(rows, rowNode.path); - if (cachedData.rowCount && cachedData.rowCount >= 0) { - apiRef.current.setRowCount(cachedData.rowCount); - } + apiRef.current.setRowCount(cachedData.rowCount === undefined ? -1 : cachedData.rowCount); apiRef.current.setRowChildrenExpansion(id, true); apiRef.current.unstable_dataSource.setChildrenLoading(id, false); return; diff --git a/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeData.tsx b/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeData.tsx index b98528fb36935..216df591f6e52 100644 --- a/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeData.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeData.tsx @@ -25,7 +25,7 @@ export const useGridTreeData = ( } if (props.unstable_dataSource && !params.rowNode.childrenExpanded) { - apiRef.current.unstable_dataSource.fetchRows(params.id); + apiRef.current.unstable_dataSource.fetchRows({ parentId: params.id }); return; } diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 4e6974be95b5f..7b3b16d2c4fe0 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -40,6 +40,7 @@ { "name": "ElementSize", "kind": "Interface" }, { "name": "EMPTY_PINNED_COLUMN_FIELDS", "kind": "Variable" }, { "name": "EMPTY_RENDER_CONTEXT", "kind": "Variable" }, + { "name": "FetchRowsOptions", "kind": "Interface" }, { "name": "FilterColumnsArgs", "kind": "Interface" }, { "name": "FilterPanelPropsOverrides", "kind": "Interface" }, { "name": "FocusElement", "kind": "Interface" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index 270fc6d38d597..f3341bbdf6adf 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -39,6 +39,7 @@ { "name": "ElementSize", "kind": "Interface" }, { "name": "EMPTY_PINNED_COLUMN_FIELDS", "kind": "Variable" }, { "name": "EMPTY_RENDER_CONTEXT", "kind": "Variable" }, + { "name": "FetchRowsOptions", "kind": "Interface" }, { "name": "FilterColumnsArgs", "kind": "Interface" }, { "name": "FilterPanelPropsOverrides", "kind": "Interface" }, { "name": "FocusElement", "kind": "Interface" }, From 7a47b41e16092d47458d9aaf98b1859e374ba1ec Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Tue, 1 Oct 2024 10:42:10 +0200 Subject: [PATCH 038/121] Pagination params adjustment inside lazyLoading hook. Use the same api for row slice fetching --- .../features/dataSource/useGridDataSource.ts | 31 ++-------------- .../useGridDataSourceLazyLoader.ts | 35 ++++++++++++++++--- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index 0743be947c33d..a472440d2ee2b 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -10,11 +10,7 @@ import { gridPaginationModelSelector, gridFilteredSortedRowIdsSelector, } from '@mui/x-data-grid'; -import { - GridGetRowsParams, - gridRowGroupsToFetchSelector, - GridStateInitializer, -} from '@mui/x-data-grid/internals'; +import { gridRowGroupsToFetchSelector, GridStateInitializer } from '@mui/x-data-grid/internals'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; import { gridGetRowsParamsSelector, gridDataSourceErrorsSelector } from './gridDataSourceSelector'; @@ -96,22 +92,6 @@ export const useGridDataSource = ( }), ); - // Adjust the render context range to fit the pagination model's page size - // First row index should be decreased to the start of the page, end row index should be increased to the end of the page - const adjustRowParams = React.useCallback( - (params: Pick) => { - if (typeof params.start !== 'number') { - return params; - } - - return { - start: params.start - (params.start % paginationModel.pageSize), - end: params.end + paginationModel.pageSize - (params.end % paginationModel.pageSize) - 1, - }; - }, - [paginationModel], - ); - const fetchRows = React.useCallback( async (options?: GridRowId | FetchRowsOptions) => { const getRows = props.unstable_dataSource?.getRows; @@ -205,13 +185,6 @@ export const useGridDataSource = ( ], ); - const fetchRowBatch = React.useCallback( - (fetchParams: GridGetRowsParams) => { - return fetchRows(adjustRowParams(fetchParams)); - }, - [adjustRowParams, fetchRows], - ); - const fetchRowChildren = React.useCallback( async (id) => { const pipedParams = apiRef.current.unstable_applyPipeProcessors('getRowsParams', {}); @@ -370,7 +343,7 @@ export const useGridDataSource = ( 'paginationModelChange', runIf(props.paginationMode === 'server' && !isLazyLoaded, fetchRows), ); - useGridApiEventHandler(apiRef, 'getRows', runIf(isLazyLoaded, fetchRowBatch)); + useGridApiEventHandler(apiRef, 'getRows', runIf(isLazyLoaded, fetchRows)); const isFirstRender = React.useRef(true); React.useEffect(() => { 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 1b362cda8e06b..5bc9c2ee826a5 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 @@ -65,6 +65,23 @@ export const useGridDataSourceLazyLoader = ( [dimensions.viewportInnerSize.height, dimensions.contentSize.height], ); + // Adjust the render context range to fit the pagination model's page size + // First row index should be decreased to the start of the page, end row index should be increased to the end of the page + const adjustRowParams = React.useCallback( + (params: GridGetRowsParams) => { + if (typeof params.start !== 'number') { + return params; + } + + return { + ...params, + start: params.start - (params.start % paginationModel.pageSize), + end: params.end + paginationModel.pageSize - (params.end % paginationModel.pageSize) - 1, + }; + }, + [paginationModel], + ); + const resetGrid = React.useCallback(() => { privateApiRef.current.setRows([]); previousLastRowIndex.current = 0; @@ -214,7 +231,7 @@ export const useGridDataSourceLazyLoader = ( }; privateApiRef.current.setLoading(true); - privateApiRef.current.publishEvent('getRows', getRowsParams); + privateApiRef.current.publishEvent('getRows', adjustRowParams(getRowsParams)); } }, [ @@ -226,6 +243,7 @@ export const useGridDataSourceLazyLoader = ( heights, paginationModel.pageSize, renderContext.lastRowIndex, + adjustRowParams, ], ); @@ -277,9 +295,17 @@ export const useGridDataSourceLazyLoader = ( getRowsParams.start = skeletonRowsSection.firstRowIndex; getRowsParams.end = skeletonRowsSection.lastRowIndex; - privateApiRef.current.publishEvent('getRows', getRowsParams); + privateApiRef.current.publishEvent('getRows', adjustRowParams(getRowsParams)); }, - [privateApiRef, isDisabled, props.pagination, props.paginationMode, sortModel, filterModel], + [ + privateApiRef, + isDisabled, + props.pagination, + props.paginationMode, + sortModel, + filterModel, + adjustRowParams, + ], ); const throttledHandleRenderedRowsIntervalChange = React.useMemo( @@ -317,7 +343,7 @@ export const useGridDataSourceLazyLoader = ( filterModel, }; - privateApiRef.current.publishEvent('getRows', getRowsParams); + privateApiRef.current.publishEvent('getRows', adjustRowParams(getRowsParams)); }, [ privateApiRef, @@ -326,6 +352,7 @@ export const useGridDataSourceLazyLoader = ( paginationModel.pageSize, renderContext, adjustGridRows, + adjustRowParams, ], ); From c02606f8df0bc9725839320117fe28859c75d46e Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Tue, 1 Oct 2024 11:07:05 +0200 Subject: [PATCH 039/121] Update prop validation --- packages/x-data-grid-pro/src/internals/propValidation.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/x-data-grid-pro/src/internals/propValidation.ts b/packages/x-data-grid-pro/src/internals/propValidation.ts index e4cf9b6572b42..06e607b2b3de1 100644 --- a/packages/x-data-grid-pro/src/internals/propValidation.ts +++ b/packages/x-data-grid-pro/src/internals/propValidation.ts @@ -33,8 +33,8 @@ export const propValidatorsDataGridPro: PropValidator undefined, (props) => (props.signature !== GridSignature.DataGrid && - props.rowsLoadingMode === 'server' && + (props.rowsLoadingMode === 'server' || props.onRowsScrollEnd) && props.lazyLoading && - 'MUI X: Usage of the client side lazy loading (`rowsLoadingMode="server"`) cannot be used together with server side lazy loading `lazyLoading="true"`.') || + 'MUI X: Usage of the client side lazy loading (`rowsLoadingMode="server"` or `onRowsScrollEnd=...`) cannot be used together with server side lazy loading `lazyLoading="true"`.') || undefined, ]; From 0ae8b89b271b53dc7b3b93648f1ff0ef8fd01378 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 2 Oct 2024 13:03:41 +0200 Subject: [PATCH 040/121] Clear the cache when grid is reset --- .../features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts | 1 + 1 file changed, 1 insertion(+) 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 5bc9c2ee826a5..c5b2d1ed80a57 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 @@ -84,6 +84,7 @@ export const useGridDataSourceLazyLoader = ( const resetGrid = React.useCallback(() => { privateApiRef.current.setRows([]); + privateApiRef.current.unstable_dataSource.cache.clear(); previousLastRowIndex.current = 0; const getRowsParams: GridGetRowsParams = { start: 0, From decd8488ca3c97cce2bfeb9efa2857c755b5c36a Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 2 Oct 2024 13:28:27 +0200 Subject: [PATCH 041/121] Show loading indicator when grid is reset --- .../features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts | 1 + 1 file changed, 1 insertion(+) 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 c5b2d1ed80a57..9ca464f56f970 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 @@ -84,6 +84,7 @@ export const useGridDataSourceLazyLoader = ( const resetGrid = React.useCallback(() => { privateApiRef.current.setRows([]); + privateApiRef.current.setLoading(true); privateApiRef.current.unstable_dataSource.cache.clear(); previousLastRowIndex.current = 0; const getRowsParams: GridGetRowsParams = { From 274ee2cf65101e2852493a1aa4f873497d5d73cd Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 2 Oct 2024 13:28:59 +0200 Subject: [PATCH 042/121] Update existing examples --- .../server-side-data/ServerSideLazyLoadingInfinite.js | 7 +++---- .../server-side-data/ServerSideLazyLoadingInfinite.tsx | 6 ++---- .../server-side-data/ServerSideLazyLoadingViewport.js | 7 +++---- .../server-side-data/ServerSideLazyLoadingViewport.tsx | 6 ++---- 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js index 05434bfbec683..4dca7fac867d9 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js @@ -1,10 +1,10 @@ import * as React from 'react'; -import { DataGridPro, GridToolbar } from '@mui/x-data-grid-pro'; +import { DataGridPro } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; function ServerSideLazyLoadingInfinite() { const { columns, fetchRows } = useMockServer( - { rowLength: 120 }, + { rowLength: 100 }, { useCursorPagination: false, minDelay: 300, maxDelay: 800 }, ); @@ -34,9 +34,8 @@ function ServerSideLazyLoadingInfinite() { ); diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.tsx index 0cc3708f0daff..ed4d2ea349997 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.tsx @@ -3,13 +3,12 @@ import { DataGridPro, GridDataSource, GridGetRowsParams, - GridToolbar, } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; function ServerSideLazyLoadingInfinite() { const { columns, fetchRows } = useMockServer( - { rowLength: 120 }, + { rowLength: 100 }, { useCursorPagination: false, minDelay: 300, maxDelay: 800 }, ); @@ -39,9 +38,8 @@ function ServerSideLazyLoadingInfinite() { ); diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.js index f3995b4be272b..fa1208d75f4eb 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.js @@ -1,10 +1,10 @@ import * as React from 'react'; -import { DataGridPro, GridToolbar } from '@mui/x-data-grid-pro'; +import { DataGridPro } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; function ServerSideLazyLoadingViewport() { const { columns, fetchRows } = useMockServer( - { rowLength: 120 }, + { rowLength: 100 }, { useCursorPagination: false, minDelay: 300, maxDelay: 800 }, ); @@ -35,9 +35,8 @@ function ServerSideLazyLoadingViewport() { ); diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.tsx index e45785b76a36c..90285c8e4cf73 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.tsx @@ -3,13 +3,12 @@ import { DataGridPro, GridDataSource, GridGetRowsParams, - GridToolbar, } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; function ServerSideLazyLoadingViewport() { const { columns, fetchRows } = useMockServer( - { rowLength: 120 }, + { rowLength: 100 }, { useCursorPagination: false, minDelay: 300, maxDelay: 800 }, ); @@ -40,9 +39,8 @@ function ServerSideLazyLoadingViewport() { ); From d4c07ce4f491c47ca69c9d0ee51964c7f840b553 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 2 Oct 2024 15:46:49 +0200 Subject: [PATCH 043/121] Update docs and add new examples --- .../ServerSideLazyLoadingErrorHandling.js | 97 ++++++++++++++++ .../ServerSideLazyLoadingErrorHandling.tsx | 105 ++++++++++++++++++ .../ServerSideLazyLoadingModeUpdate.js | 83 ++++++++++++++ .../ServerSideLazyLoadingModeUpdate.tsx | 95 ++++++++++++++++ .../server-side-data/lazy-loading.md | 95 +++++++++++++++- 5 files changed, 471 insertions(+), 4 deletions(-) create mode 100644 docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.js create mode 100644 docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.tsx create mode 100644 docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.js create mode 100644 docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.tsx diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.js new file mode 100644 index 0000000000000..b39e34eec14e6 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.js @@ -0,0 +1,97 @@ +import * as React from 'react'; +import { DataGridPro, useGridApiRef, GridToolbar } from '@mui/x-data-grid-pro'; +import Checkbox from '@mui/material/Checkbox'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import Alert from '@mui/material/Alert'; +import Button from '@mui/material/Button'; + +function ErrorAlert({ onClick }) { + return ( + + Retry + + } + > + Could not fetch the data + + ); +} + +function ServerSideLazyLoadingErrorHandling() { + const apiRef = useGridApiRef(); + const [retryParams, setRetryParams] = React.useState(null); + const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); + + const { fetchRows, ...props } = useMockServer( + { rowLength: 100 }, + { useCursorPagination: false, minDelay: 300, maxDelay: 800 }, + shouldRequestsFail, + ); + + const dataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + firstRowToRender: `${params.start}`, + lastRowToRender: `${params.end}`, + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + }), + [fetchRows], + ); + + return ( +
+ setShouldRequestsFail(event.target.checked)} + /> + } + label="Make the requests fail" + /> +
+ {retryParams && ( + { + apiRef.current.unstable_dataSource.fetchRows(retryParams); + setRetryParams(null); + }} + /> + )} + setRetryParams(params)} + lazyLoading + paginationModel={{ page: 0, pageSize: 10 }} + slots={{ toolbar: GridToolbar }} + /> +
+
+ ); +} + +export default ServerSideLazyLoadingErrorHandling; diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.tsx new file mode 100644 index 0000000000000..fde558faf3324 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.tsx @@ -0,0 +1,105 @@ +import * as React from 'react'; +import { + DataGridPro, + useGridApiRef, + GridToolbar, + GridDataSource, + GridGetRowsParams, +} from '@mui/x-data-grid-pro'; +import Checkbox from '@mui/material/Checkbox'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import Alert from '@mui/material/Alert'; +import Button from '@mui/material/Button'; + +function ErrorAlert({ onClick }: { onClick: () => void }) { + return ( + + Retry + + } + > + Could not fetch the data + + ); +} + +function ServerSideLazyLoadingErrorHandling() { + const apiRef = useGridApiRef(); + const [retryParams, setRetryParams] = React.useState( + null, + ); + const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); + + const { fetchRows, ...props } = useMockServer( + { rowLength: 100 }, + { useCursorPagination: false, minDelay: 300, maxDelay: 800 }, + shouldRequestsFail, + ); + + const dataSource: GridDataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + firstRowToRender: `${params.start}`, + lastRowToRender: `${params.end}`, + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + }), + [fetchRows], + ); + + return ( +
+ setShouldRequestsFail(event.target.checked)} + /> + } + label="Make the requests fail" + /> +
+ {retryParams && ( + { + apiRef.current.unstable_dataSource.fetchRows(retryParams); + setRetryParams(null); + }} + /> + )} + setRetryParams(params)} + lazyLoading + paginationModel={{ page: 0, pageSize: 10 }} + slots={{ toolbar: GridToolbar }} + /> +
+
+ ); +} + +export default ServerSideLazyLoadingErrorHandling; diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.js new file mode 100644 index 0000000000000..38e6db495de29 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.js @@ -0,0 +1,83 @@ +import * as React from 'react'; +import { DataGridPro } from '@mui/x-data-grid-pro'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import FormControl from '@mui/material/FormControl'; +import FormLabel from '@mui/material/FormLabel'; +import RadioGroup from '@mui/material/RadioGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Radio from '@mui/material/Radio'; + +function Toolbar(props) { + const { count, setCount } = props; + return ( + + Row count + setCount(Number(event.target.value))} + > + } label="Unknown" /> + } label="40" /> + } label="100" /> + + + ); +} + +function ServerSideLazyLoadingModeUpdate() { + const { columns, fetchRows } = useMockServer( + { rowLength: 100 }, + { useCursorPagination: false, minDelay: 300, maxDelay: 800 }, + ); + + const [rowCount, setRowCount] = React.useState(-1); + + const dataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + firstRowToRender: `${params.start}`, + lastRowToRender: `${params.end}`, + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + + return { + rows: getRowsResponse.rows, + }; + }, + }), + [fetchRows], + ); + + return ( +
+ +
+ ); +} + +export default ServerSideLazyLoadingModeUpdate; diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.tsx new file mode 100644 index 0000000000000..92bcb2a586179 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.tsx @@ -0,0 +1,95 @@ +import * as React from 'react'; +import { + DataGridPro, + GridDataSource, + GridGetRowsParams, + ToolbarPropsOverrides, +} from '@mui/x-data-grid-pro'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import FormControl from '@mui/material/FormControl'; +import FormLabel from '@mui/material/FormLabel'; +import RadioGroup from '@mui/material/RadioGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Radio from '@mui/material/Radio'; + +declare module '@mui/x-data-grid' { + interface ToolbarPropsOverrides { + count: number; + setCount: (count: number) => void; + } +} + +function Toolbar(props: ToolbarPropsOverrides) { + const { count, setCount } = props; + return ( + + Row count + setCount(Number(event.target.value))} + > + } label="Unknown" /> + } label="40" /> + } label="100" /> + + + ); +} + +function ServerSideLazyLoadingModeUpdate() { + const { columns, fetchRows } = useMockServer( + { rowLength: 100 }, + { useCursorPagination: false, minDelay: 300, maxDelay: 800 }, + ); + + const [rowCount, setRowCount] = React.useState(-1); + + const dataSource: GridDataSource = React.useMemo( + () => ({ + getRows: async (params: GridGetRowsParams) => { + const urlParams = new URLSearchParams({ + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + firstRowToRender: `${params.start}`, + lastRowToRender: `${params.end}`, + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + + return { + rows: getRowsResponse.rows, + }; + }, + }), + [fetchRows], + ); + + return ( +
+ +
+ ); +} + +export default ServerSideLazyLoadingModeUpdate; diff --git a/docs/data/data-grid/server-side-data/lazy-loading.md b/docs/data/data-grid/server-side-data/lazy-loading.md index 107b9ce19af89..e293e7d49ece7 100644 --- a/docs/data/data-grid/server-side-data/lazy-loading.md +++ b/docs/data/data-grid/server-side-data/lazy-loading.md @@ -6,14 +6,101 @@ title: React Data Grid - Server-side lazy loading

Row lazy-loading with server-side data source.

-## Viewport loading mode +Lazy Loading changes the way pagination works by removing page controls and loading data dynamically (in a single list) as the user scrolls through the grid. -The viewport loading mode is a lazy loading mode that loads new rows based on the viewport. It loads new rows in page size chunks as the user scrolls through the data grid and reveals empty rows. +It is enabled by adding `lazyLoading` prop in combination with `unstable_dataSource` prop. + +Initially, the first page data is fetched and displayed in the grid. What triggers the loading of next page data depends on the value of the total row count. + +If the total row count is known, the grid will be filled with skeleton rows and will fetch more data if one of the skeleton rows falls into the rendering context. + +If the total row count is unknown, the grid will fetch more data when the user scrolls to the bottom of the grid. This loading strategy is often referred to as **infinite loading**. + +:::info +Row count can be provided either by returning the `rowCount` in the response of the `getRows` method in `unstable_dataSource`, via the `rowCount` prop or by calling [`setRowCount`](/x/api/data-grid/grid-api/#grid-api-prop-setRowCount) API. +::: + +:::warning +Order of precedence for the row count: + +- `rowCount` prop +- `rowCount` returned by the `getRows` method +- row count set using the `setRowCount` API + +This means that, if the row count is set using the API, the value will be overridden once a new value is returned by the `getRows` method, even if it is `undefined`. +::: + +## Viewport loading + +The viewport loading mode is enabled when the row count is known (`rowCount >= 0`). Grid will fetch the first page and add skeleton rows to match the total row count. Other pages are fetched once the user starts scrolling and moves a skeleton row inside the rendering context (index range defined by [Virtualization](/x/react-data-grid/virtualization/)). + +If the user scrolls too fast, the grid loads multiple pages with one request (by adjusting `start` and `end` param) in order to reduce the server load. + +The demo below shows the viewport loading mode. {{"demo": "ServerSideLazyLoadingViewport.js", "bg": "inline"}} -## Infinite loading mode +:::info +The data source demos use a utility function `useMockServer` to simulate the server-side data fetching. +In a real-world scenario, you should replace this with your own server-side data-fetching logic. + +Open info section of the browser console to see the requests being made and the data being fetched in response. +::: + +## Infinite loading + +The infinite loading mode is enabled when the row count is unknown (`-1` or `undefined`). New page is loaded when the scroll reaches the bottom of the viewport area. -The infinite loading mode is a lazy loading mode that loads new rows when the scroll reaches the bottom of the viewport area. +The area which triggers the new request can be changed using `scrollEndThreshold`. + +The demo below shows the infinite loading mode. Page size is set to `15` and the mock server is configured to return a total of `100` rows. Once the response does not contain any new rows, the grid will stop requesting new data. {{"demo": "ServerSideLazyLoadingInfinite.js", "bg": "inline"}} + +## Updating the loading mode + +The grid will dynamically change the loading mode if the total row count gets updated in any of the three ways described above. + +Based on the previous and the new value for the total row count, the following scenarios are possible: + +- **Unknown `rowCount` to known `rowCount`**: If row count is not unknown anymore, the grid will switch to the viewport loading mode. It will check the amount of allready fetched rows and will add skeleton rows to match the total row count. + +- **Known `rowCount` to unknown `rowCount`**: If the row count is updated and set to `-1`, the grid will reset, fetch the first page and set itself in the infinite loading mode. + +- **Known `rowCount` greater than the actual row count**: This can happen either by reducing the value of the row count after more rows were already fetched or if the row count was unknown and the grid in the inifite loading mode already fetched more rows. In this case, the grid will reset, fetch the first page and continue in one of the modes depending on the new value of the `rowCount`. + +:::warning +`rowCount` is expected to be static. Changing its value can cause the grid to reset and the cache to be cleared which leads to poor performance and user experience. +::: + +The demo below serves more as a showcase of the behavior described above and is not representing something you would implement in a real-world scenario. + +{{"demo": "ServerSideLazyLoadingModeUpdate.js", "bg": "inline"}} + +## Nested rows 🚧 + +:::warning +This feature isn't implemented yet. It's coming. + +👍 Upvote [issue #14527](https://github.com/mui/mui-x/issues/14527) if you want to see it land faster. + +Don't hesitate to leave a comment on the same issue to influence what gets built. Especially if you already have a use case for this feature, or if you are facing a pain point with your current solution. +::: + +When completed, it will be possible to use `lazyLoading` flag in combination with `treeData` and `rowGroupingModel`. + +## Error handling + +To handle errors, use `unstable_onDataSourceError` prop as described in the [Error handling](/x/react-data-grid/server-side-data/#error-handling) section of the data source overview page. + +Second parameter of type `GridGetRowsParams` can be passed to `getRows` method of the [`unstable_dataSource`](/x/api/data-grid/grid-api/#grid-api-prop-unstable_dataSource) to retry the request. If successful, the grid will use `rows` and `rowCount` data to determine if the rows should be appended at the end of the grid or the skeleton rows should be replaced. + +The following demo gives an example how to use `GridGetRowsParams` to retry a failed request. + +{{"demo": "ServerSideLazyLoadingErrorHandling.js", "bg": "inline"}} + +## API + +- [DataGrid](/x/api/data-grid/data-grid/) +- [DataGridPro](/x/api/data-grid/data-grid-pro/) +- [DataGridPremium](/x/api/data-grid/data-grid-premium/) From 302458187c225470c859d28859ceefd02cc956f4 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 2 Oct 2024 21:33:41 +0200 Subject: [PATCH 044/121] Fix docs --- .../server-side-data/lazy-loading.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/data/data-grid/server-side-data/lazy-loading.md b/docs/data/data-grid/server-side-data/lazy-loading.md index e293e7d49ece7..f612980fe12b9 100644 --- a/docs/data/data-grid/server-side-data/lazy-loading.md +++ b/docs/data/data-grid/server-side-data/lazy-loading.md @@ -12,9 +12,9 @@ It is enabled by adding `lazyLoading` prop in combination with `unstable_dataSou Initially, the first page data is fetched and displayed in the grid. What triggers the loading of next page data depends on the value of the total row count. -If the total row count is known, the grid will be filled with skeleton rows and will fetch more data if one of the skeleton rows falls into the rendering context. +If the total row count is known, the grid gets filled with skeleton rows and fetches more data if one of the skeleton rows falls into the rendering context. -If the total row count is unknown, the grid will fetch more data when the user scrolls to the bottom of the grid. This loading strategy is often referred to as **infinite loading**. +If the total row count is unknown, the grid fetches more data when the user scrolls to the bottom of the grid. This loading strategy is often referred to as **infinite loading**. :::info Row count can be provided either by returning the `rowCount` in the response of the `getRows` method in `unstable_dataSource`, via the `rowCount` prop or by calling [`setRowCount`](/x/api/data-grid/grid-api/#grid-api-prop-setRowCount) API. @@ -27,12 +27,12 @@ Order of precedence for the row count: - `rowCount` returned by the `getRows` method - row count set using the `setRowCount` API -This means that, if the row count is set using the API, the value will be overridden once a new value is returned by the `getRows` method, even if it is `undefined`. +This means that, if the row count is set using the API, that value gets overridden once a new value is returned by the `getRows` method, even if it is `undefined`. ::: ## Viewport loading -The viewport loading mode is enabled when the row count is known (`rowCount >= 0`). Grid will fetch the first page and add skeleton rows to match the total row count. Other pages are fetched once the user starts scrolling and moves a skeleton row inside the rendering context (index range defined by [Virtualization](/x/react-data-grid/virtualization/)). +The viewport loading mode is enabled when the row count is known (`rowCount >= 0`). Grid fetches the first page immediately and adds skeleton rows to match the total row count. Other pages are fetched once the user starts scrolling and moves a skeleton row inside the rendering context (index range defined by [Virtualization](/x/react-data-grid/virtualization/)). If the user scrolls too fast, the grid loads multiple pages with one request (by adjusting `start` and `end` param) in order to reduce the server load. @@ -53,21 +53,21 @@ The infinite loading mode is enabled when the row count is unknown (`-1` or `und The area which triggers the new request can be changed using `scrollEndThreshold`. -The demo below shows the infinite loading mode. Page size is set to `15` and the mock server is configured to return a total of `100` rows. Once the response does not contain any new rows, the grid will stop requesting new data. +The demo below shows the infinite loading mode. Page size is set to `15` and the mock server is configured to return a total of `100` rows. Once the response does not contain any new rows, the grid stops requesting new data. {{"demo": "ServerSideLazyLoadingInfinite.js", "bg": "inline"}} ## Updating the loading mode -The grid will dynamically change the loading mode if the total row count gets updated in any of the three ways described above. +The grid changes the loading mode dynamically if the total row count gets updated in any of the three ways described above. Based on the previous and the new value for the total row count, the following scenarios are possible: -- **Unknown `rowCount` to known `rowCount`**: If row count is not unknown anymore, the grid will switch to the viewport loading mode. It will check the amount of allready fetched rows and will add skeleton rows to match the total row count. +- **Unknown `rowCount` to known `rowCount`**: If row count is not unknown anymore, the grid switches to the viewport loading mode. It checks the amount of allready fetched rows and adds skeleton rows to match the total row count. -- **Known `rowCount` to unknown `rowCount`**: If the row count is updated and set to `-1`, the grid will reset, fetch the first page and set itself in the infinite loading mode. +- **Known `rowCount` to unknown `rowCount`**: If the row count is updated and set to `-1`, the grid resets, fetches the first page and sets itself in the infinite loading mode. -- **Known `rowCount` greater than the actual row count**: This can happen either by reducing the value of the row count after more rows were already fetched or if the row count was unknown and the grid in the inifite loading mode already fetched more rows. In this case, the grid will reset, fetch the first page and continue in one of the modes depending on the new value of the `rowCount`. +- **Known `rowCount` greater than the actual row count**: This can happen either by reducing the value of the row count after more rows were already fetched or if the row count was unknown and the grid in the inifite loading mode already fetched more rows. In this case, the grid resets, fetches the first page and continues in one of the modes depending on the new value of the `rowCount`. :::warning `rowCount` is expected to be static. Changing its value can cause the grid to reset and the cache to be cleared which leads to poor performance and user experience. @@ -93,7 +93,7 @@ When completed, it will be possible to use `lazyLoading` flag in combination wit To handle errors, use `unstable_onDataSourceError` prop as described in the [Error handling](/x/react-data-grid/server-side-data/#error-handling) section of the data source overview page. -Second parameter of type `GridGetRowsParams` can be passed to `getRows` method of the [`unstable_dataSource`](/x/api/data-grid/grid-api/#grid-api-prop-unstable_dataSource) to retry the request. If successful, the grid will use `rows` and `rowCount` data to determine if the rows should be appended at the end of the grid or the skeleton rows should be replaced. +Second parameter of type `GridGetRowsParams` can be passed to `getRows` method of the [`unstable_dataSource`](/x/api/data-grid/grid-api/#grid-api-prop-unstable_dataSource) to retry the request. If successful, the grid uses `rows` and `rowCount` data to determine if the rows should be appended at the end of the grid or if the skeleton rows should be replaced. The following demo gives an example how to use `GridGetRowsParams` to retry a failed request. From a831683d6b78257bfd5c7aea54d9bf7bdf42f9a3 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 2 Oct 2024 21:44:15 +0200 Subject: [PATCH 045/121] Update description --- .../src/DataGridPremium/DataGridPremium.tsx | 4 ++-- packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx | 4 ++-- packages/x-data-grid-pro/src/models/dataGridProProps.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx index d68cbac0d2564..5002fad6595d7 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx @@ -521,8 +521,8 @@ DataGridPremiumRaw.propTypes = { keepNonExistentRowsSelected: PropTypes.bool, /** * Used together with `unstable_dataSource` to enable lazy loading. - * If enabled, the grid will stop adding `paginationModel` to the data requests (`getRows`) and start sending `start` and `end` values depending on the scroll position. - * A new request will be made whenever the user scrolls to the area that has skeleton rows. + * If enabled, the grid stops adding `paginationModel` to the data requests (`getRows`) + * and starts sending `start` and `end` values depending on the loading mode and the scroll position. * @default false */ lazyLoading: PropTypes.bool, diff --git a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx index 37186a3b16888..4c3ecbc78b19b 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx @@ -477,8 +477,8 @@ DataGridProRaw.propTypes = { keepNonExistentRowsSelected: PropTypes.bool, /** * Used together with `unstable_dataSource` to enable lazy loading. - * If enabled, the grid will stop adding `paginationModel` to the data requests (`getRows`) and start sending `start` and `end` values depending on the scroll position. - * A new request will be made whenever the user scrolls to the area that has skeleton rows. + * If enabled, the grid stops adding `paginationModel` to the data requests (`getRows`) + * and starts sending `start` and `end` values depending on the loading mode and the scroll position. * @default false */ lazyLoading: PropTypes.bool, diff --git a/packages/x-data-grid-pro/src/models/dataGridProProps.ts b/packages/x-data-grid-pro/src/models/dataGridProProps.ts index 4503fee8bfb3f..65fb323f2e25d 100644 --- a/packages/x-data-grid-pro/src/models/dataGridProProps.ts +++ b/packages/x-data-grid-pro/src/models/dataGridProProps.ts @@ -145,8 +145,8 @@ export interface DataGridProPropsWithDefaultValue Date: Wed, 2 Oct 2024 21:44:39 +0200 Subject: [PATCH 046/121] Fix type error --- .../server-side-data/ServerSideLazyLoadingModeUpdate.js | 2 +- .../server-side-data/ServerSideLazyLoadingModeUpdate.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.js index 38e6db495de29..ecbb54ca6f065 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.js @@ -21,7 +21,7 @@ function Toolbar(props) { aria-labelledby="demo-row-count-buttons-group-label" name="row-count-buttons-group" value={count} - onChange={(event) => setCount(Number(event.target.value))} + onChange={(event) => setCount && setCount(Number(event.target.value))} > } label="Unknown" /> } label="40" /> diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.tsx index 92bcb2a586179..2fa21cdd19d48 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.tsx @@ -3,7 +3,7 @@ import { DataGridPro, GridDataSource, GridGetRowsParams, - ToolbarPropsOverrides, + GridSlotsComponentsProps, } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; import FormControl from '@mui/material/FormControl'; @@ -19,7 +19,7 @@ declare module '@mui/x-data-grid' { } } -function Toolbar(props: ToolbarPropsOverrides) { +function Toolbar(props: NonNullable) { const { count, setCount } = props; return ( setCount(Number(event.target.value))} + onChange={(event) => setCount && setCount(Number(event.target.value))} > } label="Unknown" /> } label="40" /> From b464d334729ae0d069d34769aee9daf5aae0b92a Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 3 Oct 2024 08:50:56 +0200 Subject: [PATCH 047/121] Update API docs --- .../api-docs/data-grid/data-grid-premium/data-grid-premium.json | 2 +- .../api-docs/data-grid/data-grid-pro/data-grid-pro.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json index c76ad08d5d6fb..5ba8389682ee9 100644 --- a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json +++ b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json @@ -236,7 +236,7 @@ "description": "If true, the selection model will retain selected rows that do not exist. Useful when using server side pagination and row selections need to be retained when changing pages." }, "lazyLoading": { - "description": "Used together with unstable_dataSource to enable lazy loading. If enabled, the grid will stop adding paginationModel to the data requests (getRows) and start sending start and end values depending on the scroll position. A new request will be made whenever the user scrolls to the area that has skeleton rows." + "description": "Used together with unstable_dataSource to enable lazy loading. If enabled, the grid stops adding paginationModel to the data requests (getRows) and starts sending start and end values depending on the loading mode and the scroll position." }, "loading": { "description": "If true, a loading overlay is displayed." }, "localeText": { diff --git a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json index e5e02f0cb3a10..458262b85a282 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json +++ b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json @@ -217,7 +217,7 @@ "description": "If true, the selection model will retain selected rows that do not exist. Useful when using server side pagination and row selections need to be retained when changing pages." }, "lazyLoading": { - "description": "Used together with unstable_dataSource to enable lazy loading. If enabled, the grid will stop adding paginationModel to the data requests (getRows) and start sending start and end values depending on the scroll position. A new request will be made whenever the user scrolls to the area that has skeleton rows." + "description": "Used together with unstable_dataSource to enable lazy loading. If enabled, the grid stops adding paginationModel to the data requests (getRows) and starts sending start and end values depending on the loading mode and the scroll position." }, "loading": { "description": "If true, a loading overlay is displayed." }, "localeText": { From 77d1e2ad157cc2bc42b0e1a80a62d12d1c8ac885 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 3 Oct 2024 09:15:18 +0200 Subject: [PATCH 048/121] Align toolbar customization --- .../ServerSideLazyLoadingModeUpdate.js | 7 +++---- .../ServerSideLazyLoadingModeUpdate.tsx | 17 +++++++---------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.js index ecbb54ca6f065..dca93be3bfc08 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.js @@ -7,8 +7,7 @@ import RadioGroup from '@mui/material/RadioGroup'; import FormControlLabel from '@mui/material/FormControlLabel'; import Radio from '@mui/material/Radio'; -function Toolbar(props) { - const { count, setCount } = props; +function GridCustomToolbar({ count, setCount }) { return ( setCount && setCount(Number(event.target.value))} + onChange={(event) => setCount(Number(event.target.value))} > } label="Unknown" /> } label="40" /> @@ -68,7 +67,7 @@ function ServerSideLazyLoadingModeUpdate() { lazyLoading paginationModel={{ page: 0, pageSize: 10 }} rowCount={rowCount} - slots={{ toolbar: Toolbar }} + slots={{ toolbar: GridCustomToolbar }} slotProps={{ toolbar: { count: rowCount, diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.tsx index 2fa21cdd19d48..e5ec165b33a60 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.tsx @@ -3,7 +3,7 @@ import { DataGridPro, GridDataSource, GridGetRowsParams, - GridSlotsComponentsProps, + GridSlots, } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; import FormControl from '@mui/material/FormControl'; @@ -12,15 +12,12 @@ import RadioGroup from '@mui/material/RadioGroup'; import FormControlLabel from '@mui/material/FormControlLabel'; import Radio from '@mui/material/Radio'; -declare module '@mui/x-data-grid' { - interface ToolbarPropsOverrides { - count: number; - setCount: (count: number) => void; - } +interface CustomToolbarProps { + count: number; + setCount: (count: number) => void; } -function Toolbar(props: NonNullable) { - const { count, setCount } = props; +function GridCustomToolbar({ count, setCount }: CustomToolbarProps) { return ( ) { aria-labelledby="demo-row-count-buttons-group-label" name="row-count-buttons-group" value={count} - onChange={(event) => setCount && setCount(Number(event.target.value))} + onChange={(event) => setCount(Number(event.target.value))} > } label="Unknown" /> } label="40" /> @@ -80,7 +77,7 @@ function ServerSideLazyLoadingModeUpdate() { lazyLoading paginationModel={{ page: 0, pageSize: 10 }} rowCount={rowCount} - slots={{ toolbar: Toolbar }} + slots={{ toolbar: GridCustomToolbar as GridSlots['toolbar'] }} slotProps={{ toolbar: { count: rowCount, From d79bdac5b001eaeb885bb264bf44fb9c8c6e7156 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 3 Oct 2024 09:34:07 +0200 Subject: [PATCH 049/121] Update future feature links --- docs/data/data-grid/server-side-data/lazy-loading.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/data-grid/server-side-data/lazy-loading.md b/docs/data/data-grid/server-side-data/lazy-loading.md index f612980fe12b9..1245df7412e52 100644 --- a/docs/data/data-grid/server-side-data/lazy-loading.md +++ b/docs/data/data-grid/server-side-data/lazy-loading.md @@ -87,7 +87,7 @@ This feature isn't implemented yet. It's coming. Don't hesitate to leave a comment on the same issue to influence what gets built. Especially if you already have a use case for this feature, or if you are facing a pain point with your current solution. ::: -When completed, it will be possible to use `lazyLoading` flag in combination with `treeData` and `rowGroupingModel`. +When completed, it will be possible to use `lazyLoading` flag in combination with [Tree data](/x/react-data-grid/server-side-data/tree-data/) and [Row grouping](/x/react-data-grid/server-side-data/row-grouping/). ## Error handling From 902d1c659f4000efec384f0ee093fdad03117d88 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 3 Oct 2024 09:38:17 +0200 Subject: [PATCH 050/121] Do not export GridDataSourceCacheDefaultConfig --- packages/x-data-grid-pro/src/hooks/features/index.ts | 2 +- scripts/x-data-grid-premium.exports.json | 1 - scripts/x-data-grid-pro.exports.json | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/index.ts b/packages/x-data-grid-pro/src/hooks/features/index.ts index dd9209be6a533..456da75b157c1 100644 --- a/packages/x-data-grid-pro/src/hooks/features/index.ts +++ b/packages/x-data-grid-pro/src/hooks/features/index.ts @@ -6,4 +6,4 @@ export * from './treeData'; export * from './detailPanel'; export * from './rowPinning'; export * from './dataSource/interfaces'; -export * from './dataSource/cache'; +export { GridDataSourceCacheDefault } from './dataSource/cache'; diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 7b3b16d2c4fe0..a0e1ff0dde41c 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -256,7 +256,6 @@ { "name": "GridDataSourceApiBase", "kind": "Interface" }, { "name": "GridDataSourceCache", "kind": "Interface" }, { "name": "GridDataSourceCacheDefault", "kind": "Class" }, - { "name": "GridDataSourceCacheDefaultConfig", "kind": "TypeAlias" }, { "name": "GridDataSourceGroupNode", "kind": "Interface" }, { "name": "GridDataSourcePrivateApi", "kind": "Interface" }, { "name": "GridDataSourceState", "kind": "Interface" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index f3341bbdf6adf..6f20393ae95b6 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -229,7 +229,6 @@ { "name": "GridDataSourceApiBase", "kind": "Interface" }, { "name": "GridDataSourceCache", "kind": "Interface" }, { "name": "GridDataSourceCacheDefault", "kind": "Class" }, - { "name": "GridDataSourceCacheDefaultConfig", "kind": "TypeAlias" }, { "name": "GridDataSourceGroupNode", "kind": "Interface" }, { "name": "GridDataSourcePrivateApi", "kind": "Interface" }, { "name": "GridDataSourceState", "kind": "Interface" }, From c1a0d4e3c968bafa0009d582fb09a6303ee250f9 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 3 Oct 2024 12:19:59 +0200 Subject: [PATCH 051/121] Add prop to control throttling --- .../server-side-data/lazy-loading.md | 2 + .../src/DataGridPremium/DataGridPremium.tsx | 6 + .../src/DataGridPro/DataGridPro.tsx | 6 + .../src/DataGridPro/useDataGridProProps.ts | 1 + .../useGridDataSourceLazyLoader.ts | 11 +- .../src/models/dataGridProProps.ts | 6 + .../dataSourceLazyLoader.DataGridPro.test.tsx | 194 ++++++++++++++++++ 7 files changed, 223 insertions(+), 3 deletions(-) create mode 100644 packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx diff --git a/docs/data/data-grid/server-side-data/lazy-loading.md b/docs/data/data-grid/server-side-data/lazy-loading.md index 1245df7412e52..1cc4a0b43ce4e 100644 --- a/docs/data/data-grid/server-side-data/lazy-loading.md +++ b/docs/data/data-grid/server-side-data/lazy-loading.md @@ -36,6 +36,8 @@ The viewport loading mode is enabled when the row count is known (`rowCount >= 0 If the user scrolls too fast, the grid loads multiple pages with one request (by adjusting `start` and `end` param) in order to reduce the server load. +In addition to this, the grid throttles new requests made to the data source after each rendering context change. This can be controlled with `lazyLoadingRequestThrottleMs` prop. + The demo below shows the viewport loading mode. {{"demo": "ServerSideLazyLoadingViewport.js", "bg": "inline"}} diff --git a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx index 5002fad6595d7..a3bb3feeeea85 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx @@ -526,6 +526,11 @@ DataGridPremiumRaw.propTypes = { * @default false */ lazyLoading: PropTypes.bool, + /** + * If positive, the Data Grid will throttle data source requests on rendered rows interval change. + * @default 500 + */ + lazyLoadingRequestThrottleMs: PropTypes.number, /** * If `true`, a loading overlay is displayed. * @default false @@ -1025,6 +1030,7 @@ DataGridPremiumRaw.propTypes = { scrollbarSize: PropTypes.number, /** * Set the area in `px` at the bottom of the grid viewport where onRowsScrollEnd is called. + * If combined with `lazyLoading`, it defines the area where the next data request is triggered. * @default 80 */ scrollEndThreshold: PropTypes.number, diff --git a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx index 4c3ecbc78b19b..f14ea100cf80a 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx @@ -482,6 +482,11 @@ DataGridProRaw.propTypes = { * @default false */ lazyLoading: PropTypes.bool, + /** + * If positive, the Data Grid will throttle data source requests on rendered rows interval change. + * @default 500 + */ + lazyLoadingRequestThrottleMs: PropTypes.number, /** * If `true`, a loading overlay is displayed. * @default false @@ -932,6 +937,7 @@ DataGridProRaw.propTypes = { scrollbarSize: PropTypes.number, /** * Set the area in `px` at the bottom of the grid viewport where onRowsScrollEnd is called. + * If combined with `lazyLoading`, it defines the area where the next data request is triggered. * @default 80 */ scrollEndThreshold: PropTypes.number, diff --git a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts index 4724876fa8590..0d100ebdcec75 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts +++ b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts @@ -57,6 +57,7 @@ export const DATA_GRID_PRO_PROPS_DEFAULT_VALUES: DataGridProPropsWithDefaultValu treeData: false, unstable_listView: false, lazyLoading: false, + lazyLoadingRequestThrottleMs: 500, }; const defaultSlots = DATA_GRID_PRO_DEFAULT_SLOTS_COMPONENTS; 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 9ca464f56f970..d4ac9ef752a75 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 @@ -44,7 +44,12 @@ export const useGridDataSourceLazyLoader = ( privateApiRef: React.MutableRefObject, props: Pick< DataGridProProcessedProps, - 'pagination' | 'paginationMode' | 'unstable_dataSource' | 'lazyLoading' | 'scrollEndThreshold' + | 'pagination' + | 'paginationMode' + | 'unstable_dataSource' + | 'lazyLoading' + | 'lazyLoadingRequestThrottleMs' + | 'scrollEndThreshold' >, ): void => { const sortModel = useGridSelector(privateApiRef, gridSortModelSelector); @@ -311,8 +316,8 @@ export const useGridDataSourceLazyLoader = ( ); const throttledHandleRenderedRowsIntervalChange = React.useMemo( - () => throttle(handleRenderedRowsIntervalChange, 500), // TODO: make it configurable - [handleRenderedRowsIntervalChange], + () => throttle(handleRenderedRowsIntervalChange, props.lazyLoadingRequestThrottleMs), + [props.lazyLoadingRequestThrottleMs, handleRenderedRowsIntervalChange], ); const handleGridSortModelChange = React.useCallback>( diff --git a/packages/x-data-grid-pro/src/models/dataGridProProps.ts b/packages/x-data-grid-pro/src/models/dataGridProProps.ts index 65fb323f2e25d..8676702920213 100644 --- a/packages/x-data-grid-pro/src/models/dataGridProProps.ts +++ b/packages/x-data-grid-pro/src/models/dataGridProProps.ts @@ -77,6 +77,7 @@ export interface DataGridProPropsWithDefaultValue - Data source lazy loader', () => { + const { render } = createRenderer(); + + const baselineProps: { rows: GridRowsProp; columns: GridColDef[] } = { + rows: [ + { + id: 1, + first: 'Mike', + }, + { + id: 2, + first: 'Jack', + }, + { + id: 3, + first: 'Jim', + }, + ], + columns: [{ field: 'id' }, { field: 'first' }], + }; + + let apiRef: React.MutableRefObject; + + function TestLazyLoader(props: Partial) { + apiRef = useGridApiRef(); + return ( +
+ +
+ ); + } + + it('should not call onFetchRows if the viewport is fully loaded', function test() { + if (isJSDOM) { + this.skip(); // Needs layout + } + const handleFetchRows = spy(); + const rows = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }, { id: 6 }, { id: 7 }]; + render(); + expect(handleFetchRows.callCount).to.equal(0); + }); + + it('should call onFetchRows when sorting is applied', function test() { + if (isJSDOM) { + this.skip(); // Needs layout + } + const handleFetchRows = spy(); + render(); + + expect(handleFetchRows.callCount).to.equal(1); + // Should be 1. When tested in the browser it's called only 2 time + fireEvent.click(getColumnHeaderCell(0)); + expect(handleFetchRows.callCount).to.equal(2); + }); + + it('should render skeleton cell if rowCount is bigger than the number of rows', function test() { + if (isJSDOM) { + this.skip(); // Needs layout + } + + render(); + + // The 4th row should be a skeleton one + expect(getRow(3).dataset.id).to.equal('auto-generated-skeleton-row-root-0'); + }); + + it('should update all rows accordingly when `apiRef.current.unstable_replaceRows` is called', () => { + render(); + + const newRows: GridRowModel[] = [ + { id: 4, name: 'John' }, + { id: 5, name: 'Mac' }, + ]; + + const initialAllRows = apiRef.current.getRowNode(GRID_ROOT_GROUP_ID)!.children; + expect(initialAllRows.slice(3, 6)).to.deep.equal([ + 'auto-generated-skeleton-row-root-0', + 'auto-generated-skeleton-row-root-1', + 'auto-generated-skeleton-row-root-2', + ]); + act(() => apiRef.current.unstable_replaceRows(4, newRows)); + + const updatedAllRows = apiRef.current.getRowNode(GRID_ROOT_GROUP_ID)!.children; + expect(updatedAllRows.slice(4, 6)).to.deep.equal([4, 5]); + }); + + // See https://github.com/mui/mui-x/issues/6857 + it('should update the row when `apiRef.current.updateRows` is called on lazy-loaded rows', () => { + render(); + + const newRows: GridRowModel[] = [ + { id: 4, first: 'John' }, + { id: 5, first: 'Mac' }, + ]; + + act(() => apiRef.current.unstable_replaceRows(3, newRows)); + expect(getColumnValues(1)).to.deep.equal(['Mike', 'Jack', 'Jim', 'John', 'Mac']); + + act(() => apiRef.current.updateRows([{ id: 4, first: 'John updated' }])); + expect(getColumnValues(1)).to.deep.equal(['Mike', 'Jack', 'Jim', 'John updated', 'Mac']); + }); + + it('should update all rows accordingly when `apiRef.current.unstable_replaceRows` is called and props.getRowId is defined', () => { + render( + row.clientId} + rows={[ + { + clientId: 1, + first: 'Mike', + }, + { + clientId: 2, + first: 'Jack', + }, + { + clientId: 3, + first: 'Jim', + }, + ]} + columns={[{ field: 'clientId' }]} + />, + ); + + const newRows: GridRowModel[] = [ + { clientId: 4, name: 'John' }, + { clientId: 5, name: 'Mac' }, + ]; + + const initialAllRows = apiRef.current.getRowNode(GRID_ROOT_GROUP_ID)!.children; + expect(initialAllRows.slice(3, 6)).to.deep.equal([ + 'auto-generated-skeleton-row-root-0', + 'auto-generated-skeleton-row-root-1', + 'auto-generated-skeleton-row-root-2', + ]); + act(() => apiRef.current.unstable_replaceRows(4, newRows)); + + const updatedAllRows = apiRef.current.getRowNode(GRID_ROOT_GROUP_ID)!.children; + expect(updatedAllRows.slice(4, 6)).to.deep.equal([4, 5]); + + expect(apiRef.current.getRowNode(4)).not.to.equal(null); + expect(apiRef.current.getRowNode(5)).not.to.equal(null); + }); + + it('should update rows when `apiRef.current.updateRows` with data reversed', () => { + render(); + + const newRows: GridRowModel[] = [ + { + id: 3, + first: 'Jim', + }, + { + id: 2, + first: 'Jack', + }, + { + id: 1, + first: 'Mike', + }, + ]; + + act(() => apiRef.current.unstable_replaceRows(0, newRows)); + expect(getColumnValues(1)).to.deep.equal(['Jim', 'Jack', 'Mike']); + }); +}); From 2b83584bba4b0e9fac1dffed9c96e89e87104872 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 3 Oct 2024 12:28:25 +0200 Subject: [PATCH 052/121] Update API docs --- docs/pages/x/api/data-grid/data-grid-premium.json | 1 + docs/pages/x/api/data-grid/data-grid-pro.json | 1 + .../data-grid/data-grid-premium/data-grid-premium.json | 5 ++++- .../api-docs/data-grid/data-grid-pro/data-grid-pro.json | 5 ++++- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/pages/x/api/data-grid/data-grid-premium.json b/docs/pages/x/api/data-grid/data-grid-premium.json index afbb2c23605f1..2e5c46e9149ff 100644 --- a/docs/pages/x/api/data-grid/data-grid-premium.json +++ b/docs/pages/x/api/data-grid/data-grid-premium.json @@ -212,6 +212,7 @@ "keepColumnPositionIfDraggedOutside": { "type": { "name": "bool" }, "default": "false" }, "keepNonExistentRowsSelected": { "type": { "name": "bool" }, "default": "false" }, "lazyLoading": { "type": { "name": "bool" }, "default": "false" }, + "lazyLoadingRequestThrottleMs": { "type": { "name": "number" }, "default": "500" }, "loading": { "type": { "name": "bool" }, "default": "false" }, "localeText": { "type": { "name": "object" } }, "logger": { diff --git a/docs/pages/x/api/data-grid/data-grid-pro.json b/docs/pages/x/api/data-grid/data-grid-pro.json index fdd7e14d72550..704b1bf0f714b 100644 --- a/docs/pages/x/api/data-grid/data-grid-pro.json +++ b/docs/pages/x/api/data-grid/data-grid-pro.json @@ -189,6 +189,7 @@ "keepColumnPositionIfDraggedOutside": { "type": { "name": "bool" }, "default": "false" }, "keepNonExistentRowsSelected": { "type": { "name": "bool" }, "default": "false" }, "lazyLoading": { "type": { "name": "bool" }, "default": "false" }, + "lazyLoadingRequestThrottleMs": { "type": { "name": "number" }, "default": "500" }, "loading": { "type": { "name": "bool" }, "default": "false" }, "localeText": { "type": { "name": "object" } }, "logger": { diff --git a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json index 5ba8389682ee9..f0852be9f7443 100644 --- a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json +++ b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json @@ -238,6 +238,9 @@ "lazyLoading": { "description": "Used together with unstable_dataSource to enable lazy loading. If enabled, the grid stops adding paginationModel to the data requests (getRows) and starts sending start and end values depending on the loading mode and the scroll position." }, + "lazyLoadingRequestThrottleMs": { + "description": "If positive, the Data Grid will throttle data source requests on rendered rows interval change." + }, "loading": { "description": "If true, a loading overlay is displayed." }, "localeText": { "description": "Set the locale text of the Data Grid. You can find all the translation keys supported in the source in the GitHub repository." @@ -631,7 +634,7 @@ "description": "Override the height/width of the Data Grid inner scrollbar." }, "scrollEndThreshold": { - "description": "Set the area in px at the bottom of the grid viewport where onRowsScrollEnd is called." + "description": "Set the area in px at the bottom of the grid viewport where onRowsScrollEnd is called. If combined with lazyLoading, it defines the area where the next data request is triggered." }, "showCellVerticalBorder": { "description": "If true, vertical borders will be displayed between cells." diff --git a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json index 458262b85a282..ed4118fb02036 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json +++ b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json @@ -219,6 +219,9 @@ "lazyLoading": { "description": "Used together with unstable_dataSource to enable lazy loading. If enabled, the grid stops adding paginationModel to the data requests (getRows) and starts sending start and end values depending on the loading mode and the scroll position." }, + "lazyLoadingRequestThrottleMs": { + "description": "If positive, the Data Grid will throttle data source requests on rendered rows interval change." + }, "loading": { "description": "If true, a loading overlay is displayed." }, "localeText": { "description": "Set the locale text of the Data Grid. You can find all the translation keys supported in the source in the GitHub repository." @@ -573,7 +576,7 @@ "description": "Override the height/width of the Data Grid inner scrollbar." }, "scrollEndThreshold": { - "description": "Set the area in px at the bottom of the grid viewport where onRowsScrollEnd is called." + "description": "Set the area in px at the bottom of the grid viewport where onRowsScrollEnd is called. If combined with lazyLoading, it defines the area where the next data request is triggered." }, "showCellVerticalBorder": { "description": "If true, vertical borders will be displayed between cells." From 308134359d156ccdd2ed6fa93cf806ddf01a9aac Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Fri, 4 Oct 2024 09:13:15 +0200 Subject: [PATCH 053/121] Fix: Sorting in viewport mode should fill the grid with skeleton rows --- .../useGridDataSourceLazyLoader.ts | 95 +++++++++++-------- 1 file changed, 54 insertions(+), 41 deletions(-) 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 d4ac9ef752a75..4ad9c0f0ea8db 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 @@ -131,48 +131,61 @@ export const useGridDataSourceLazyLoader = ( [privateApiRef, resetGrid], ); - const adjustGridRows = React.useCallback(() => { - const tree = privateApiRef.current.state.rows.tree; - const rootGroup = tree[GRID_ROOT_GROUP_ID] as GridGroupNode; - const rootGroupChildren = [...rootGroup.children]; - - const pageRowCount = privateApiRef.current.state.pagination.rowCount; - const rootChildrenCount = rootGroupChildren.length; + const addSkeletonRows = React.useCallback( + (fillEmptyGrid = false) => { + const tree = privateApiRef.current.state.rows.tree; + const rootGroup = tree[GRID_ROOT_GROUP_ID] as GridGroupNode; + const rootGroupChildren = [...rootGroup.children]; - // if row count cannot be determined or all rows are there, do nothing - if (pageRowCount === -1 || rootChildrenCount === 0 || rootChildrenCount === pageRowCount) { - return; - } + const pageRowCount = privateApiRef.current.state.pagination.rowCount; + const rootChildrenCount = rootGroupChildren.length; - // fill the grid with skeleton rows - for (let i = 0; i < pageRowCount - rootChildrenCount; i += 1) { - const skeletonId = getSkeletonRowId(i); + /** + * Do nothing if + * - rowCount is unknown + * - children count is 0 and empty grid should not be filled + * - children count is equal to rowCount + */ + if ( + pageRowCount === -1 || + pageRowCount === undefined || + (!fillEmptyGrid && rootChildrenCount === 0) || + rootChildrenCount === pageRowCount + ) { + return; + } - rootGroupChildren.push(skeletonId); + // fill the grid with skeleton rows + for (let i = 0; i < pageRowCount - rootChildrenCount; i += 1) { + const skeletonId = getSkeletonRowId(i); - const skeletonRowNode: GridSkeletonRowNode = { - type: 'skeletonRow', - id: skeletonId, - parent: GRID_ROOT_GROUP_ID, - depth: 0, - }; + rootGroupChildren.push(skeletonId); - tree[skeletonId] = skeletonRowNode; - } + const skeletonRowNode: GridSkeletonRowNode = { + type: 'skeletonRow', + id: skeletonId, + parent: GRID_ROOT_GROUP_ID, + depth: 0, + }; - tree[GRID_ROOT_GROUP_ID] = { ...rootGroup, children: rootGroupChildren }; + tree[skeletonId] = skeletonRowNode; + } - privateApiRef.current.setState( - (state) => ({ - ...state, - rows: { - ...state.rows, - tree, - }, - }), - 'addSkeletonRows', - ); - }, [privateApiRef]); + tree[GRID_ROOT_GROUP_ID] = { ...rootGroup, children: rootGroupChildren }; + + privateApiRef.current.setState( + (state) => ({ + ...state, + rows: { + ...state.rows, + tree, + }, + }), + 'addSkeletonRows', + ); + }, + [privateApiRef], + ); const updateLoadingTrigger = React.useCallback( (rowCount: number) => { @@ -199,10 +212,10 @@ export const useGridDataSourceLazyLoader = ( updateLoadingTrigger(privateApiRef.current.state.pagination.rowCount); } - adjustGridRows(); + addSkeletonRows(); privateApiRef.current.state.rows.loading = false; privateApiRef.current.requestPipeProcessorsApplication('hydrateRows'); - }, [privateApiRef, isDisabled, updateLoadingTrigger, adjustGridRows]); + }, [privateApiRef, isDisabled, updateLoadingTrigger, addSkeletonRows]); const handleRowCountChange = React.useCallback(() => { if (isDisabled || loadingTrigger.current === null) { @@ -210,9 +223,9 @@ export const useGridDataSourceLazyLoader = ( } updateLoadingTrigger(privateApiRef.current.state.pagination.rowCount); - adjustGridRows(); + addSkeletonRows(); privateApiRef.current.requestPipeProcessorsApplication('hydrateRows'); - }, [privateApiRef, isDisabled, updateLoadingTrigger, adjustGridRows]); + }, [privateApiRef, isDisabled, updateLoadingTrigger, addSkeletonRows]); const handleScrolling: GridEventListener<'scrollPositionChange'> = React.useCallback( (newScrollPosition) => { @@ -330,7 +343,7 @@ export const useGridDataSourceLazyLoader = ( previousLastRowIndex.current = 0; if (loadingTrigger.current === LoadingTrigger.VIEWPORT) { // replace all rows with skeletons to maintain the same scroll position - adjustGridRows(); + addSkeletonRows(true); } const rangeParams = @@ -358,7 +371,7 @@ export const useGridDataSourceLazyLoader = ( filterModel, paginationModel.pageSize, renderContext, - adjustGridRows, + addSkeletonRows, adjustRowParams, ], ); From b9161d0fb6b8d8fc2027d98df752c64187691556 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Fri, 4 Oct 2024 11:01:46 +0200 Subject: [PATCH 054/121] Add tests --- .../dataSourceLazyLoader.DataGridPro.test.tsx | 430 ++++++++++++------ 1 file changed, 287 insertions(+), 143 deletions(-) diff --git a/packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx index eb5686e0ace51..e573abf73a2b2 100644 --- a/packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx @@ -1,194 +1,338 @@ import * as React from 'react'; -import { createRenderer, fireEvent, act } from '@mui/internal-test-utils'; -import { getColumnHeaderCell, getColumnValues, getRow } from 'test/utils/helperFn'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import { createRenderer, waitFor } from '@mui/internal-test-utils'; +import { getRow } from 'test/utils/helperFn'; import { expect } from 'chai'; import { DataGridPro, DataGridProProps, - GRID_ROOT_GROUP_ID, GridApi, - GridColDef, - GridGroupNode, - GridRowModel, - GridRowsProp, + GridDataSource, + GridGetRowsParams, + GridGetRowsResponse, useGridApiRef, } from '@mui/x-data-grid-pro'; -import { spy } from 'sinon'; +import { SinonSpy, spy } from 'sinon'; const isJSDOM = /jsdom/.test(window.navigator.userAgent); -describe(' - Data source lazy loader', () => { +describe.only(' - Data source lazy loader', () => { const { render } = createRenderer(); + const defaultTransformGetRowsResponse = (response: GridGetRowsResponse) => response; - const baselineProps: { rows: GridRowsProp; columns: GridColDef[] } = { - rows: [ - { - id: 1, - first: 'Mike', - }, - { - id: 2, - first: 'Jack', - }, - { - id: 3, - first: 'Jim', - }, - ], - columns: [{ field: 'id' }, { field: 'first' }], - }; - + let transformGetRowsResponse: (response: GridGetRowsResponse) => GridGetRowsResponse; let apiRef: React.MutableRefObject; + let fetchRowsSpy: SinonSpy; + let mockServer: ReturnType; - function TestLazyLoader(props: Partial) { + function TestDataSourceLazyLoader(props: Partial) { apiRef = useGridApiRef(); + mockServer = useMockServer( + { rowLength: 100, maxColumns: 1 }, + { useCursorPagination: false, minDelay: 0, maxDelay: 0, verbose: false }, + ); + fetchRowsSpy = spy(mockServer, 'fetchRows'); + + const dataSource: GridDataSource = React.useMemo( + () => ({ + getRows: async (params: GridGetRowsParams) => { + const urlParams = new URLSearchParams({ + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + firstRowToRender: `${params.start}`, + lastRowToRender: `${params.end}`, + }); + + const getRowsResponse = await fetchRowsSpy( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + + const response = transformGetRowsResponse(getRowsResponse); + return { + rows: response.rows, + rowCount: response.rowCount, + }; + }, + }), + [fetchRowsSpy], + ); + + const baselineProps = { + unstable_dataSource: dataSource, + columns: mockServer.columns, + lazyLoading: true, + paginationModel: { page: 0, pageSize: 10 }, + disableVirtualization: true, + }; + return (
- +
); } - it('should not call onFetchRows if the viewport is fully loaded', function test() { + beforeEach(function beforeTest() { if (isJSDOM) { this.skip(); // Needs layout } - const handleFetchRows = spy(); - const rows = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }, { id: 6 }, { id: 7 }]; - render(); - expect(handleFetchRows.callCount).to.equal(0); - }); - it('should call onFetchRows when sorting is applied', function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } - const handleFetchRows = spy(); - render(); + transformGetRowsResponse = defaultTransformGetRowsResponse; + }); - expect(handleFetchRows.callCount).to.equal(1); - // Should be 1. When tested in the browser it's called only 2 time - fireEvent.click(getColumnHeaderCell(0)); - expect(handleFetchRows.callCount).to.equal(2); + it('should load the first page initially', async () => { + render(); + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(1); + }); }); - it('should render skeleton cell if rowCount is bigger than the number of rows', function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } + describe('Viewport loading', () => { + it('should render skeleton rows if rowCount is bigger than the number of rows', async () => { + render(); + // wait until the rows are rendered + await waitFor(() => expect(getRow(0)).not.to.be.undefined); - render(); + // The 11th row should be a skeleton + expect(getRow(10).dataset.id).to.equal('auto-generated-skeleton-row-root-0'); + }); - // The 4th row should be a skeleton one - expect(getRow(3).dataset.id).to.equal('auto-generated-skeleton-row-root-0'); - }); + it('should make a new data source request once the skeleton rows are in the render context', async () => { + render(); + // wait until the rows are rendered + await waitFor(() => expect(getRow(0)).not.to.be.undefined); - it('should update all rows accordingly when `apiRef.current.unstable_replaceRows` is called', () => { - render(); + // reset the spy call count + fetchRowsSpy.resetHistory(); - const newRows: GridRowModel[] = [ - { id: 4, name: 'John' }, - { id: 5, name: 'Mac' }, - ]; + apiRef.current.scrollToIndexes({ rowIndex: 10 }); - const initialAllRows = apiRef.current.getRowNode(GRID_ROOT_GROUP_ID)!.children; - expect(initialAllRows.slice(3, 6)).to.deep.equal([ - 'auto-generated-skeleton-row-root-0', - 'auto-generated-skeleton-row-root-1', - 'auto-generated-skeleton-row-root-2', - ]); - act(() => apiRef.current.unstable_replaceRows(4, newRows)); + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(1); + }); + }); - const updatedAllRows = apiRef.current.getRowNode(GRID_ROOT_GROUP_ID)!.children; - expect(updatedAllRows.slice(4, 6)).to.deep.equal([4, 5]); - }); + it('should keep the scroll position when sorting is applied', async () => { + render(); + // wait until the rows are rendered + await waitFor(() => expect(getRow(0)).not.to.be.undefined); - // See https://github.com/mui/mui-x/issues/6857 - it('should update the row when `apiRef.current.updateRows` is called on lazy-loaded rows', () => { - render(); + const initialSearchParams = new URL(fetchRowsSpy.lastCall.args[0]).searchParams; + expect(initialSearchParams.get('lastRowToRender')).to.equal('9'); - const newRows: GridRowModel[] = [ - { id: 4, first: 'John' }, - { id: 5, first: 'Mac' }, - ]; + apiRef.current.scrollToIndexes({ rowIndex: 10 }); - act(() => apiRef.current.unstable_replaceRows(3, newRows)); - expect(getColumnValues(1)).to.deep.equal(['Mike', 'Jack', 'Jim', 'John', 'Mac']); + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(2); + }); - act(() => apiRef.current.updateRows([{ id: 4, first: 'John updated' }])); - expect(getColumnValues(1)).to.deep.equal(['Mike', 'Jack', 'Jim', 'John updated', 'Mac']); - }); + const beforeSortSearchParams = new URL(fetchRowsSpy.lastCall.args[0]).searchParams; + expect(beforeSortSearchParams.get('lastRowToRender')).to.not.equal('9'); - it('should update all rows accordingly when `apiRef.current.unstable_replaceRows` is called and props.getRowId is defined', () => { - render( - row.clientId} - rows={[ - { - clientId: 1, - first: 'Mike', - }, + apiRef.current.sortColumn(mockServer.columns[0].field, 'asc'); + + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(3); + }); + + const afterSortSearchParams = new URL(fetchRowsSpy.lastCall.args[0]).searchParams; + expect(afterSortSearchParams.get('lastRowToRender')).to.equal( + beforeSortSearchParams.get('lastRowToRender'), + ); + }); + + it('should reset the scroll position when filter is applied', async () => { + render(); + // wait until the rows are rendered + await waitFor(() => expect(getRow(0)).not.to.be.undefined); + + apiRef.current.scrollToIndexes({ rowIndex: 10 }); + + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(2); + }); + + const beforeFilteringSearchParams = new URL(fetchRowsSpy.lastCall.args[0]).searchParams; + // last row is not the first page anymore + expect(beforeFilteringSearchParams.get('firstRowToRender')).to.not.equal('0'); + + apiRef.current.setFilterModel({ + items: [ { - clientId: 2, - first: 'Jack', + field: mockServer.columns[0].field, + value: '0', + operator: 'contains', }, + ], + }); + + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(3); + }); + + const afterFilteringSearchParams = new URL(fetchRowsSpy.lastCall.args[0]).searchParams; + // last row is the end of the first page + expect(afterFilteringSearchParams.get('firstRowToRender')).to.equal('0'); + }); + }); + + describe('Infinite loading', () => { + beforeEach(() => { + // override rowCount + transformGetRowsResponse = (response) => ({ ...response, rowCount: -1 }); + }); + + it('should not render skeleton rows if rowCount is unknown', async () => { + render(); + // wait until the rows are rendered + await waitFor(() => expect(getRow(0)).not.to.be.undefined); + + // The 11th row should not exist + expect(() => getRow(10)).to.throw(); + }); + + it('should make a new data source request in infinite loading mode once the bottom row is reached', async () => { + render(); + // wait until the rows are rendered + await waitFor(() => expect(getRow(0)).not.to.be.undefined); + + // reset the spy call count + fetchRowsSpy.resetHistory(); + + // make one small and one big scroll that makes sure that the bottom of the grid window is reached + apiRef.current.scrollToIndexes({ rowIndex: 1 }); + apiRef.current.scrollToIndexes({ rowIndex: 9 }); + + // Only one additional fetch should have been made + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(1); + }); + }); + + it('should reset the scroll position when sorting is applied', async () => { + render(); + // wait until the rows are rendered + await waitFor(() => expect(getRow(0)).not.to.be.undefined); + + apiRef.current.scrollToIndexes({ rowIndex: 9 }); + + // wait until the rows are rendered + await waitFor(() => expect(getRow(10)).not.to.be.undefined); + + const beforeSortingSearchParams = new URL(fetchRowsSpy.lastCall.args[0]).searchParams; + // last row is not the first page anymore + expect(beforeSortingSearchParams.get('lastRowToRender')).to.not.equal('9'); + + apiRef.current.sortColumn(mockServer.columns[0].field, 'asc'); + + const afterSortingSearchParams = new URL(fetchRowsSpy.lastCall.args[0]).searchParams; + // last row is the end of the first page + expect(afterSortingSearchParams.get('lastRowToRender')).to.equal('9'); + }); + + it('should reset the scroll position when filter is applied', async () => { + render(); + // wait until the rows are rendered + await waitFor(() => expect(getRow(0)).not.to.be.undefined); + + apiRef.current.scrollToIndexes({ rowIndex: 9 }); + + // wait until the rows are rendered + await waitFor(() => expect(getRow(10)).not.to.be.undefined); + + const beforeFilteringSearchParams = new URL(fetchRowsSpy.lastCall.args[0]).searchParams; + // last row is not the first page anymore + expect(beforeFilteringSearchParams.get('lastRowToRender')).to.not.equal('9'); + + apiRef.current.setFilterModel({ + items: [ { - clientId: 3, - first: 'Jim', + field: mockServer.columns[0].field, + value: '0', + operator: 'contains', }, - ]} - columns={[{ field: 'clientId' }]} - />, - ); + ], + }); - const newRows: GridRowModel[] = [ - { clientId: 4, name: 'John' }, - { clientId: 5, name: 'Mac' }, - ]; + const afterFilteringSearchParams = new URL(fetchRowsSpy.lastCall.args[0]).searchParams; + // last row is the end of the first page + expect(afterFilteringSearchParams.get('lastRowToRender')).to.equal('9'); + }); + }); - const initialAllRows = apiRef.current.getRowNode(GRID_ROOT_GROUP_ID)!.children; - expect(initialAllRows.slice(3, 6)).to.deep.equal([ - 'auto-generated-skeleton-row-root-0', - 'auto-generated-skeleton-row-root-1', - 'auto-generated-skeleton-row-root-2', - ]); - act(() => apiRef.current.unstable_replaceRows(4, newRows)); + describe('Row count updates', () => { + it('should add skeleton rows once the rowCount becomes known', async () => { + // override rowCount + transformGetRowsResponse = (response) => ({ ...response, rowCount: undefined }); + const { setProps } = render(); + // wait until the rows are rendered + await waitFor(() => expect(getRow(0)).not.to.be.undefined); - const updatedAllRows = apiRef.current.getRowNode(GRID_ROOT_GROUP_ID)!.children; - expect(updatedAllRows.slice(4, 6)).to.deep.equal([4, 5]); + // The 11th row should not exist + expect(() => getRow(10)).to.throw(); - expect(apiRef.current.getRowNode(4)).not.to.equal(null); - expect(apiRef.current.getRowNode(5)).not.to.equal(null); - }); + // make the rowCount known + setProps({ rowCount: 100 }); + + // The 11th row should be a skeleton + expect(getRow(10).dataset.id).to.equal('auto-generated-skeleton-row-root-0'); + }); + + it('should reset the grid if the rowCount becomes unknown', async () => { + // override rowCount + transformGetRowsResponse = (response) => ({ ...response, rowCount: undefined }); + const { setProps } = render(); + // wait until the rows are rendered + await waitFor(() => expect(getRow(0)).not.to.be.undefined); + + // The 11th row should not exist + expect(getRow(10).dataset.id).to.equal('auto-generated-skeleton-row-root-0'); + + // make the rowCount unknown + setProps({ rowCount: -1 }); + + // The 11th row should not exist + expect(() => getRow(10)).to.throw(); + }); + + it('should reset the grid if the rowCount becomes smaller than the actual row count', async () => { + // override rowCount + transformGetRowsResponse = (response) => ({ ...response, rowCount: undefined }); + const { setProps } = render( + , + ); + // wait until the rows are rendered + await waitFor(() => expect(getRow(0)).not.to.be.undefined); + + const getRowsEventSpy = spy(); + apiRef.current.subscribeEvent('getRows', getRowsEventSpy); + + // reduce the rowCount to be more than the number of rows + setProps({ rowCount: 80 }); + expect(getRowsEventSpy.callCount).to.equal(0); + + // reduce the rowCount once more, but now to be less than the number of rows + setProps({ rowCount: 20 }); + expect(getRowsEventSpy.callCount).to.equal(1); + }); + + it('should allow setting the row count via API', async () => { + // override rowCount + transformGetRowsResponse = (response) => ({ ...response, rowCount: undefined }); + render(); + // wait until the rows are rendered + await waitFor(() => expect(getRow(0)).not.to.be.undefined); + + // The 11th row should not exist + expect(() => getRow(10)).to.throw(); + + // set the rowCount via API + apiRef.current.setRowCount(100); - it('should update rows when `apiRef.current.updateRows` with data reversed', () => { - render(); - - const newRows: GridRowModel[] = [ - { - id: 3, - first: 'Jim', - }, - { - id: 2, - first: 'Jack', - }, - { - id: 1, - first: 'Mike', - }, - ]; - - act(() => apiRef.current.unstable_replaceRows(0, newRows)); - expect(getColumnValues(1)).to.deep.equal(['Jim', 'Jack', 'Mike']); + // wait until the rows are added + await waitFor(() => expect(getRow(10)).not.to.be.undefined); + // The 11th row should be a skeleton + expect(getRow(10).dataset.id).to.equal('auto-generated-skeleton-row-root-0'); + }); }); }); From 46959103a6edf1bbfdc77c88a502781db34c6cd9 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Fri, 4 Oct 2024 11:09:34 +0200 Subject: [PATCH 055/121] Fix test --- .../src/tests/dataSourceLazyLoader.DataGridPro.test.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx index e573abf73a2b2..d78862677cfbc 100644 --- a/packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx @@ -16,7 +16,7 @@ import { SinonSpy, spy } from 'sinon'; const isJSDOM = /jsdom/.test(window.navigator.userAgent); -describe.only(' - Data source lazy loader', () => { +describe(' - Data source lazy loader', () => { const { render } = createRenderer(); const defaultTransformGetRowsResponse = (response: GridGetRowsResponse) => response; @@ -32,6 +32,7 @@ describe.only(' - Data source lazy loader', () => { { useCursorPagination: false, minDelay: 0, maxDelay: 0, verbose: false }, ); fetchRowsSpy = spy(mockServer, 'fetchRows'); + const { fetchRows } = mockServer; const dataSource: GridDataSource = React.useMemo( () => ({ @@ -43,7 +44,7 @@ describe.only(' - Data source lazy loader', () => { lastRowToRender: `${params.end}`, }); - const getRowsResponse = await fetchRowsSpy( + const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, ); @@ -54,7 +55,7 @@ describe.only(' - Data source lazy loader', () => { }; }, }), - [fetchRowsSpy], + [fetchRows], ); const baselineProps = { From 44cb10548507db4b3556ad68757d2f32291f755b Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Fri, 4 Oct 2024 11:50:14 +0200 Subject: [PATCH 056/121] Use default delay for the mock server in the demos --- .../data-grid/server-side-data/ServerSideLazyLoadingInfinite.js | 2 +- .../server-side-data/ServerSideLazyLoadingInfinite.tsx | 2 +- .../server-side-data/ServerSideLazyLoadingModeUpdate.js | 2 +- .../server-side-data/ServerSideLazyLoadingModeUpdate.tsx | 2 +- .../data-grid/server-side-data/ServerSideLazyLoadingViewport.js | 2 +- .../server-side-data/ServerSideLazyLoadingViewport.tsx | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js index 4dca7fac867d9..714e3e71e7a65 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js @@ -5,7 +5,7 @@ import { useMockServer } from '@mui/x-data-grid-generator'; function ServerSideLazyLoadingInfinite() { const { columns, fetchRows } = useMockServer( { rowLength: 100 }, - { useCursorPagination: false, minDelay: 300, maxDelay: 800 }, + { useCursorPagination: false }, ); const dataSource = React.useMemo( diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.tsx index ed4d2ea349997..0fa45cd21b7ba 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.tsx @@ -9,7 +9,7 @@ import { useMockServer } from '@mui/x-data-grid-generator'; function ServerSideLazyLoadingInfinite() { const { columns, fetchRows } = useMockServer( { rowLength: 100 }, - { useCursorPagination: false, minDelay: 300, maxDelay: 800 }, + { useCursorPagination: false }, ); const dataSource: GridDataSource = React.useMemo( diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.js index dca93be3bfc08..a1b4b5948f089 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.js @@ -33,7 +33,7 @@ function GridCustomToolbar({ count, setCount }) { function ServerSideLazyLoadingModeUpdate() { const { columns, fetchRows } = useMockServer( { rowLength: 100 }, - { useCursorPagination: false, minDelay: 300, maxDelay: 800 }, + { useCursorPagination: false }, ); const [rowCount, setRowCount] = React.useState(-1); diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.tsx index e5ec165b33a60..1a83f55569e36 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.tsx @@ -43,7 +43,7 @@ function GridCustomToolbar({ count, setCount }: CustomToolbarProps) { function ServerSideLazyLoadingModeUpdate() { const { columns, fetchRows } = useMockServer( { rowLength: 100 }, - { useCursorPagination: false, minDelay: 300, maxDelay: 800 }, + { useCursorPagination: false }, ); const [rowCount, setRowCount] = React.useState(-1); diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.js index fa1208d75f4eb..113ae48797244 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.js @@ -5,7 +5,7 @@ import { useMockServer } from '@mui/x-data-grid-generator'; function ServerSideLazyLoadingViewport() { const { columns, fetchRows } = useMockServer( { rowLength: 100 }, - { useCursorPagination: false, minDelay: 300, maxDelay: 800 }, + { useCursorPagination: false }, ); const dataSource = React.useMemo( diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.tsx index 90285c8e4cf73..6aa08b1b7b8f5 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.tsx @@ -9,7 +9,7 @@ import { useMockServer } from '@mui/x-data-grid-generator'; function ServerSideLazyLoadingViewport() { const { columns, fetchRows } = useMockServer( { rowLength: 100 }, - { useCursorPagination: false, minDelay: 300, maxDelay: 800 }, + { useCursorPagination: false }, ); const dataSource: GridDataSource = React.useMemo( From 53d1e564cff9cdc641cf4f7112fed65e61011306 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Fri, 4 Oct 2024 12:03:08 +0200 Subject: [PATCH 057/121] Add loading overlay on sorting --- .../features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts | 1 + 1 file changed, 1 insertion(+) 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 4ad9c0f0ea8db..4118a9da9767f 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 @@ -344,6 +344,7 @@ export const useGridDataSourceLazyLoader = ( if (loadingTrigger.current === LoadingTrigger.VIEWPORT) { // replace all rows with skeletons to maintain the same scroll position addSkeletonRows(true); + privateApiRef.current.setLoading(true); } const rangeParams = From c45d270ef83a42932f9f74be620f1aecd95251c0 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Fri, 4 Oct 2024 12:30:13 +0200 Subject: [PATCH 058/121] Put back a bit more delay on the mock server --- .../data-grid/server-side-data/ServerSideLazyLoadingInfinite.js | 2 +- .../server-side-data/ServerSideLazyLoadingInfinite.tsx | 2 +- .../server-side-data/ServerSideLazyLoadingModeUpdate.js | 2 +- .../server-side-data/ServerSideLazyLoadingModeUpdate.tsx | 2 +- .../data-grid/server-side-data/ServerSideLazyLoadingViewport.js | 2 +- .../server-side-data/ServerSideLazyLoadingViewport.tsx | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js index 714e3e71e7a65..4b75c71adff48 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js @@ -5,7 +5,7 @@ import { useMockServer } from '@mui/x-data-grid-generator'; function ServerSideLazyLoadingInfinite() { const { columns, fetchRows } = useMockServer( { rowLength: 100 }, - { useCursorPagination: false }, + { useCursorPagination: false, minDelay: 200, maxDelay: 500 }, ); const dataSource = React.useMemo( diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.tsx index 0fa45cd21b7ba..71c3d1f32dd24 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.tsx @@ -9,7 +9,7 @@ import { useMockServer } from '@mui/x-data-grid-generator'; function ServerSideLazyLoadingInfinite() { const { columns, fetchRows } = useMockServer( { rowLength: 100 }, - { useCursorPagination: false }, + { useCursorPagination: false, minDelay: 200, maxDelay: 500 }, ); const dataSource: GridDataSource = React.useMemo( diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.js index a1b4b5948f089..147004b5eaa64 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.js @@ -33,7 +33,7 @@ function GridCustomToolbar({ count, setCount }) { function ServerSideLazyLoadingModeUpdate() { const { columns, fetchRows } = useMockServer( { rowLength: 100 }, - { useCursorPagination: false }, + { useCursorPagination: false, minDelay: 200, maxDelay: 500 }, ); const [rowCount, setRowCount] = React.useState(-1); diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.tsx index 1a83f55569e36..1c261cef1745c 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.tsx @@ -43,7 +43,7 @@ function GridCustomToolbar({ count, setCount }: CustomToolbarProps) { function ServerSideLazyLoadingModeUpdate() { const { columns, fetchRows } = useMockServer( { rowLength: 100 }, - { useCursorPagination: false }, + { useCursorPagination: false, minDelay: 200, maxDelay: 500 }, ); const [rowCount, setRowCount] = React.useState(-1); diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.js index 113ae48797244..76dd2268d6ca0 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.js @@ -5,7 +5,7 @@ import { useMockServer } from '@mui/x-data-grid-generator'; function ServerSideLazyLoadingViewport() { const { columns, fetchRows } = useMockServer( { rowLength: 100 }, - { useCursorPagination: false }, + { useCursorPagination: false, minDelay: 200, maxDelay: 500 }, ); const dataSource = React.useMemo( diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.tsx index 6aa08b1b7b8f5..a55e9e2857749 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.tsx @@ -9,7 +9,7 @@ import { useMockServer } from '@mui/x-data-grid-generator'; function ServerSideLazyLoadingViewport() { const { columns, fetchRows } = useMockServer( { rowLength: 100 }, - { useCursorPagination: false }, + { useCursorPagination: false, minDelay: 200, maxDelay: 500 }, ); const dataSource: GridDataSource = React.useMemo( From 48d5acfaf16195c24a4850995bbe2f082e2576bd Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Fri, 25 Oct 2024 13:57:17 +0200 Subject: [PATCH 059/121] Update API docs to distinguish client and server side event --- docs/data/data-grid/events/events.json | 2 +- packages/x-data-grid-pro/src/typeOverloads/modules.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/data/data-grid/events/events.json b/docs/data/data-grid/events/events.json index a2831d2974c28..fedd745dd5c45 100644 --- a/docs/data/data-grid/events/events.json +++ b/docs/data/data-grid/events/events.json @@ -227,7 +227,7 @@ { "projects": ["x-data-grid-pro", "x-data-grid-premium"], "name": "fetchRows", - "description": "Fired when a new batch of rows is requested to be loaded. Called with a GridFetchRowsParams object.", + "description": "Fired when a new batch of rows is requested to be loaded. Called with a GridFetchRowsParams object. Used to trigger onFetchRows.", "params": "GridFetchRowsParams", "event": "MuiEvent<{}>", "componentProp": "onFetchRows" diff --git a/packages/x-data-grid-pro/src/typeOverloads/modules.ts b/packages/x-data-grid-pro/src/typeOverloads/modules.ts index 36cd489dfea19..78fb6c2162705 100644 --- a/packages/x-data-grid-pro/src/typeOverloads/modules.ts +++ b/packages/x-data-grid-pro/src/typeOverloads/modules.ts @@ -43,16 +43,17 @@ export interface GridEventLookupPro { rowOrderChange: { params: GridRowOrderChangeParams }; /** * Fired when a new batch of rows is requested to be loaded. Called with a [[GridFetchRowsParams]] object. + * Used to trigger `onFetchRows`. */ fetchRows: { params: GridFetchRowsParams }; // Data source /** - * Fired when the grid needs to fetch a new batch of rows from the data source. + * Fired to make a new request through the data source's `getRows` method. * @ignore - do not document. */ getRows: { params: GridGetRowsParams }; /** - * Fired when the new data is successfully added to the grid either directly from the data source or from the cache + * Fired when the data request is resolved either via the data source or from the cache. * @ignore - do not document. */ rowsFetched: {}; From 278bfecc7523efe5407093ac8c763facb297d032 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 30 Oct 2024 13:40:51 +0100 Subject: [PATCH 060/121] Example updates --- .../server-side-data/ServerSideLazyLoadingInfinite.js | 4 ++-- .../server-side-data/ServerSideLazyLoadingInfinite.tsx | 4 ++-- .../server-side-data/ServerSideLazyLoadingModeUpdate.js | 4 ++-- .../server-side-data/ServerSideLazyLoadingModeUpdate.tsx | 4 ++-- .../server-side-data/ServerSideLazyLoadingViewport.js | 6 +++--- .../server-side-data/ServerSideLazyLoadingViewport.tsx | 6 +++--- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js index 4b75c71adff48..1f65d7ce06ace 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js @@ -3,7 +3,7 @@ import { DataGridPro } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; function ServerSideLazyLoadingInfinite() { - const { columns, fetchRows } = useMockServer( + const { fetchRows, ...props } = useMockServer( { rowLength: 100 }, { useCursorPagination: false, minDelay: 200, maxDelay: 500 }, ); @@ -32,7 +32,7 @@ function ServerSideLazyLoadingInfinite() { return (
Date: Wed, 30 Oct 2024 13:55:22 +0100 Subject: [PATCH 061/121] Remove new label from the sub-page --- docs/data/pages.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/data/pages.ts b/docs/data/pages.ts index 21f40bd7b3323..32026cedadeb1 100644 --- a/docs/data/pages.ts +++ b/docs/data/pages.ts @@ -115,7 +115,6 @@ const pages: MuiPage[] = [ { pathname: '/x/react-data-grid/server-side-data/lazy-loading', plan: 'pro', - newFeature: true, }, { pathname: '/x/react-data-grid/aggregation', plan: 'premium' }, { pathname: '/x/react-data-grid/pivoting', plan: 'premium', planned: true }, From f2f1a3909dc47065a7b1464e65f8658a49408e44 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 30 Oct 2024 13:55:33 +0100 Subject: [PATCH 062/121] Error example updates --- .../server-side-data/ServerSideLazyLoadingErrorHandling.js | 7 ++++--- .../ServerSideLazyLoadingErrorHandling.tsx | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.js index b39e34eec14e6..52d74f5454623 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.js @@ -11,9 +11,10 @@ function ErrorAlert({ onClick }) { void }) { Date: Fri, 1 Nov 2024 15:05:36 +0100 Subject: [PATCH 063/121] put back parentId param to fetchRows --- .../GridDataSourceTreeDataGroupingCell.tsx | 2 +- .../hooks/features/dataSource/interfaces.ts | 16 +++----- .../features/dataSource/useGridDataSource.ts | 40 +++++++------------ .../features/treeData/useGridTreeData.tsx | 2 +- 4 files changed, 23 insertions(+), 37 deletions(-) diff --git a/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx b/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx index 0dd7ce2473530..d5ee7aa79ab5f 100644 --- a/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx +++ b/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx @@ -60,7 +60,7 @@ function GridTreeDataGroupingCellIcon(props: GridTreeDataGroupingCellIconProps) const handleClick = (event: React.MouseEvent) => { if (!rowNode.childrenExpanded) { // always fetch/get from cache the children when the node is expanded - apiRef.current.unstable_dataSource.fetchRows({ parentId: id }); + apiRef.current.unstable_dataSource.fetchRows(id); } else { apiRef.current.setRowChildrenExpansion(id, !rowNode.childrenExpanded); } diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/interfaces.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/interfaces.ts index 073c324eaa704..08eaf7c33f42d 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/interfaces.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/interfaces.ts @@ -1,17 +1,11 @@ import { GridRowId } from '@mui/x-data-grid'; -import { GridDataSourceCache } from '../../../models'; +import { GridDataSourceCache, GridGetRowsParams } from '../../../models'; export interface GridDataSourceState { loading: Record; errors: Record; } -export interface FetchRowsOptions { - parentId?: GridRowId; - start?: number | string; - end?: number; -} - /** * The base data source API interface that is available in the grid [[apiRef]]. */ @@ -29,11 +23,13 @@ export interface GridDataSourceApiBase { */ setChildrenFetchError: (parentId: GridRowId, error: Error | null) => void; /** - * Fetches the rows from the server for with given options. + * Fetches the rows from the server. * If no `parentId` option is provided, it fetches the root rows. - * @param {FetchRowsOptions} options Options that allow setting the specific request params. + * Any missing parameter from `params` will be filled from the state (sorting, filtering, etc.). + * @param {GridRowId} parentId The id of the parent node. + * @param {Partial} params Request parameters override. */ - fetchRows: (options?: GridRowId | FetchRowsOptions) => void; + fetchRows: (parentId?: GridRowId, params?: Partial) => void; /** * The data source cache object. */ diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index a472440d2ee2b..0ff77a931a198 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -6,20 +6,19 @@ import { useGridApiMethod, GridDataSourceGroupNode, useGridSelector, - GridRowId, gridPaginationModelSelector, gridFilteredSortedRowIdsSelector, + GRID_ROOT_GROUP_ID, } from '@mui/x-data-grid'; -import { gridRowGroupsToFetchSelector, GridStateInitializer } from '@mui/x-data-grid/internals'; +import { + GridGetRowsParams, + gridRowGroupsToFetchSelector, + GridStateInitializer, +} from '@mui/x-data-grid/internals'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; import { gridGetRowsParamsSelector, gridDataSourceErrorsSelector } from './gridDataSourceSelector'; -import { - FetchRowsOptions, - GridDataSourceApi, - GridDataSourceApiBase, - GridDataSourcePrivateApi, -} from './interfaces'; +import { GridDataSourceApi, GridDataSourceApiBase, GridDataSourcePrivateApi } from './interfaces'; import { NestedDataManager, RequestStatus, runIf } from './utils'; import { GridDataSourceCache } from '../../../models'; import { GridDataSourceCacheDefault, GridDataSourceCacheDefaultConfig } from './cache'; @@ -92,26 +91,13 @@ export const useGridDataSource = ( }), ); - const fetchRows = React.useCallback( - async (options?: GridRowId | FetchRowsOptions) => { + const fetchRows = React.useCallback( + async (parentId, params) => { const getRows = props.unstable_dataSource?.getRows; if (!getRows) { return; } - const hasDeprecatedArgument = typeof options === 'string' || typeof options === 'number'; - if (hasDeprecatedArgument) { - console.warn( - '`fetchRows` argument should be an options object (`FetchRowsOptions`). `GridRowId` argument is deprecated.', - ); - } - - const parentId = hasDeprecatedArgument || !options ? options : options.parentId; - const fetchParamsOverride = - hasDeprecatedArgument || options?.start === undefined || options?.end === undefined - ? {} - : { start: options.start, end: options.end }; - if (parentId) { nestedDataManager.queue([parentId]); return; @@ -127,7 +113,7 @@ export const useGridDataSource = ( const fetchParams = { ...gridGetRowsParamsSelector(apiRef), ...apiRef.current.unstable_applyPipeProcessors('getRowsParams', {}), - ...fetchParamsOverride, + ...params, }; const startingIndex = typeof fetchParams.start === 'string' @@ -343,7 +329,11 @@ export const useGridDataSource = ( 'paginationModelChange', runIf(props.paginationMode === 'server' && !isLazyLoaded, fetchRows), ); - useGridApiEventHandler(apiRef, 'getRows', runIf(isLazyLoaded, fetchRows)); + useGridApiEventHandler( + apiRef, + 'getRows', + runIf(isLazyLoaded, (params: GridGetRowsParams) => fetchRows(GRID_ROOT_GROUP_ID, params)), + ); const isFirstRender = React.useRef(true); React.useEffect(() => { diff --git a/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeData.tsx b/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeData.tsx index 216df591f6e52..b98528fb36935 100644 --- a/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeData.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeData.tsx @@ -25,7 +25,7 @@ export const useGridTreeData = ( } if (props.unstable_dataSource && !params.rowNode.childrenExpanded) { - apiRef.current.unstable_dataSource.fetchRows({ parentId: params.id }); + apiRef.current.unstable_dataSource.fetchRows(params.id); return; } From 1ef9cbf132ae5fc5dc38aae006f2e0f812f73f1a Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Fri, 1 Nov 2024 15:06:03 +0100 Subject: [PATCH 064/121] Update error example to work with the new fetchRows definition --- .../ServerSideLazyLoadingErrorHandling.js | 13 +++++++++++-- .../ServerSideLazyLoadingErrorHandling.tsx | 7 ++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.js index 52d74f5454623..4eb744761fb52 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.js @@ -1,5 +1,10 @@ import * as React from 'react'; -import { DataGridPro, useGridApiRef, GridToolbar } from '@mui/x-data-grid-pro'; +import { + DataGridPro, + useGridApiRef, + GridToolbar, + GRID_ROOT_GROUP_ID, +} from '@mui/x-data-grid-pro'; import Checkbox from '@mui/material/Checkbox'; import FormControlLabel from '@mui/material/FormControlLabel'; import { useMockServer } from '@mui/x-data-grid-generator'; @@ -76,7 +81,10 @@ function ServerSideLazyLoadingErrorHandling() { {retryParams && ( { - apiRef.current.unstable_dataSource.fetchRows(retryParams); + apiRef.current.unstable_dataSource.fetchRows( + GRID_ROOT_GROUP_ID, + retryParams, + ); setRetryParams(null); }} /> @@ -86,6 +94,7 @@ function ServerSideLazyLoadingErrorHandling() { apiRef={apiRef} unstable_dataSource={dataSource} unstable_onDataSourceError={(_, params) => setRetryParams(params)} + unstable_dataSourceCache={null} lazyLoading paginationModel={{ page: 0, pageSize: 10 }} slots={{ toolbar: GridToolbar }} diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.tsx index f06d9eb503f09..71b102ae2c9c1 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.tsx @@ -5,6 +5,7 @@ import { GridToolbar, GridDataSource, GridGetRowsParams, + GRID_ROOT_GROUP_ID, } from '@mui/x-data-grid-pro'; import Checkbox from '@mui/material/Checkbox'; import FormControlLabel from '@mui/material/FormControlLabel'; @@ -84,7 +85,10 @@ function ServerSideLazyLoadingErrorHandling() { {retryParams && ( { - apiRef.current.unstable_dataSource.fetchRows(retryParams); + apiRef.current.unstable_dataSource.fetchRows( + GRID_ROOT_GROUP_ID, + retryParams, + ); setRetryParams(null); }} /> @@ -94,6 +98,7 @@ function ServerSideLazyLoadingErrorHandling() { apiRef={apiRef} unstable_dataSource={dataSource} unstable_onDataSourceError={(_, params) => setRetryParams(params)} + unstable_dataSourceCache={null} lazyLoading paginationModel={{ page: 0, pageSize: 10 }} slots={{ toolbar: GridToolbar }} From 344c860d8795fae3ef27e2986d99b1e616152921 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Fri, 1 Nov 2024 15:25:39 +0100 Subject: [PATCH 065/121] Fix check parent condition --- .../src/hooks/features/dataSource/useGridDataSource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index 0ff77a931a198..7a51b687f1dfa 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -98,7 +98,7 @@ export const useGridDataSource = ( return; } - if (parentId) { + if (parentId && parentId !== GRID_ROOT_GROUP_ID) { nestedDataManager.queue([parentId]); return; } From 3312610497cb6a74cc075e3a0fb97bae62d34ef0 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Mon, 4 Nov 2024 08:07:45 +0100 Subject: [PATCH 066/121] Update API --- scripts/x-data-grid-premium.exports.json | 1 - scripts/x-data-grid-pro.exports.json | 1 - 2 files changed, 2 deletions(-) diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index a0e1ff0dde41c..9f0316969cd44 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -40,7 +40,6 @@ { "name": "ElementSize", "kind": "Interface" }, { "name": "EMPTY_PINNED_COLUMN_FIELDS", "kind": "Variable" }, { "name": "EMPTY_RENDER_CONTEXT", "kind": "Variable" }, - { "name": "FetchRowsOptions", "kind": "Interface" }, { "name": "FilterColumnsArgs", "kind": "Interface" }, { "name": "FilterPanelPropsOverrides", "kind": "Interface" }, { "name": "FocusElement", "kind": "Interface" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index 6f20393ae95b6..33ec9624fe2c5 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -39,7 +39,6 @@ { "name": "ElementSize", "kind": "Interface" }, { "name": "EMPTY_PINNED_COLUMN_FIELDS", "kind": "Variable" }, { "name": "EMPTY_RENDER_CONTEXT", "kind": "Variable" }, - { "name": "FetchRowsOptions", "kind": "Interface" }, { "name": "FilterColumnsArgs", "kind": "Interface" }, { "name": "FilterPanelPropsOverrides", "kind": "Interface" }, { "name": "FocusElement", "kind": "Interface" }, From 4783c8969fc25d6661bbb4065408bc60da54265f Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 6 Nov 2024 14:50:38 +0100 Subject: [PATCH 067/121] Add data source row update strategy --- .../src/hooks/features/dataSource/utils.ts | 5 +++++ .../gridStrategyProcessingApi.ts | 14 ++++++++++++++ .../useGridStrategyProcessing.ts | 6 ++---- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts index 978f7828bd155..7d48411bb5ac2 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts @@ -16,6 +16,11 @@ export enum RequestStatus { UNKNOWN, } +export enum DataSourceRowsUpdateStrategy { + Default = 'set-new-rows', + LazyLoading = 'replace-row-range', +} + /** * Fetches row children from the server with option to limit the number of concurrent requests * Determines the status of a request based on the enum `RequestStatus` diff --git a/packages/x-data-grid/src/hooks/core/strategyProcessing/gridStrategyProcessingApi.ts b/packages/x-data-grid/src/hooks/core/strategyProcessing/gridStrategyProcessingApi.ts index ef4094d3887fb..6ad1fb8134e9b 100644 --- a/packages/x-data-grid/src/hooks/core/strategyProcessing/gridStrategyProcessingApi.ts +++ b/packages/x-data-grid/src/hooks/core/strategyProcessing/gridStrategyProcessingApi.ts @@ -13,6 +13,7 @@ import { GridSortingMethodParams, GridSortingMethodValue, } from '../../features/sorting/gridSortingState'; +import { GridGetRowsParams, GridGetRowsResponse } from '../../../models/gridDataSource'; export type GridStrategyProcessorName = keyof GridStrategyProcessingLookup; @@ -20,6 +21,19 @@ export type GridStrategyGroup = GridStrategyProcessingLookup[keyof GridStrategyProcessingLookup]['group']; export interface GridStrategyProcessingLookup { + dataSourceRowsUpdate: { + group: 'dataSource'; + params: + | { + response: GridGetRowsResponse; + fetchParams: GridGetRowsParams; + } + | { + error: Error; + fetchParams: GridGetRowsParams; + }; + value: void; + }; rowTreeCreation: { group: 'rowTree'; params: GridRowTreeCreationParams; diff --git a/packages/x-data-grid/src/hooks/core/strategyProcessing/useGridStrategyProcessing.ts b/packages/x-data-grid/src/hooks/core/strategyProcessing/useGridStrategyProcessing.ts index 0dbe135f4d75d..69d88c9a163c9 100644 --- a/packages/x-data-grid/src/hooks/core/strategyProcessing/useGridStrategyProcessing.ts +++ b/packages/x-data-grid/src/hooks/core/strategyProcessing/useGridStrategyProcessing.ts @@ -14,6 +14,7 @@ export const GRID_DEFAULT_STRATEGY = 'none'; export const GRID_STRATEGIES_PROCESSORS: { [P in GridStrategyProcessorName]: GridStrategyProcessingLookup[P]['group']; } = { + dataSourceRowsUpdate: 'dataSource', rowTreeCreation: 'rowTree', filtering: 'rowTree', sorting: 'rowTree', @@ -59,10 +60,7 @@ type UntypedStrategyProcessors = { * ===================================================================================================================== * * Each processor name is part of a strategy group which can only have one active strategy at the time. - * For now, there is only one strategy group named `rowTree` which customize - * - row tree creation algorithm. - * - sorting algorithm. - * - filtering algorithm. + * For now, there are two groupes named `rowTree` and `dataSource`. */ export const useGridStrategyProcessing = (apiRef: React.MutableRefObject) => { const availableStrategies = React.useRef( From 7560bdcc743a4215f4f597921bb62b896bd71e83 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 6 Nov 2024 15:51:22 +0100 Subject: [PATCH 068/121] Fix docs warning --- .../data/data-grid/server-side-data/ServerSideDataGridNoCache.js | 1 + .../data-grid/server-side-data/ServerSideDataGridNoCache.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js index 43c742a5b90a1..e598eb38982ed 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js @@ -38,6 +38,7 @@ export default function ServerSideDataGridNoCache() { ...initialState, pagination: { paginationModel: { pageSize: 10, page: 0 }, + rowCount: 0, }, }), [initialState], diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx index b62606d8985f0..19a578b73a8c1 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx @@ -38,6 +38,7 @@ export default function ServerSideDataGridNoCache() { ...initialState, pagination: { paginationModel: { pageSize: 10, page: 0 }, + rowCount: 0, }, }), [initialState], From 23c781946ba2cf5387dd884f82969e2b82a214ca Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 6 Nov 2024 15:56:57 +0100 Subject: [PATCH 069/121] Data source and lazy loading hook are using new strategy group to determine how the data should be processed --- .../features/dataSource/useGridDataSource.ts | 129 +++++++++----- .../useGridDataSourceLazyLoader.ts | 159 +++++++++++++----- 2 files changed, 201 insertions(+), 87 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index 7a51b687f1dfa..dd9ce54b2acd7 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -2,24 +2,26 @@ import * as React from 'react'; import useLazyRef from '@mui/utils/useLazyRef'; import { useGridApiEventHandler, - gridRowsLoadingSelector, useGridApiMethod, GridDataSourceGroupNode, useGridSelector, gridPaginationModelSelector, - gridFilteredSortedRowIdsSelector, GRID_ROOT_GROUP_ID, + useFirstRender, + GridEventListener, } from '@mui/x-data-grid'; import { GridGetRowsParams, gridRowGroupsToFetchSelector, GridStateInitializer, + GridStrategyProcessor, + useGridRegisterStrategyProcessor, } from '@mui/x-data-grid/internals'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; import { gridGetRowsParamsSelector, gridDataSourceErrorsSelector } from './gridDataSourceSelector'; import { GridDataSourceApi, GridDataSourceApiBase, GridDataSourcePrivateApi } from './interfaces'; -import { NestedDataManager, RequestStatus, runIf } from './utils'; +import { DataSourceRowsUpdateStrategy, NestedDataManager, RequestStatus, runIf } from './utils'; import { GridDataSourceCache } from '../../../models'; import { GridDataSourceCacheDefault, GridDataSourceCacheDefaultConfig } from './cache'; @@ -66,15 +68,23 @@ export const useGridDataSource = ( | 'lazyLoading' >, ) => { + const setStrategyAvailability = React.useCallback(() => { + apiRef.current.setStrategyAvailability( + 'dataSource', + DataSourceRowsUpdateStrategy.Default, + props.unstable_dataSource && !props.lazyLoading ? () => true : () => false, + ); + }, [apiRef, props.lazyLoading, props.unstable_dataSource]); + + const [defaultRowsUpdateStrategyActive, setDefaultRowsUpdateStrategyActive] = + React.useState(false); const nestedDataManager = useLazyRef( () => new NestedDataManager(apiRef), ).current; const paginationModel = useGridSelector(apiRef, gridPaginationModelSelector); const groupsToAutoFetch = useGridSelector(apiRef, gridRowGroupsToFetchSelector); - const filteredSortedRowIds = useGridSelector(apiRef, gridFilteredSortedRowIdsSelector); const scheduledGroups = React.useRef(0); - const isLazyLoaded = !!props.unstable_dataSource && props.lazyLoading; const onError = props.unstable_onDataSourceError; const cacheChunkSize = React.useMemo(() => { @@ -115,58 +125,46 @@ export const useGridDataSource = ( ...apiRef.current.unstable_applyPipeProcessors('getRowsParams', {}), ...params, }; - const startingIndex = - typeof fetchParams.start === 'string' - ? Math.max(filteredSortedRowIds.indexOf(fetchParams.start), 0) - : fetchParams.start; + const cachedData = apiRef.current.unstable_dataSource.cache.get(fetchParams); if (cachedData !== undefined) { - const rows = cachedData.rows; - apiRef.current.setRowCount(cachedData.rowCount === undefined ? -1 : cachedData.rowCount); - if (isLazyLoaded) { - apiRef.current.unstable_replaceRows(startingIndex, rows); - } else { - apiRef.current.setRows(rows); - } - apiRef.current.publishEvent('rowsFetched'); + apiRef.current.applyStrategyProcessor('dataSourceRowsUpdate', { + response: cachedData, + fetchParams, + }); return; } - // with lazy loading, only the initial load should show the loading overlay - const useLoadingIndicator = !isLazyLoaded || apiRef.current.getRowsCount() === 0; - const isLoading = gridRowsLoadingSelector(apiRef); - if (!isLoading && useLoadingIndicator) { + // Manage loading state only for the default strategy + if (defaultRowsUpdateStrategyActive || apiRef.current.getRowsCount() === 0) { apiRef.current.setLoading(true); } try { const getRowsResponse = await getRows(fetchParams); apiRef.current.unstable_dataSource.cache.set(fetchParams, getRowsResponse); - apiRef.current.setRowCount( - getRowsResponse.rowCount === undefined ? -1 : getRowsResponse.rowCount, - ); - if (isLazyLoaded) { - apiRef.current.unstable_replaceRows(startingIndex, getRowsResponse.rows); - } else { - apiRef.current.setRows(getRowsResponse.rows); - } - apiRef.current.setLoading(false); - apiRef.current.publishEvent('rowsFetched'); + apiRef.current.applyStrategyProcessor('dataSourceRowsUpdate', { + response: getRowsResponse, + fetchParams, + }); } catch (error) { - if (!isLazyLoaded) { - apiRef.current.setRows([]); - } - apiRef.current.setLoading(false); + apiRef.current.applyStrategyProcessor('dataSourceRowsUpdate', { + error: error as Error, + fetchParams, + }); onError?.(error as Error, fetchParams); + } finally { + if (defaultRowsUpdateStrategyActive) { + apiRef.current.setLoading(false); + } } }, [ nestedDataManager, apiRef, + defaultRowsUpdateStrategyActive, props.unstable_dataSource?.getRows, - isLazyLoaded, - filteredSortedRowIds, onError, ], ); @@ -288,6 +286,31 @@ export const useGridDataSource = ( [apiRef], ); + const handleStrategyActivityChange = React.useCallback< + GridEventListener<'strategyAvailabilityChange'> + >(() => { + setDefaultRowsUpdateStrategyActive( + apiRef.current.getActiveStrategy('dataSource') === DataSourceRowsUpdateStrategy.Default, + ); + }, [apiRef]); + + const handleDataUpdate = React.useCallback>( + (params) => { + if ('error' in params) { + apiRef.current.setRows([]); + return; + } + + const { response } = params; + if (response.rowCount !== undefined) { + apiRef.current.setRowCount(response.rowCount); + } + apiRef.current.setRows(response.rows); + apiRef.current.publishEvent('rowsFetched'); + }, + [apiRef], + ); + const resetDataSourceState = React.useCallback(() => { apiRef.current.setState((state) => { return { @@ -314,25 +337,31 @@ export const useGridDataSource = ( useGridApiMethod(apiRef, dataSourceApi, 'public'); useGridApiMethod(apiRef, dataSourcePrivateApi, 'private'); + useGridRegisterStrategyProcessor( + apiRef, + DataSourceRowsUpdateStrategy.Default, + 'dataSourceRowsUpdate', + handleDataUpdate, + ); + + useGridApiEventHandler(apiRef, 'strategyAvailabilityChange', handleStrategyActivityChange); useGridApiEventHandler( apiRef, 'sortModelChange', - runIf(props.sortingMode === 'server' && !isLazyLoaded, fetchRows), + runIf(defaultRowsUpdateStrategyActive, () => fetchRows()), ); useGridApiEventHandler( apiRef, 'filterModelChange', - runIf(props.filterMode === 'server' && !isLazyLoaded, fetchRows), + runIf(defaultRowsUpdateStrategyActive, () => fetchRows()), ); useGridApiEventHandler( apiRef, 'paginationModelChange', - runIf(props.paginationMode === 'server' && !isLazyLoaded, fetchRows), + runIf(defaultRowsUpdateStrategyActive, () => fetchRows()), ); - useGridApiEventHandler( - apiRef, - 'getRows', - runIf(isLazyLoaded, (params: GridGetRowsParams) => fetchRows(GRID_ROOT_GROUP_ID, params)), + useGridApiEventHandler(apiRef, 'getRows', (params: GridGetRowsParams) => + fetchRows(GRID_ROOT_GROUP_ID, params), ); const isFirstRender = React.useRef(true); @@ -347,6 +376,14 @@ export const useGridDataSource = ( setCache((prevCache) => (prevCache !== newCache ? newCache : prevCache)); }, [props.unstable_dataSourceCache, cacheChunkSize]); + React.useEffect(() => { + if (!isFirstRender.current) { + setStrategyAvailability(); + } else { + isFirstRender.current = false; + } + }, [setStrategyAvailability]); + React.useEffect(() => { if (props.unstable_dataSource) { apiRef.current.unstable_dataSource.cache.clear(); @@ -365,4 +402,8 @@ export const useGridDataSource = ( scheduledGroups.current = groupsToAutoFetch.length; } }, [apiRef, nestedDataManager, groupsToAutoFetch]); + + useFirstRender(() => { + setStrategyAvailability(); + }); }; 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 4118a9da9767f..d12ea92ca1b0d 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 @@ -11,16 +11,21 @@ import { GridSkeletonRowNode, gridPaginationModelSelector, gridDimensionsSelector, + gridFilteredSortedRowIdsSelector, + useFirstRender, } from '@mui/x-data-grid'; import { getVisibleRows, GridGetRowsParams, gridRenderContextSelector, + GridStrategyProcessor, + useGridRegisterStrategyProcessor, } from '@mui/x-data-grid/internals'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; import { findSkeletonRowsSection } from '../lazyLoader/utils'; import { GRID_SKELETON_ROW_ROOT_ID } from '../lazyLoader/useGridLazyLoaderPreProcessors'; +import { DataSourceRowsUpdateStrategy, runIf } from '../dataSource/utils'; enum LoadingTrigger { VIEWPORT, @@ -52,15 +57,26 @@ export const useGridDataSourceLazyLoader = ( | 'scrollEndThreshold' >, ): void => { + const setStrategyAvailability = React.useCallback(() => { + privateApiRef.current.setStrategyAvailability( + 'dataSource', + DataSourceRowsUpdateStrategy.LazyLoading, + props.unstable_dataSource && props.lazyLoading ? () => true : () => false, + ); + }, [privateApiRef, props.lazyLoading, props.unstable_dataSource]); + + 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 renderContext = useGridSelector(privateApiRef, gridRenderContextSelector); const renderedRowsIntervalCache = React.useRef(INTERVAL_CACHE_INITIAL_STATE); const previousLastRowIndex = React.useRef(0); const loadingTrigger = React.useRef(null); - const isDisabled = !props.unstable_dataSource || props.lazyLoading !== true; + const rowsStale = React.useRef(false); const heights = React.useMemo( () => ({ @@ -88,9 +104,9 @@ export const useGridDataSourceLazyLoader = ( ); const resetGrid = React.useCallback(() => { - privateApiRef.current.setRows([]); privateApiRef.current.setLoading(true); privateApiRef.current.unstable_dataSource.cache.clear(); + rowsStale.current = true; previousLastRowIndex.current = 0; const getRowsParams: GridGetRowsParams = { start: 0, @@ -203,34 +219,53 @@ export const useGridDataSourceLazyLoader = ( [ensureValidRowCount], ); - const handleDataUpdate = React.useCallback(() => { - if (isDisabled) { - return; - } + const handleDataUpdate = React.useCallback>( + (params) => { + if ('error' in params) { + return; + } - if (loadingTrigger.current === null) { - updateLoadingTrigger(privateApiRef.current.state.pagination.rowCount); - } + const { response, fetchParams } = params; + privateApiRef.current.setRowCount(response.rowCount === undefined ? -1 : response.rowCount); - addSkeletonRows(); - privateApiRef.current.state.rows.loading = false; - privateApiRef.current.requestPipeProcessorsApplication('hydrateRows'); - }, [privateApiRef, isDisabled, updateLoadingTrigger, addSkeletonRows]); + if (rowsStale.current) { + rowsStale.current = false; + privateApiRef.current.scroll({ top: 0 }); + privateApiRef.current.setRows(response.rows); + } else { + const startingIndex = + typeof fetchParams.start === 'string' + ? Math.max(filteredSortedRowIds.indexOf(fetchParams.start), 0) + : fetchParams.start; + + privateApiRef.current.unstable_replaceRows(startingIndex, response.rows); + } + + if (loadingTrigger.current === null) { + updateLoadingTrigger(privateApiRef.current.state.pagination.rowCount); + } + + addSkeletonRows(); + privateApiRef.current.setLoading(false); + privateApiRef.current.requestPipeProcessorsApplication('hydrateRows'); + privateApiRef.current.publishEvent('rowsFetched'); + }, + [privateApiRef, filteredSortedRowIds, updateLoadingTrigger, addSkeletonRows], + ); const handleRowCountChange = React.useCallback(() => { - if (isDisabled || loadingTrigger.current === null) { + if (loadingTrigger.current === null) { return; } updateLoadingTrigger(privateApiRef.current.state.pagination.rowCount); addSkeletonRows(); privateApiRef.current.requestPipeProcessorsApplication('hydrateRows'); - }, [privateApiRef, isDisabled, updateLoadingTrigger, addSkeletonRows]); + }, [privateApiRef, updateLoadingTrigger, addSkeletonRows]); const handleScrolling: GridEventListener<'scrollPositionChange'> = React.useCallback( (newScrollPosition) => { if ( - isDisabled || loadingTrigger.current !== LoadingTrigger.SCROLL_END || previousLastRowIndex.current >= renderContext.lastRowIndex ) { @@ -257,7 +292,6 @@ export const useGridDataSourceLazyLoader = ( [ privateApiRef, props.scrollEndThreshold, - isDisabled, sortModel, filterModel, heights, @@ -271,7 +305,7 @@ export const useGridDataSourceLazyLoader = ( GridEventListener<'renderedRowsIntervalChange'> >( (params) => { - if (isDisabled || loadingTrigger.current !== LoadingTrigger.VIEWPORT) { + if (loadingTrigger.current !== LoadingTrigger.VIEWPORT) { return; } @@ -319,7 +353,6 @@ export const useGridDataSourceLazyLoader = ( }, [ privateApiRef, - isDisabled, props.pagination, props.paginationMode, sortModel, @@ -335,18 +368,7 @@ export const useGridDataSourceLazyLoader = ( const handleGridSortModelChange = React.useCallback>( (newSortModel) => { - if (isDisabled) { - return; - } - - privateApiRef.current.setRows([]); previousLastRowIndex.current = 0; - if (loadingTrigger.current === LoadingTrigger.VIEWPORT) { - // replace all rows with skeletons to maintain the same scroll position - addSkeletonRows(true); - privateApiRef.current.setLoading(true); - } - const rangeParams = loadingTrigger.current === LoadingTrigger.VIEWPORT ? { @@ -364,11 +386,19 @@ export const useGridDataSourceLazyLoader = ( filterModel, }; + privateApiRef.current.setLoading(true); + if (loadingTrigger.current === LoadingTrigger.VIEWPORT) { + // replace all rows with skeletons to maintain the same scroll position + privateApiRef.current.setRows([]); + addSkeletonRows(true); + } else { + rowsStale.current = true; + } + privateApiRef.current.publishEvent('getRows', adjustRowParams(getRowsParams)); }, [ privateApiRef, - isDisabled, filterModel, paginationModel.pageSize, renderContext, @@ -379,11 +409,7 @@ export const useGridDataSourceLazyLoader = ( const handleGridFilterModelChange = React.useCallback>( (newFilterModel) => { - if (isDisabled) { - return; - } - - privateApiRef.current.setRows([]); + rowsStale.current = true; previousLastRowIndex.current = 0; const getRowsParams: GridGetRowsParams = { start: 0, @@ -392,19 +418,66 @@ export const useGridDataSourceLazyLoader = ( filterModel: newFilterModel, }; + privateApiRef.current.setLoading(true); privateApiRef.current.publishEvent('getRows', getRowsParams); }, - [privateApiRef, isDisabled, sortModel, paginationModel.pageSize], + [privateApiRef, sortModel, paginationModel.pageSize], ); - useGridApiEventHandler(privateApiRef, 'rowsFetched', handleDataUpdate); - useGridApiEventHandler(privateApiRef, 'rowCountChange', handleRowCountChange); - useGridApiEventHandler(privateApiRef, 'scrollPositionChange', handleScrolling); + const handleStrategyActivityChange = React.useCallback< + GridEventListener<'strategyAvailabilityChange'> + >(() => { + setLazyLoadingRowsUpdateStrategyActive( + privateApiRef.current.getActiveStrategy('dataSource') === + DataSourceRowsUpdateStrategy.LazyLoading, + ); + }, [privateApiRef]); + + useGridRegisterStrategyProcessor( + privateApiRef, + DataSourceRowsUpdateStrategy.LazyLoading, + 'dataSourceRowsUpdate', + handleDataUpdate, + ); + + useGridApiEventHandler(privateApiRef, 'strategyAvailabilityChange', handleStrategyActivityChange); + + useGridApiEventHandler( + privateApiRef, + 'rowCountChange', + runIf(lazyLoadingRowsUpdateStrategyActive, handleRowCountChange), + ); + useGridApiEventHandler( + privateApiRef, + 'scrollPositionChange', + runIf(lazyLoadingRowsUpdateStrategyActive, handleScrolling), + ); useGridApiEventHandler( privateApiRef, 'renderedRowsIntervalChange', - throttledHandleRenderedRowsIntervalChange, + runIf(lazyLoadingRowsUpdateStrategyActive, throttledHandleRenderedRowsIntervalChange), + ); + useGridApiEventHandler( + privateApiRef, + 'sortModelChange', + runIf(lazyLoadingRowsUpdateStrategyActive, handleGridSortModelChange), ); - useGridApiEventHandler(privateApiRef, 'sortModelChange', handleGridSortModelChange); - useGridApiEventHandler(privateApiRef, 'filterModelChange', handleGridFilterModelChange); + useGridApiEventHandler( + privateApiRef, + 'filterModelChange', + runIf(lazyLoadingRowsUpdateStrategyActive, handleGridFilterModelChange), + ); + + useFirstRender(() => { + setStrategyAvailability(); + }); + + const isFirstRender = React.useRef(true); + React.useEffect(() => { + if (!isFirstRender.current) { + setStrategyAvailability(); + } else { + isFirstRender.current = false; + } + }, [setStrategyAvailability]); }; From 76ef289e3c5bdf203be6a421208eb6d92234fd1c Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 6 Nov 2024 16:09:13 +0100 Subject: [PATCH 070/121] Fix pages rebase --- docs/data/pages.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/docs/data/pages.ts b/docs/data/pages.ts index 32026cedadeb1..65b21f66e4a1a 100644 --- a/docs/data/pages.ts +++ b/docs/data/pages.ts @@ -124,11 +124,31 @@ const pages: MuiPage[] = [ { pathname: '/x/react-data-grid/server-side-data/row-grouping', plan: 'premium', + children: [ + { pathname: '/x/react-data-grid/row-grouping', title: 'Overview' }, + { + pathname: '/x/react-data-grid/recipes-row-grouping', + title: 'Recipes', + }, + ], + }, + { pathname: '/x/react-data-grid/aggregation', plan: 'premium' }, + { pathname: '/x/react-data-grid/pivoting', plan: 'premium', planned: true }, + { pathname: '/x/react-data-grid/export' }, + { pathname: '/x/react-data-grid/clipboard', title: 'Copy and paste', newFeature: true }, + { pathname: '/x/react-data-grid/scrolling' }, + + { + pathname: '/x/react-data-grid/list-view', + title: 'List view', + plan: 'pro', + unstable: true, }, { pathname: '/x/react-data-grid/server-side-data-group', title: 'Server-side data', plan: 'pro', + newFeature: true, children: [ { pathname: '/x/react-data-grid/server-side-data', @@ -148,7 +168,6 @@ const pages: MuiPage[] = [ { pathname: '/x/react-data-grid/server-side-data/row-grouping', plan: 'premium', - unstable: true, }, { pathname: '/x/react-data-grid/server-side-data/aggregation', From 6408977d79e41d525ef7f64ef2f89ca6ce717e0d Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 6 Nov 2024 16:17:52 +0100 Subject: [PATCH 071/121] Remove unnecessary variable --- .../useGridDataSourceLazyLoader.ts | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) 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 d12ea92ca1b0d..2fc27b4a0cb51 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 @@ -78,14 +78,6 @@ export const useGridDataSourceLazyLoader = ( const loadingTrigger = React.useRef(null); const rowsStale = React.useRef(false); - const heights = React.useMemo( - () => ({ - viewport: dimensions.viewportInnerSize.height, - content: dimensions.contentSize.height, - }), - [dimensions.viewportInnerSize.height, dimensions.contentSize.height], - ); - // Adjust the render context range to fit the pagination model's page size // First row index should be decreased to the start of the page, end row index should be increased to the end of the page const adjustRowParams = React.useCallback( @@ -272,8 +264,8 @@ export const useGridDataSourceLazyLoader = ( return; } - const position = newScrollPosition.top + heights.viewport; - const target = heights.content - props.scrollEndThreshold; + const position = newScrollPosition.top + dimensions.viewportInnerSize.height; + const target = dimensions.contentSize.height - props.scrollEndThreshold; if (position >= target) { previousLastRowIndex.current = renderContext.lastRowIndex; @@ -294,7 +286,7 @@ export const useGridDataSourceLazyLoader = ( props.scrollEndThreshold, sortModel, filterModel, - heights, + dimensions, paginationModel.pageSize, renderContext.lastRowIndex, adjustRowParams, From bdf5b6e940c45b4dd369052fbb097fffbf048e1d Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 6 Nov 2024 16:33:21 +0100 Subject: [PATCH 072/121] Update start and end query param names in the mock server --- .../ServerSideLazyLoadingErrorHandling.js | 4 ++-- .../ServerSideLazyLoadingErrorHandling.tsx | 4 ++-- .../ServerSideLazyLoadingInfinite.js | 4 ++-- .../ServerSideLazyLoadingInfinite.tsx | 4 ++-- .../ServerSideLazyLoadingModeUpdate.js | 4 ++-- .../ServerSideLazyLoadingModeUpdate.tsx | 4 ++-- .../ServerSideLazyLoadingViewport.js | 4 ++-- .../ServerSideLazyLoadingViewport.tsx | 4 ++-- .../src/hooks/serverUtils.ts | 16 ++++++------- .../dataSourceLazyLoader.DataGridPro.test.tsx | 24 +++++++++---------- 10 files changed, 35 insertions(+), 37 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.js index 4eb744761fb52..d1bf5aec72363 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.js @@ -51,8 +51,8 @@ function ServerSideLazyLoadingErrorHandling() { const urlParams = new URLSearchParams({ filterModel: JSON.stringify(params.filterModel), sortModel: JSON.stringify(params.sortModel), - firstRowToRender: `${params.start}`, - lastRowToRender: `${params.end}`, + start: `${params.start}`, + end: `${params.end}`, }); const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.tsx index 71b102ae2c9c1..54c14b32f046b 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.tsx @@ -55,8 +55,8 @@ function ServerSideLazyLoadingErrorHandling() { const urlParams = new URLSearchParams({ filterModel: JSON.stringify(params.filterModel), sortModel: JSON.stringify(params.sortModel), - firstRowToRender: `${params.start}`, - lastRowToRender: `${params.end}`, + start: `${params.start}`, + end: `${params.end}`, }); const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js index 1f65d7ce06ace..c2a75162e751a 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js @@ -14,8 +14,8 @@ function ServerSideLazyLoadingInfinite() { const urlParams = new URLSearchParams({ filterModel: JSON.stringify(params.filterModel), sortModel: JSON.stringify(params.sortModel), - firstRowToRender: `${params.start}`, - lastRowToRender: `${params.end}`, + start: `${params.start}`, + end: `${params.end}`, }); const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.tsx index bf1b8182f8d82..05b5486df6a89 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.tsx @@ -18,8 +18,8 @@ function ServerSideLazyLoadingInfinite() { const urlParams = new URLSearchParams({ filterModel: JSON.stringify(params.filterModel), sortModel: JSON.stringify(params.sortModel), - firstRowToRender: `${params.start}`, - lastRowToRender: `${params.end}`, + start: `${params.start}`, + end: `${params.end}`, }); const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.js index 8f25d19f51d75..dc1204f74f02c 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.js @@ -44,8 +44,8 @@ function ServerSideLazyLoadingModeUpdate() { const urlParams = new URLSearchParams({ filterModel: JSON.stringify(params.filterModel), sortModel: JSON.stringify(params.sortModel), - firstRowToRender: `${params.start}`, - lastRowToRender: `${params.end}`, + start: `${params.start}`, + end: `${params.end}`, }); const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.tsx index e837bc46513ff..f233412666331 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.tsx @@ -54,8 +54,8 @@ function ServerSideLazyLoadingModeUpdate() { const urlParams = new URLSearchParams({ filterModel: JSON.stringify(params.filterModel), sortModel: JSON.stringify(params.sortModel), - firstRowToRender: `${params.start}`, - lastRowToRender: `${params.end}`, + start: `${params.start}`, + end: `${params.end}`, }); const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.js index ba8853b8b64e1..88fe9c31794aa 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.js @@ -14,8 +14,8 @@ function ServerSideLazyLoadingViewport() { const urlParams = new URLSearchParams({ filterModel: JSON.stringify(params.filterModel), sortModel: JSON.stringify(params.sortModel), - firstRowToRender: `${params.start}`, - lastRowToRender: `${params.end}`, + start: `${params.start}`, + end: `${params.end}`, }); const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.tsx index f169ae57120b7..1d6c08815ec87 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.tsx @@ -18,8 +18,8 @@ function ServerSideLazyLoadingViewport() { const urlParams = new URLSearchParams({ filterModel: JSON.stringify(params.filterModel), sortModel: JSON.stringify(params.sortModel), - firstRowToRender: `${params.start}`, - lastRowToRender: `${params.end}`, + start: `${params.start}`, + end: `${params.end}`, }); const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, diff --git a/packages/x-data-grid-generator/src/hooks/serverUtils.ts b/packages/x-data-grid-generator/src/hooks/serverUtils.ts index 30418ea503806..c0643d608e923 100644 --- a/packages/x-data-grid-generator/src/hooks/serverUtils.ts +++ b/packages/x-data-grid-generator/src/hooks/serverUtils.ts @@ -40,8 +40,8 @@ export interface QueryOptions { pageSize?: number; filterModel?: GridFilterModel; sortModel?: GridSortModel; - firstRowToRender?: number; - lastRowToRender?: number; + start?: number; + end?: number; } export interface ServerSideQueryOptions { @@ -50,8 +50,8 @@ export interface ServerSideQueryOptions { groupKeys?: string[]; filterModel?: GridFilterModel; sortModel?: GridSortModel; - firstRowToRender?: number; - lastRowToRender?: number; + start?: number; + end?: number; groupFields?: string[]; } @@ -277,7 +277,7 @@ export const loadServerRows = ( } const delay = randomInt(minDelay, maxDelay); - const { cursor, page = 0, pageSize, firstRowToRender, lastRowToRender } = queryOptions; + const { cursor, page = 0, pageSize, start, end } = queryOptions; let nextCursor; let firstRowIndex; @@ -289,9 +289,9 @@ export const loadServerRows = ( filteredRows = [...filteredRows].sort(rowComparator); const totalRowCount = filteredRows.length; - if (firstRowToRender !== undefined && lastRowToRender !== undefined) { - firstRowIndex = firstRowToRender; - lastRowIndex = lastRowToRender; + if (start !== undefined && end !== undefined) { + firstRowIndex = start; + lastRowIndex = end; } else if (!pageSize) { firstRowIndex = 0; lastRowIndex = filteredRows.length - 1; diff --git a/packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx index d78862677cfbc..3625ea2d6f557 100644 --- a/packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx @@ -40,8 +40,8 @@ describe(' - Data source lazy loader', () => { const urlParams = new URLSearchParams({ filterModel: JSON.stringify(params.filterModel), sortModel: JSON.stringify(params.sortModel), - firstRowToRender: `${params.start}`, - lastRowToRender: `${params.end}`, + start: `${params.start}`, + end: `${params.end}`, }); const getRowsResponse = await fetchRows( @@ -119,7 +119,7 @@ describe(' - Data source lazy loader', () => { await waitFor(() => expect(getRow(0)).not.to.be.undefined); const initialSearchParams = new URL(fetchRowsSpy.lastCall.args[0]).searchParams; - expect(initialSearchParams.get('lastRowToRender')).to.equal('9'); + expect(initialSearchParams.get('end')).to.equal('9'); apiRef.current.scrollToIndexes({ rowIndex: 10 }); @@ -128,7 +128,7 @@ describe(' - Data source lazy loader', () => { }); const beforeSortSearchParams = new URL(fetchRowsSpy.lastCall.args[0]).searchParams; - expect(beforeSortSearchParams.get('lastRowToRender')).to.not.equal('9'); + expect(beforeSortSearchParams.get('end')).to.not.equal('9'); apiRef.current.sortColumn(mockServer.columns[0].field, 'asc'); @@ -137,9 +137,7 @@ describe(' - Data source lazy loader', () => { }); const afterSortSearchParams = new URL(fetchRowsSpy.lastCall.args[0]).searchParams; - expect(afterSortSearchParams.get('lastRowToRender')).to.equal( - beforeSortSearchParams.get('lastRowToRender'), - ); + expect(afterSortSearchParams.get('end')).to.equal(beforeSortSearchParams.get('end')); }); it('should reset the scroll position when filter is applied', async () => { @@ -155,7 +153,7 @@ describe(' - Data source lazy loader', () => { const beforeFilteringSearchParams = new URL(fetchRowsSpy.lastCall.args[0]).searchParams; // last row is not the first page anymore - expect(beforeFilteringSearchParams.get('firstRowToRender')).to.not.equal('0'); + expect(beforeFilteringSearchParams.get('start')).to.not.equal('0'); apiRef.current.setFilterModel({ items: [ @@ -173,7 +171,7 @@ describe(' - Data source lazy loader', () => { const afterFilteringSearchParams = new URL(fetchRowsSpy.lastCall.args[0]).searchParams; // last row is the end of the first page - expect(afterFilteringSearchParams.get('firstRowToRender')).to.equal('0'); + expect(afterFilteringSearchParams.get('end')).to.equal('0'); }); }); @@ -222,13 +220,13 @@ describe(' - Data source lazy loader', () => { const beforeSortingSearchParams = new URL(fetchRowsSpy.lastCall.args[0]).searchParams; // last row is not the first page anymore - expect(beforeSortingSearchParams.get('lastRowToRender')).to.not.equal('9'); + expect(beforeSortingSearchParams.get('end')).to.not.equal('9'); apiRef.current.sortColumn(mockServer.columns[0].field, 'asc'); const afterSortingSearchParams = new URL(fetchRowsSpy.lastCall.args[0]).searchParams; // last row is the end of the first page - expect(afterSortingSearchParams.get('lastRowToRender')).to.equal('9'); + expect(afterSortingSearchParams.get('end')).to.equal('9'); }); it('should reset the scroll position when filter is applied', async () => { @@ -243,7 +241,7 @@ describe(' - Data source lazy loader', () => { const beforeFilteringSearchParams = new URL(fetchRowsSpy.lastCall.args[0]).searchParams; // last row is not the first page anymore - expect(beforeFilteringSearchParams.get('lastRowToRender')).to.not.equal('9'); + expect(beforeFilteringSearchParams.get('end')).to.not.equal('9'); apiRef.current.setFilterModel({ items: [ @@ -257,7 +255,7 @@ describe(' - Data source lazy loader', () => { const afterFilteringSearchParams = new URL(fetchRowsSpy.lastCall.args[0]).searchParams; // last row is the end of the first page - expect(afterFilteringSearchParams.get('lastRowToRender')).to.equal('9'); + expect(afterFilteringSearchParams.get('end')).to.equal('9'); }); }); From 5bb4e2850c21029f84f63b8294e2e8d23dd28c05 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 6 Nov 2024 16:42:30 +0100 Subject: [PATCH 073/121] Ignore type --- packages/x-data-grid-generator/src/hooks/useMockServer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/x-data-grid-generator/src/hooks/useMockServer.ts b/packages/x-data-grid-generator/src/hooks/useMockServer.ts index e9c168f0ca08a..07cf79dce50ed 100644 --- a/packages/x-data-grid-generator/src/hooks/useMockServer.ts +++ b/packages/x-data-grid-generator/src/hooks/useMockServer.ts @@ -104,7 +104,7 @@ const getColumnsFromOptions = (options: ColumnsOptions): GridColDefGenerator[] | return columns; }; -function decodeParams(url: string): GridGetRowsParams { +function decodeParams(url: string) { const params = new URL(url).searchParams; const decodedParams = {} as any; const array = Array.from(params.entries()); @@ -117,7 +117,7 @@ function decodeParams(url: string): GridGetRowsParams { } } - return decodedParams as GridGetRowsParams; + return decodedParams; } const getInitialState = (columns: GridColDefGenerator[], groupingField?: string) => { From f15373b60a8ec1b14d90a6f13c0649ab89874d1e Mon Sep 17 00:00:00 2001 From: Armin Mehinovic <4390250+arminmeh@users.noreply.github.com> Date: Wed, 6 Nov 2024 16:51:49 +0100 Subject: [PATCH 074/121] Docs and demo improvements Co-authored-by: Kenan Yusuf Co-authored-by: Bilal Shafi Signed-off-by: Armin Mehinovic <4390250+arminmeh@users.noreply.github.com> --- .../ServerSideLazyLoadingErrorHandling.tsx | 55 +++++++++---------- .../server-side-data/lazy-loading.md | 11 ++-- 2 files changed, 32 insertions(+), 34 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.tsx index 54c14b32f046b..dcd3a32f67b0d 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.tsx @@ -13,26 +13,23 @@ import { useMockServer } from '@mui/x-data-grid-generator'; import Alert from '@mui/material/Alert'; import Button from '@mui/material/Button'; -function ErrorAlert({ onClick }: { onClick: () => void }) { +function ErrorSnackbar(props: SnackbarProps & { onRetry: () => void }) { + const { onRetry, ...rest } = props; return ( - - Retry - - } - > - Could not fetch the data - + + + Retry + + } + > + Failed to fetch row data + + ); } @@ -83,16 +80,16 @@ function ServerSideLazyLoadingErrorHandling() { />
{retryParams && ( - { - apiRef.current.unstable_dataSource.fetchRows( - GRID_ROOT_GROUP_ID, - retryParams, - ); - setRetryParams(null); - }} - /> - )} + { + apiRef.current.unstable_dataSource.fetchRows( + GRID_ROOT_GROUP_ID, + retryParams, + ); + setRetryParams(null); + }} + /> Date: Wed, 6 Nov 2024 16:54:33 +0100 Subject: [PATCH 075/121] Fix error demo --- .../ServerSideLazyLoadingErrorHandling.js | 41 +++++++++---------- .../ServerSideLazyLoadingErrorHandling.tsx | 22 +++++----- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.js index d1bf5aec72363..da05c2b3c9ea2 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.js @@ -10,27 +10,25 @@ import FormControlLabel from '@mui/material/FormControlLabel'; import { useMockServer } from '@mui/x-data-grid-generator'; import Alert from '@mui/material/Alert'; import Button from '@mui/material/Button'; +import Snackbar from '@mui/material/Snackbar'; -function ErrorAlert({ onClick }) { +function ErrorSnackbar(props) { + const { onRetry, ...rest } = props; return ( - - Retry - - } - > - Could not fetch the data - + + + Retry + + } + > + Failed to fetch row data + + ); } @@ -79,8 +77,9 @@ function ServerSideLazyLoadingErrorHandling() { />
{retryParams && ( - { + { apiRef.current.unstable_dataSource.fetchRows( GRID_ROOT_GROUP_ID, retryParams, diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.tsx index dcd3a32f67b0d..ed8244044376d 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.tsx @@ -12,6 +12,7 @@ import FormControlLabel from '@mui/material/FormControlLabel'; import { useMockServer } from '@mui/x-data-grid-generator'; import Alert from '@mui/material/Alert'; import Button from '@mui/material/Button'; +import Snackbar, { SnackbarProps } from '@mui/material/Snackbar'; function ErrorSnackbar(props: SnackbarProps & { onRetry: () => void }) { const { onRetry, ...rest } = props; @@ -80,16 +81,17 @@ function ServerSideLazyLoadingErrorHandling() { />
{retryParams && ( - { - apiRef.current.unstable_dataSource.fetchRows( - GRID_ROOT_GROUP_ID, - retryParams, - ); - setRetryParams(null); - }} - /> + { + apiRef.current.unstable_dataSource.fetchRows( + GRID_ROOT_GROUP_ID, + retryParams, + ); + setRetryParams(null); + }} + /> + )} Date: Wed, 6 Nov 2024 18:46:41 +0100 Subject: [PATCH 076/121] Fix lint issue. Fix test --- packages/x-data-grid-generator/src/hooks/useMockServer.ts | 1 - .../src/tests/dataSourceLazyLoader.DataGridPro.test.tsx | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/x-data-grid-generator/src/hooks/useMockServer.ts b/packages/x-data-grid-generator/src/hooks/useMockServer.ts index 07cf79dce50ed..9fb8aff79a8c2 100644 --- a/packages/x-data-grid-generator/src/hooks/useMockServer.ts +++ b/packages/x-data-grid-generator/src/hooks/useMockServer.ts @@ -3,7 +3,6 @@ import { LRUCache } from 'lru-cache'; import { getGridDefaultColumnTypes, GridRowModel, - GridGetRowsParams, GridGetRowsResponse, GridColDef, GridInitialState, diff --git a/packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx index 3625ea2d6f557..9ff0bb4b3ba41 100644 --- a/packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx @@ -152,7 +152,7 @@ describe(' - Data source lazy loader', () => { }); const beforeFilteringSearchParams = new URL(fetchRowsSpy.lastCall.args[0]).searchParams; - // last row is not the first page anymore + // first row is not the first page anymore expect(beforeFilteringSearchParams.get('start')).to.not.equal('0'); apiRef.current.setFilterModel({ @@ -170,8 +170,8 @@ describe(' - Data source lazy loader', () => { }); const afterFilteringSearchParams = new URL(fetchRowsSpy.lastCall.args[0]).searchParams; - // last row is the end of the first page - expect(afterFilteringSearchParams.get('end')).to.equal('0'); + // first row is the start of the first page + expect(afterFilteringSearchParams.get('start')).to.equal('0'); }); }); From a1c4d20252889b9f6401212df95be3e9b1ee97b1 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 7 Nov 2024 09:07:37 +0100 Subject: [PATCH 077/121] Clear Snackbar alert after successful data fetch --- .../server-side-data/ServerSideLazyLoadingErrorHandling.js | 3 +++ .../server-side-data/ServerSideLazyLoadingErrorHandling.tsx | 3 +++ 2 files changed, 6 insertions(+) diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.js index da05c2b3c9ea2..5aeda136a2c2e 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.js @@ -55,6 +55,9 @@ function ServerSideLazyLoadingErrorHandling() { const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, ); + + // Reset the retryParams when new rows are fetched + setRetryParams(null); return { rows: getRowsResponse.rows, rowCount: getRowsResponse.rowCount, diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.tsx index ed8244044376d..ca836369d7444 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.tsx @@ -59,6 +59,9 @@ function ServerSideLazyLoadingErrorHandling() { const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, ); + + // Reset the retryParams when new rows are fetched + setRetryParams(null); return { rows: getRowsResponse.rows, rowCount: getRowsResponse.rowCount, From 533f49b8d73f1ec35d36cafff7503a945d67d176 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 7 Nov 2024 09:08:04 +0100 Subject: [PATCH 078/121] Remove events --- .../features/dataSource/useGridDataSource.ts | 5 ----- .../useGridDataSourceLazyLoader.ts | 20 +++++++++++++------ .../dataSourceLazyLoader.DataGridPro.test.tsx | 14 ++++++------- .../src/typeOverloads/modules.ts | 12 ----------- 4 files changed, 21 insertions(+), 30 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index dd9ce54b2acd7..05a5005b41bc5 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -11,7 +11,6 @@ import { GridEventListener, } from '@mui/x-data-grid'; import { - GridGetRowsParams, gridRowGroupsToFetchSelector, GridStateInitializer, GridStrategyProcessor, @@ -306,7 +305,6 @@ export const useGridDataSource = ( apiRef.current.setRowCount(response.rowCount); } apiRef.current.setRows(response.rows); - apiRef.current.publishEvent('rowsFetched'); }, [apiRef], ); @@ -360,9 +358,6 @@ export const useGridDataSource = ( 'paginationModelChange', runIf(defaultRowsUpdateStrategyActive, () => fetchRows()), ); - useGridApiEventHandler(apiRef, 'getRows', (params: GridGetRowsParams) => - fetchRows(GRID_ROOT_GROUP_ID, params), - ); const isFirstRender = React.useRef(true); React.useEffect(() => { 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 2fc27b4a0cb51..750be8e5d9312 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 @@ -107,7 +107,7 @@ export const useGridDataSourceLazyLoader = ( filterModel, }; - privateApiRef.current.publishEvent('getRows', getRowsParams); + privateApiRef.current.unstable_dataSource.fetchRows(GRID_ROOT_GROUP_ID, getRowsParams); }, [privateApiRef, sortModel, filterModel, paginationModel.pageSize]); const ensureValidRowCount = React.useCallback( @@ -240,7 +240,6 @@ export const useGridDataSourceLazyLoader = ( addSkeletonRows(); privateApiRef.current.setLoading(false); privateApiRef.current.requestPipeProcessorsApplication('hydrateRows'); - privateApiRef.current.publishEvent('rowsFetched'); }, [privateApiRef, filteredSortedRowIds, updateLoadingTrigger, addSkeletonRows], ); @@ -278,7 +277,10 @@ export const useGridDataSourceLazyLoader = ( }; privateApiRef.current.setLoading(true); - privateApiRef.current.publishEvent('getRows', adjustRowParams(getRowsParams)); + privateApiRef.current.unstable_dataSource.fetchRows( + GRID_ROOT_GROUP_ID, + adjustRowParams(getRowsParams), + ); } }, [ @@ -341,7 +343,10 @@ export const useGridDataSourceLazyLoader = ( getRowsParams.start = skeletonRowsSection.firstRowIndex; getRowsParams.end = skeletonRowsSection.lastRowIndex; - privateApiRef.current.publishEvent('getRows', adjustRowParams(getRowsParams)); + privateApiRef.current.unstable_dataSource.fetchRows( + GRID_ROOT_GROUP_ID, + adjustRowParams(getRowsParams), + ); }, [ privateApiRef, @@ -387,7 +392,10 @@ export const useGridDataSourceLazyLoader = ( rowsStale.current = true; } - privateApiRef.current.publishEvent('getRows', adjustRowParams(getRowsParams)); + privateApiRef.current.unstable_dataSource.fetchRows( + GRID_ROOT_GROUP_ID, + adjustRowParams(getRowsParams), + ); }, [ privateApiRef, @@ -411,7 +419,7 @@ export const useGridDataSourceLazyLoader = ( }; privateApiRef.current.setLoading(true); - privateApiRef.current.publishEvent('getRows', getRowsParams); + privateApiRef.current.unstable_dataSource.fetchRows(GRID_ROOT_GROUP_ID, getRowsParams); }, [privateApiRef, sortModel, paginationModel.pageSize], ); diff --git a/packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx index 9ff0bb4b3ba41..9b44a657a7de3 100644 --- a/packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx @@ -297,22 +297,22 @@ describe(' - Data source lazy loader', () => { it('should reset the grid if the rowCount becomes smaller than the actual row count', async () => { // override rowCount transformGetRowsResponse = (response) => ({ ...response, rowCount: undefined }); - const { setProps } = render( + render( , ); // wait until the rows are rendered await waitFor(() => expect(getRow(0)).not.to.be.undefined); - const getRowsEventSpy = spy(); - apiRef.current.subscribeEvent('getRows', getRowsEventSpy); + // reset the spy call count + fetchRowsSpy.resetHistory(); // reduce the rowCount to be more than the number of rows - setProps({ rowCount: 80 }); - expect(getRowsEventSpy.callCount).to.equal(0); + apiRef.current.setRowCount(80); + expect(fetchRowsSpy.callCount).to.equal(0); // reduce the rowCount once more, but now to be less than the number of rows - setProps({ rowCount: 20 }); - expect(getRowsEventSpy.callCount).to.equal(1); + apiRef.current.setRowCount(20); + await waitFor(() => expect(fetchRowsSpy.callCount).to.equal(1)); }); it('should allow setting the row count via API', async () => { diff --git a/packages/x-data-grid-pro/src/typeOverloads/modules.ts b/packages/x-data-grid-pro/src/typeOverloads/modules.ts index 78fb6c2162705..430f21d777f4c 100644 --- a/packages/x-data-grid-pro/src/typeOverloads/modules.ts +++ b/packages/x-data-grid-pro/src/typeOverloads/modules.ts @@ -3,7 +3,6 @@ import type { GridRowScrollEndParams, GridRowOrderChangeParams, GridFetchRowsParams, - GridGetRowsParams, } from '../models'; import type { GridRenderHeaderFilterProps } from '../components/headerFiltering/GridHeaderFilterCell'; import type { GridColumnPinningInternalCache } from '../hooks/features/columnPinning/gridColumnPinningInterface'; @@ -46,17 +45,6 @@ export interface GridEventLookupPro { * Used to trigger `onFetchRows`. */ fetchRows: { params: GridFetchRowsParams }; - // Data source - /** - * Fired to make a new request through the data source's `getRows` method. - * @ignore - do not document. - */ - getRows: { params: GridGetRowsParams }; - /** - * Fired when the data request is resolved either via the data source or from the cache. - * @ignore - do not document. - */ - rowsFetched: {}; } export interface GridPipeProcessingLookupPro { From 96a729d3a0496cab33a086d85687693434eb8cc7 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 7 Nov 2024 09:44:10 +0100 Subject: [PATCH 079/121] Merge callouts --- .../data-grid/server-side-data/lazy-loading.md | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/docs/data/data-grid/server-side-data/lazy-loading.md b/docs/data/data-grid/server-side-data/lazy-loading.md index dc5b21659f7fd..a5f347fa9a3b4 100644 --- a/docs/data/data-grid/server-side-data/lazy-loading.md +++ b/docs/data/data-grid/server-side-data/lazy-loading.md @@ -18,17 +18,13 @@ This loading strategy is often referred to as [**viewport loading**](#viewport-l If the total row count is unknown, the Data Grid fetches more data when the user scrolls to the bottom. This loading strategy is often referred to as [**infinite loading**](#infinite-loading). :::info -Row count can be provided either by returning the `rowCount` in the response of the `getRows` method in `unstable_dataSource`, via the `rowCount` prop or by calling [`setRowCount`](/x/api/data-grid/grid-api/#grid-api-prop-setRowCount) API. -::: - -:::warning -Order of precedence for the row count: +Row count can be provided in either of the following ways. -- `rowCount` prop -- `rowCount` returned by the `getRows` method -- row count set using the `setRowCount` API +- Pass as [`rowCount`](/x/api/data-grid/data-grid/#data-grid-prop-rowCount) prop +- Return `rowCount` in the `getRows` method of the [data source](/x/react-data-grid/server-side-data/#data-source) +- Set the `rowCount` using the [`setRowCount`](/x/api/data-grid/grid-api/#grid-api-prop-setRowCount) API method. -This means that, if the row count is set using the API, that value gets overridden once a new value is returned by the `getRows` method, even if it is `undefined`. +The above list is given in the order of precedence, which means if the row count is set using the API, that value gets overridden once a new value is returned by the `getRows` method, even if it is `undefined`. ::: ## Viewport loading From 7545e94832215c1f7f75f43835fa9b7bda46b174 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 7 Nov 2024 21:45:08 +0100 Subject: [PATCH 080/121] Split cache and chunk manager --- .../src/hooks/features/dataSource/cache.ts | 99 ++------------ .../features/dataSource/useGridDataSource.ts | 47 +++++-- .../src/hooks/features/dataSource/utils.ts | 129 +++++++++++++++++- 3 files changed, 170 insertions(+), 105 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts index ada0a066a10df..0176587dbda99 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts @@ -7,13 +7,6 @@ export type GridDataSourceCacheDefaultConfig = { * @default 300000 (5 minutes) */ ttl?: number; - /** - * The number of rows to store in each cache entry. If not set, the whole array will be stored in a single cache entry. - * Setting this value to smallest page size will result in better cache hit rate. - * Has no effect if cursor pagination is used. - * @default undefined - */ - chunkSize?: number; }; function getKey(params: GridGetRowsParams) { @@ -28,106 +21,34 @@ function getKey(params: GridGetRowsParams) { } export class GridDataSourceCacheDefault { - private cache: Record< - string, - { - value: GridGetRowsResponse; - expiry: number; - chunk: { startIndex: string | number; endIndex: number }; - } - >; + private cache: Record; private ttl: number; - private chunkSize: number; - - private getChunkRanges = (params: GridGetRowsParams) => { - if (this.chunkSize < 1 || typeof params.start !== 'number') { - return [{ startIndex: params.start, endIndex: params.end }]; - } - - // split the range into chunks - const chunkRanges: { startIndex: number; endIndex: number }[] = []; - for (let i = params.start; i < params.end; i += this.chunkSize) { - const endIndex = Math.min(i + this.chunkSize - 1, params.end); - chunkRanges.push({ startIndex: i, endIndex }); - } - - return chunkRanges; - }; - - constructor({ chunkSize, ttl = 300000 }: GridDataSourceCacheDefaultConfig) { + constructor({ ttl = 300000 }: GridDataSourceCacheDefaultConfig) { this.cache = {}; this.ttl = ttl; - this.chunkSize = chunkSize || 0; } set(key: GridGetRowsParams, value: GridGetRowsResponse) { - const chunks = this.getChunkRanges(key); + const keyString = getKey(key); const expiry = Date.now() + this.ttl; - - chunks.forEach((chunk) => { - const isLastChunk = chunk.endIndex === key.end; - const keyString = getKey({ ...key, start: chunk.startIndex, end: chunk.endIndex }); - const chunkValue: GridGetRowsResponse = { - ...value, - pageInfo: { - ...value.pageInfo, - // If the original response had page info, update that information for all but last chunk and keep the original value for the last chunk - hasNextPage: - (value.pageInfo?.hasNextPage !== undefined && !isLastChunk) || - value.pageInfo?.hasNextPage, - nextCursor: - value.pageInfo?.nextCursor !== undefined && !isLastChunk - ? value.rows[chunk.endIndex + 1].id - : value.pageInfo?.nextCursor, - }, - rows: - typeof chunk.startIndex !== 'number' || typeof key.start !== 'number' - ? value.rows - : value.rows.slice(chunk.startIndex - key.start, chunk.endIndex - key.start + 1), - }; - - this.cache[keyString] = { value: chunkValue, expiry, chunk }; - }); + this.cache[keyString] = { value, expiry }; } get(key: GridGetRowsParams): GridGetRowsResponse | undefined { - const chunks = this.getChunkRanges(key); - - const startChunk = chunks.findIndex((chunk) => chunk.startIndex === key.start); - const endChunk = chunks.findIndex((chunk) => chunk.endIndex === key.end); - - // If desired range cannot fit completely in chunks, then it is a cache miss - if (startChunk === -1 || endChunk === -1) { + const keyString = getKey(key); + const entry = this.cache[keyString]; + if (!entry) { return undefined; } - const cachedResponses: (GridGetRowsResponse | null)[] = []; - - for (let i = startChunk; i <= endChunk; i += 1) { - const keyString = getKey({ ...key, start: chunks[i].startIndex, end: chunks[i].endIndex }); - const entry = this.cache[keyString]; - const isCacheValid = entry?.value && Date.now() < entry.expiry; - cachedResponses.push(isCacheValid ? entry?.value : null); - } - - // If any of the chunks is missing, then it is a cache miss - if (cachedResponses.some((response) => response === null)) { + if (Date.now() > entry.expiry) { + delete this.cache[keyString]; return undefined; } - // Merge the chunks into a single response - return (cachedResponses as GridGetRowsResponse[]).reduce( - (acc: GridGetRowsResponse, response) => { - return { - rows: [...acc.rows, ...response.rows], - rowCount: response.rowCount, - pageInfo: response.pageInfo, - }; - }, - { rows: [], rowCount: 0, pageInfo: {} }, - ); + return entry.value; } clear() { diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index 05a5005b41bc5..cef9dc7b5dbeb 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -20,7 +20,13 @@ import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; import { gridGetRowsParamsSelector, gridDataSourceErrorsSelector } from './gridDataSourceSelector'; import { GridDataSourceApi, GridDataSourceApiBase, GridDataSourcePrivateApi } from './interfaces'; -import { DataSourceRowsUpdateStrategy, NestedDataManager, RequestStatus, runIf } from './utils'; +import { + CacheChunkManager, + DataSourceRowsUpdateStrategy, + NestedDataManager, + RequestStatus, + runIf, +} from './utils'; import { GridDataSourceCache } from '../../../models'; import { GridDataSourceCacheDefault, GridDataSourceCacheDefaultConfig } from './cache'; @@ -94,11 +100,14 @@ export const useGridDataSource = ( return Math.min(paginationModel.pageSize, sortedPageSizeOptions[0]); }, [paginationModel.pageSize, props.pageSizeOptions]); + const cacheChunkManager = useLazyRef( + () => new CacheChunkManager({ chunkSize: cacheChunkSize }), + ).current; + const [cache, setCache] = React.useState(() => - getCache(props.unstable_dataSourceCache, { - chunkSize: cacheChunkSize, - }), + getCache(props.unstable_dataSourceCache), ); + cacheChunkManager.setDataSourceCache(cache); const fetchRows = React.useCallback( async (parentId, params) => { @@ -125,8 +134,7 @@ export const useGridDataSource = ( ...params, }; - const cachedData = apiRef.current.unstable_dataSource.cache.get(fetchParams); - + const cachedData = cacheChunkManager.getCacheData(fetchParams); if (cachedData !== undefined) { apiRef.current.applyStrategyProcessor('dataSourceRowsUpdate', { response: cachedData, @@ -142,7 +150,7 @@ export const useGridDataSource = ( try { const getRowsResponse = await getRows(fetchParams); - apiRef.current.unstable_dataSource.cache.set(fetchParams, getRowsResponse); + cacheChunkManager.setCacheData(fetchParams, getRowsResponse); apiRef.current.applyStrategyProcessor('dataSourceRowsUpdate', { response: getRowsResponse, fetchParams, @@ -161,6 +169,7 @@ export const useGridDataSource = ( }, [ nestedDataManager, + cacheChunkManager, apiRef, defaultRowsUpdateStrategyActive, props.unstable_dataSource?.getRows, @@ -193,8 +202,7 @@ export const useGridDataSource = ( groupKeys: rowNode.path, }; - const cachedData = apiRef.current.unstable_dataSource.cache.get(fetchParams); - + const cachedData = cacheChunkManager.getCacheData(fetchParams); if (cachedData !== undefined) { const rows = cachedData.rows; nestedDataManager.setRequestSettled(id); @@ -222,7 +230,7 @@ export const useGridDataSource = ( return; } nestedDataManager.setRequestSettled(id); - apiRef.current.unstable_dataSource.cache.set(fetchParams, getRowsResponse); + cacheChunkManager.setCacheData(fetchParams, getRowsResponse); apiRef.current.setRowCount( getRowsResponse.rowCount === undefined ? -1 : getRowsResponse.rowCount, ); @@ -237,7 +245,14 @@ export const useGridDataSource = ( nestedDataManager.setRequestSettled(id); } }, - [nestedDataManager, onError, apiRef, props.treeData, props.unstable_dataSource?.getRows], + [ + nestedDataManager, + cacheChunkManager, + onError, + apiRef, + props.treeData, + props.unstable_dataSource?.getRows, + ], ); const setChildrenLoading = React.useCallback( @@ -365,11 +380,13 @@ export const useGridDataSource = ( isFirstRender.current = false; return; } - const newCache = getCache(props.unstable_dataSourceCache, { - chunkSize: cacheChunkSize, + const newCache = getCache(props.unstable_dataSourceCache); + setCache((prevCache) => { + const cacheToUse = prevCache !== newCache ? newCache : prevCache; + cacheChunkManager.setDataSourceCache(cacheToUse); + return cacheToUse; }); - setCache((prevCache) => (prevCache !== newCache ? newCache : prevCache)); - }, [props.unstable_dataSourceCache, cacheChunkSize]); + }, [props.unstable_dataSourceCache, cacheChunkManager]); React.useEffect(() => { if (!isFirstRender.current) { diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts index 7d48411bb5ac2..8b233e197f84e 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts @@ -1,5 +1,10 @@ import { GridRowId } from '@mui/x-data-grid'; -import { GridPrivateApiPro } from '../../../models/gridApiPro'; +import { + GridPrivateApiPro, + GridDataSourceCache, + GridGetRowsParams, + GridGetRowsResponse, +} from '../../../models'; const MAX_CONCURRENT_REQUESTS = Infinity; @@ -9,6 +14,15 @@ export const runIf = (condition: boolean, fn: Function) => (params: unknown) => } }; +export type GridDataSourceCacheChunkManagerConfig = { + /** + * The number of rows to store in each cache entry. If not set, the whole array will be stored in a single cache entry. + * Setting this value to smallest page size will result in better cache hit rate. + * Has no effect if cursor pagination is used. + */ + chunkSize: number; +}; + export enum RequestStatus { QUEUED, PENDING, @@ -117,3 +131,116 @@ export class NestedDataManager { public getActiveRequestsCount = () => this.pendingRequests.size + this.queuedRequests.size; } + +/** + * Provides better cache hit rate by splitting the data into smaller chunks + * Splits the data into smaller chunks to be stored in the cache + * Merges multiple cache entries into a single response + */ +export class CacheChunkManager { + private dataSourceCache: GridDataSourceCache | undefined; + + private chunkSize: number; + + constructor(config: GridDataSourceCacheChunkManagerConfig) { + this.chunkSize = config.chunkSize; + } + + private generateChunkedKeys = (params: GridGetRowsParams): GridGetRowsParams[] => { + if (this.chunkSize < 1 || typeof params.start !== 'number') { + return [params]; + } + + // split the range into chunks + const chunkedKeys: GridGetRowsParams[] = []; + for (let i = params.start; i < params.end; i += this.chunkSize) { + const end = Math.min(i + this.chunkSize - 1, params.end); + chunkedKeys.push({ ...params, start: i, end }); + } + + return chunkedKeys; + }; + + private getCacheKeys = (key: GridGetRowsParams) => { + const chunkedKeys = this.generateChunkedKeys(key); + + const startChunk = chunkedKeys.findIndex((chunkedKey) => chunkedKey.start === key.start); + const endChunk = chunkedKeys.findIndex((chunkedKey) => chunkedKey.end === key.end); + + // If desired range cannot fit completely in chunks, then it is a cache miss + if (startChunk === -1 || endChunk === -1) { + return [key]; + } + + const keys = []; + for (let i = startChunk; i <= endChunk; i += 1) { + keys.push(chunkedKeys[i]); + } + + return keys; + }; + + public setDataSourceCache = (newDataSourceCache: GridDataSourceCache) => { + this.dataSourceCache = newDataSourceCache; + }; + + public getCacheData = (key: GridGetRowsParams): GridGetRowsResponse | undefined => { + if (!this.dataSourceCache) { + return undefined; + } + + const cacheKeys = this.getCacheKeys(key); + const responses = cacheKeys.map((cacheKey) => + (this.dataSourceCache as GridDataSourceCache).get(cacheKey), + ); + + // If any of the chunks is missing, then it is a cache miss + if (responses.some((response) => response === undefined)) { + return undefined; + } + + return (responses as GridGetRowsResponse[]).reduce( + (acc, response) => { + return { + rows: [...acc.rows, ...response.rows], + rowCount: response.rowCount, + pageInfo: response.pageInfo, + }; + }, + { rows: [], rowCount: 0, pageInfo: {} }, + ); + }; + + public setCacheData = (key: GridGetRowsParams, response: GridGetRowsResponse) => { + if (!this.dataSourceCache) { + return; + } + + const cacheKeys = this.getCacheKeys(key); + const lastIndex = cacheKeys.length - 1; + + cacheKeys.forEach((chunkKey, index) => { + const isLastChunk = index === lastIndex; + const responseSlice: GridGetRowsResponse = { + ...response, + pageInfo: { + ...response.pageInfo, + // If the original response had page info, update that information for all but last chunk and keep the original value for the last chunk + hasNextPage: + (response.pageInfo?.hasNextPage !== undefined && !isLastChunk) || + response.pageInfo?.hasNextPage, + nextCursor: + response.pageInfo?.nextCursor !== undefined && !isLastChunk + ? response.rows[chunkKey.end + 1].id + : response.pageInfo?.nextCursor, + }, + rows: + typeof chunkKey.start !== 'number' || typeof key.start !== 'number' + ? response.rows + : response.rows.slice(chunkKey.start - key.start, chunkKey.end - key.start + 1), + }; + + (this.dataSourceCache as GridDataSourceCache).set(chunkKey, responseSlice); + }); + }; +} From dce506c11bb24aeb4a8a69d3e20c5ea396e6fc8f Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 7 Nov 2024 22:36:30 +0100 Subject: [PATCH 081/121] Do not use cache directly in the chunk manager --- .../features/dataSource/useGridDataSource.ts | 41 +++++-- .../src/hooks/features/dataSource/utils.ts | 105 ++++++------------ 2 files changed, 60 insertions(+), 86 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index cef9dc7b5dbeb..a7dc884f808a2 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -11,6 +11,7 @@ import { GridEventListener, } from '@mui/x-data-grid'; import { + GridGetRowsResponse, gridRowGroupsToFetchSelector, GridStateInitializer, GridStrategyProcessor, @@ -103,11 +104,9 @@ export const useGridDataSource = ( const cacheChunkManager = useLazyRef( () => new CacheChunkManager({ chunkSize: cacheChunkSize }), ).current; - const [cache, setCache] = React.useState(() => getCache(props.unstable_dataSourceCache), ); - cacheChunkManager.setDataSourceCache(cache); const fetchRows = React.useCallback( async (parentId, params) => { @@ -134,7 +133,12 @@ export const useGridDataSource = ( ...params, }; - const cachedData = cacheChunkManager.getCacheData(fetchParams); + const cacheKeys = cacheChunkManager.getCacheKeys(fetchParams); + const responses = cacheKeys.map((cacheKey) => cache.get(cacheKey)); + const cachedData = responses.some((response) => response === undefined) + ? undefined + : CacheChunkManager.mergeResponses(responses as GridGetRowsResponse[]); + if (cachedData !== undefined) { apiRef.current.applyStrategyProcessor('dataSourceRowsUpdate', { response: cachedData, @@ -150,7 +154,12 @@ export const useGridDataSource = ( try { const getRowsResponse = await getRows(fetchParams); - cacheChunkManager.setCacheData(fetchParams, getRowsResponse); + + const cacheResponses = cacheChunkManager.splitResponse(fetchParams, getRowsResponse); + cacheResponses.forEach((response, key) => { + cache.set(key, response); + }); + apiRef.current.applyStrategyProcessor('dataSourceRowsUpdate', { response: getRowsResponse, fetchParams, @@ -170,6 +179,7 @@ export const useGridDataSource = ( [ nestedDataManager, cacheChunkManager, + cache, apiRef, defaultRowsUpdateStrategyActive, props.unstable_dataSource?.getRows, @@ -202,7 +212,12 @@ export const useGridDataSource = ( groupKeys: rowNode.path, }; - const cachedData = cacheChunkManager.getCacheData(fetchParams); + const cacheKeys = cacheChunkManager.getCacheKeys(fetchParams); + const responses = cacheKeys.map((cacheKey) => cache.get(cacheKey)); + const cachedData = responses.some((response) => response === undefined) + ? undefined + : CacheChunkManager.mergeResponses(responses as GridGetRowsResponse[]); + if (cachedData !== undefined) { const rows = cachedData.rows; nestedDataManager.setRequestSettled(id); @@ -230,7 +245,12 @@ export const useGridDataSource = ( return; } nestedDataManager.setRequestSettled(id); - cacheChunkManager.setCacheData(fetchParams, getRowsResponse); + + const cacheResponses = cacheChunkManager.splitResponse(fetchParams, getRowsResponse); + cacheResponses.forEach((response, key) => { + cache.set(key, response); + }); + apiRef.current.setRowCount( getRowsResponse.rowCount === undefined ? -1 : getRowsResponse.rowCount, ); @@ -248,6 +268,7 @@ export const useGridDataSource = ( [ nestedDataManager, cacheChunkManager, + cache, onError, apiRef, props.treeData, @@ -381,12 +402,8 @@ export const useGridDataSource = ( return; } const newCache = getCache(props.unstable_dataSourceCache); - setCache((prevCache) => { - const cacheToUse = prevCache !== newCache ? newCache : prevCache; - cacheChunkManager.setDataSourceCache(cacheToUse); - return cacheToUse; - }); - }, [props.unstable_dataSourceCache, cacheChunkManager]); + setCache((prevCache) => (prevCache !== newCache ? newCache : prevCache)); + }, [props.unstable_dataSourceCache]); React.useEffect(() => { if (!isFirstRender.current) { diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts index 8b233e197f84e..79bb08ebbd938 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts @@ -1,10 +1,5 @@ import { GridRowId } from '@mui/x-data-grid'; -import { - GridPrivateApiPro, - GridDataSourceCache, - GridGetRowsParams, - GridGetRowsResponse, -} from '../../../models'; +import { GridPrivateApiPro, GridGetRowsParams, GridGetRowsResponse } from '../../../models'; const MAX_CONCURRENT_REQUESTS = Infinity; @@ -138,89 +133,32 @@ export class NestedDataManager { * Merges multiple cache entries into a single response */ export class CacheChunkManager { - private dataSourceCache: GridDataSourceCache | undefined; - private chunkSize: number; constructor(config: GridDataSourceCacheChunkManagerConfig) { this.chunkSize = config.chunkSize; } - private generateChunkedKeys = (params: GridGetRowsParams): GridGetRowsParams[] => { - if (this.chunkSize < 1 || typeof params.start !== 'number') { - return [params]; + public getCacheKeys = (key: GridGetRowsParams) => { + if (this.chunkSize < 1 || typeof key.start !== 'number') { + return [key]; } // split the range into chunks const chunkedKeys: GridGetRowsParams[] = []; - for (let i = params.start; i < params.end; i += this.chunkSize) { - const end = Math.min(i + this.chunkSize - 1, params.end); - chunkedKeys.push({ ...params, start: i, end }); + for (let i = key.start; i < key.end; i += this.chunkSize) { + const end = Math.min(i + this.chunkSize - 1, key.end); + chunkedKeys.push({ ...key, start: i, end }); } return chunkedKeys; }; - private getCacheKeys = (key: GridGetRowsParams) => { - const chunkedKeys = this.generateChunkedKeys(key); - - const startChunk = chunkedKeys.findIndex((chunkedKey) => chunkedKey.start === key.start); - const endChunk = chunkedKeys.findIndex((chunkedKey) => chunkedKey.end === key.end); - - // If desired range cannot fit completely in chunks, then it is a cache miss - if (startChunk === -1 || endChunk === -1) { - return [key]; - } - - const keys = []; - for (let i = startChunk; i <= endChunk; i += 1) { - keys.push(chunkedKeys[i]); - } - - return keys; - }; - - public setDataSourceCache = (newDataSourceCache: GridDataSourceCache) => { - this.dataSourceCache = newDataSourceCache; - }; - - public getCacheData = (key: GridGetRowsParams): GridGetRowsResponse | undefined => { - if (!this.dataSourceCache) { - return undefined; - } - + public splitResponse = (key: GridGetRowsParams, response: GridGetRowsResponse) => { const cacheKeys = this.getCacheKeys(key); - const responses = cacheKeys.map((cacheKey) => - (this.dataSourceCache as GridDataSourceCache).get(cacheKey), - ); - - // If any of the chunks is missing, then it is a cache miss - if (responses.some((response) => response === undefined)) { - return undefined; - } - - return (responses as GridGetRowsResponse[]).reduce( - (acc, response) => { - return { - rows: [...acc.rows, ...response.rows], - rowCount: response.rowCount, - pageInfo: response.pageInfo, - }; - }, - { rows: [], rowCount: 0, pageInfo: {} }, - ); - }; - - public setCacheData = (key: GridGetRowsParams, response: GridGetRowsResponse) => { - if (!this.dataSourceCache) { - return; - } - - const cacheKeys = this.getCacheKeys(key); - const lastIndex = cacheKeys.length - 1; - - cacheKeys.forEach((chunkKey, index) => { - const isLastChunk = index === lastIndex; + const responses = new Map(); + cacheKeys.forEach((chunkKey) => { + const isLastChunk = chunkKey.end === key.end; const responseSlice: GridGetRowsResponse = { ...response, pageInfo: { @@ -240,7 +178,26 @@ export class CacheChunkManager { : response.rows.slice(chunkKey.start - key.start, chunkKey.end - key.start + 1), }; - (this.dataSourceCache as GridDataSourceCache).set(chunkKey, responseSlice); + responses.set(chunkKey, responseSlice); }); + + return responses; + }; + + static mergeResponses = (responses: GridGetRowsResponse[]): GridGetRowsResponse => { + if (responses.length === 1) { + return responses[0]; + } + + return responses.reduce( + (acc, response) => { + return { + rows: [...acc.rows, ...response.rows], + rowCount: response.rowCount, + pageInfo: response.pageInfo, + }; + }, + { rows: [], rowCount: 0, pageInfo: {} }, + ); }; } From 6c079bbf95f676fdd286dd314d7dad061caff3ab Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Fri, 8 Nov 2024 11:34:22 +0100 Subject: [PATCH 082/121] Add an additional example for lazyLoadingRequestThrottleMs --- .../ServerSideLazyLoadingRequestThrottle.js | 113 ++++++++++++++ .../ServerSideLazyLoadingRequestThrottle.tsx | 138 ++++++++++++++++++ .../server-side-data/lazy-loading.md | 10 +- 3 files changed, 259 insertions(+), 2 deletions(-) create mode 100644 docs/data/data-grid/server-side-data/ServerSideLazyLoadingRequestThrottle.js create mode 100644 docs/data/data-grid/server-side-data/ServerSideLazyLoadingRequestThrottle.tsx diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingRequestThrottle.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingRequestThrottle.js new file mode 100644 index 0000000000000..aadaa223aeb58 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingRequestThrottle.js @@ -0,0 +1,113 @@ +import * as React from 'react'; +import { DataGridPro, GridRowCount } from '@mui/x-data-grid-pro'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import Box from '@mui/material/Box'; +import FormControl from '@mui/material/FormControl'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import FormLabel from '@mui/material/FormLabel'; +import RadioGroup from '@mui/material/RadioGroup'; +import Radio from '@mui/material/Radio'; + +function GridCustomFooterRowCount({ requestCount, ...props }) { + return ( + + Request count: {requestCount} + + + ); +} + +function GridCustomToolbar({ throttleMs, setThrottleMs }) { + return ( + + + + Throttle + + setThrottleMs(Number(event.target.value))} + > + } label="0 ms" /> + } + label="500 ms (default)" + /> + } label="1500 ms" /> + + + + ); +} + +function ServerSideLazyLoadingRequestThrottle() { + const { fetchRows, ...props } = useMockServer( + { rowLength: 1000 }, + { useCursorPagination: false, minDelay: 200, maxDelay: 500 }, + ); + + const [requestCount, setRequestCount] = React.useState(0); + const [throttleMs, setThrottleMs] = React.useState(500); + + const dataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + start: `${params.start}`, + end: `${params.end}`, + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + + setRequestCount((prev) => prev + 1); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + }), + [fetchRows], + ); + + React.useEffect(() => { + setRequestCount(0); + }, [dataSource]); + + return ( +
+ +
+ ); +} + +export default ServerSideLazyLoadingRequestThrottle; diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingRequestThrottle.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingRequestThrottle.tsx new file mode 100644 index 0000000000000..8960577ec1a7d --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingRequestThrottle.tsx @@ -0,0 +1,138 @@ +import * as React from 'react'; +import { + DataGridPro, + GridDataSource, + GridGetRowsParams, + GridRowCount, + GridRowCountProps, + GridSlots, +} from '@mui/x-data-grid-pro'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import Box from '@mui/material/Box'; +import FormControl from '@mui/material/FormControl'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import FormLabel from '@mui/material/FormLabel'; +import RadioGroup from '@mui/material/RadioGroup'; +import Radio from '@mui/material/Radio'; + +declare module '@mui/x-data-grid-pro' { + interface FooterRowCountOverrides { + requestCount: number; + } +} + +interface CustomFooterRowCounProps extends GridRowCountProps { + requestCount: number; +} + +interface CustomToolbarProps { + throttleMs: number; + setThrottleMs: (value: number) => void; +} + +function GridCustomFooterRowCount({ + requestCount, + ...props +}: CustomFooterRowCounProps) { + return ( + + Request count: {requestCount} + + + ); +} + +function GridCustomToolbar({ throttleMs, setThrottleMs }: CustomToolbarProps) { + return ( + + + + Throttle + + setThrottleMs(Number(event.target.value))} + > + } label="0 ms" /> + } + label="500 ms (default)" + /> + } label="1500 ms" /> + + + + ); +} + +function ServerSideLazyLoadingRequestThrottle() { + const { fetchRows, ...props } = useMockServer( + { rowLength: 1000 }, + { useCursorPagination: false, minDelay: 200, maxDelay: 500 }, + ); + + const [requestCount, setRequestCount] = React.useState(0); + const [throttleMs, setThrottleMs] = React.useState(500); + + const dataSource: GridDataSource = React.useMemo( + () => ({ + getRows: async (params: GridGetRowsParams) => { + const urlParams = new URLSearchParams({ + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + start: `${params.start}`, + end: `${params.end}`, + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + + setRequestCount((prev) => prev + 1); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + }), + [fetchRows], + ); + + React.useEffect(() => { + setRequestCount(0); + }, [dataSource]); + + return ( +
+ +
+ ); +} + +export default ServerSideLazyLoadingRequestThrottle; diff --git a/docs/data/data-grid/server-side-data/lazy-loading.md b/docs/data/data-grid/server-side-data/lazy-loading.md index a5f347fa9a3b4..611cc037a2719 100644 --- a/docs/data/data-grid/server-side-data/lazy-loading.md +++ b/docs/data/data-grid/server-side-data/lazy-loading.md @@ -33,8 +33,6 @@ The viewport loading mode is enabled when the row count is known (`rowCount >= 0 If the user scrolls too fast, the grid loads multiple pages with one request (by adjusting `start` and `end` param) in order to reduce the server load. -In addition to this, the grid throttles new requests made to the data source after each rendering context change. This can be controlled with `lazyLoadingRequestThrottleMs` prop. - The demo below shows the viewport loading mode. {{"demo": "ServerSideLazyLoadingViewport.js", "bg": "inline"}} @@ -46,6 +44,14 @@ In a real-world scenario, you should replace this with your own server-side data Open info section of the browser console to see the requests being made and the data being fetched in response. ::: +### Request throttling + +While user is scrolling through the grid, rendering context changes and the Data Grid tries to fill in any missing rows by making a new data source request. This avoid making huge amount of request, Data Grid throttles new data fetches on the rendering context change. By default throttle is set to 500 ms, but the time can be controlled with `lazyLoadingRequestThrottleMs` prop. + +The demo below shows the difference in behavior for different values of the `lazyLoadingRequestThrottleMs` prop. In the footer of the Data Grid, you can see amount of requests made while scrolling. + +{{"demo": "ServerSideLazyLoadingRequestThrottle.js", "bg": "inline"}} + ## Infinite loading The infinite loading mode is enabled when the row count is unknown (`-1` or `undefined`). New page is loaded when the scroll reaches the bottom of the viewport area. From 47c41ad40ac5eac0b77307a25b6a5836aafc8b2d Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Fri, 8 Nov 2024 11:42:37 +0100 Subject: [PATCH 083/121] Fix docs --- docs/data/data-grid/server-side-data/lazy-loading.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/data-grid/server-side-data/lazy-loading.md b/docs/data/data-grid/server-side-data/lazy-loading.md index 611cc037a2719..e8f7c5c1c4d64 100644 --- a/docs/data/data-grid/server-side-data/lazy-loading.md +++ b/docs/data/data-grid/server-side-data/lazy-loading.md @@ -48,7 +48,7 @@ Open info section of the browser console to see the requests being made and the While user is scrolling through the grid, rendering context changes and the Data Grid tries to fill in any missing rows by making a new data source request. This avoid making huge amount of request, Data Grid throttles new data fetches on the rendering context change. By default throttle is set to 500 ms, but the time can be controlled with `lazyLoadingRequestThrottleMs` prop. -The demo below shows the difference in behavior for different values of the `lazyLoadingRequestThrottleMs` prop. In the footer of the Data Grid, you can see amount of requests made while scrolling. +The demo below shows the difference in behavior for different values of the `lazyLoadingRequestThrottleMs` prop. In the footer of the Data Grid, you can see the amount of requests made while scrolling. {{"demo": "ServerSideLazyLoadingRequestThrottle.js", "bg": "inline"}} From 29694837d1d7fe71c6b64c7e965b33590b64f88f Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Mon, 25 Nov 2024 09:00:21 +0100 Subject: [PATCH 084/121] Allign page flags --- docs/data/pages.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/data/pages.ts b/docs/data/pages.ts index 65b21f66e4a1a..11cf7ac2d8bf6 100644 --- a/docs/data/pages.ts +++ b/docs/data/pages.ts @@ -148,7 +148,6 @@ const pages: MuiPage[] = [ pathname: '/x/react-data-grid/server-side-data-group', title: 'Server-side data', plan: 'pro', - newFeature: true, children: [ { pathname: '/x/react-data-grid/server-side-data', @@ -164,10 +163,12 @@ const pages: MuiPage[] = [ { pathname: '/x/react-data-grid/server-side-data/lazy-loading', plan: 'pro', + unstable: true, }, { pathname: '/x/react-data-grid/server-side-data/row-grouping', plan: 'premium', + unstable: true, }, { pathname: '/x/react-data-grid/server-side-data/aggregation', From f939606becd0328c9ffb45f1b07b1652dbe2f6d3 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Mon, 25 Nov 2024 12:55:34 +0100 Subject: [PATCH 085/121] Fix build --- .../ServerSideLazyLoadingModeUpdate.tsx | 14 +++++---- .../ServerSideLazyLoadingRequestThrottle.tsx | 30 ++++++++----------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.tsx index f233412666331..6362a6386c5b7 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.tsx @@ -3,7 +3,7 @@ import { DataGridPro, GridDataSource, GridGetRowsParams, - GridSlots, + GridSlotProps, } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; import FormControl from '@mui/material/FormControl'; @@ -12,12 +12,14 @@ import RadioGroup from '@mui/material/RadioGroup'; import FormControlLabel from '@mui/material/FormControlLabel'; import Radio from '@mui/material/Radio'; -interface CustomToolbarProps { - count: number; - setCount: (count: number) => void; +declare module '@mui/x-data-grid' { + interface ToolbarPropsOverrides { + count: number; + setCount: React.Dispatch>; + } } -function GridCustomToolbar({ count, setCount }: CustomToolbarProps) { +function GridCustomToolbar({ count, setCount }: GridSlotProps['toolbar']) { return ( >; + } interface FooterRowCountOverrides { - requestCount: number; + requestCount?: number; } } -interface CustomFooterRowCounProps extends GridRowCountProps { - requestCount: number; -} - -interface CustomToolbarProps { - throttleMs: number; - setThrottleMs: (value: number) => void; -} - function GridCustomFooterRowCount({ requestCount, ...props -}: CustomFooterRowCounProps) { +}: GridSlotProps['footerRowCount']) { return ( Request count: {requestCount} @@ -42,7 +36,7 @@ function GridCustomFooterRowCount({ ); } -function GridCustomToolbar({ throttleMs, setThrottleMs }: CustomToolbarProps) { +function GridCustomToolbar({ throttleMs, setThrottleMs }: GridSlotProps['toolbar']) { return ( (0); const [throttleMs, setThrottleMs] = React.useState(500); const dataSource: GridDataSource = React.useMemo( @@ -118,8 +112,8 @@ function ServerSideLazyLoadingRequestThrottle() { lazyLoadingRequestThrottleMs={throttleMs} paginationModel={{ page: 0, pageSize: 10 }} slots={{ - footerRowCount: GridCustomFooterRowCount as GridSlots['footerRowCount'], - toolbar: GridCustomToolbar as GridSlots['toolbar'], + footerRowCount: GridCustomFooterRowCount, + toolbar: GridCustomToolbar, }} slotProps={{ footerRowCount: { From 884333dd418e27dbc8b5459634ccfb03db7e101e Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Tue, 26 Nov 2024 10:15:48 +0100 Subject: [PATCH 086/121] Fix data source race condition --- .../features/dataSource/useGridDataSource.ts | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index a7dc884f808a2..44f33f222fa67 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -90,6 +90,7 @@ export const useGridDataSource = ( const paginationModel = useGridSelector(apiRef, gridPaginationModelSelector); const groupsToAutoFetch = useGridSelector(apiRef, gridRowGroupsToFetchSelector); const scheduledGroups = React.useRef(0); + const lastRequestIdentifier = React.useRef(null); const onError = props.unstable_onDataSourceError; @@ -152,6 +153,9 @@ export const useGridDataSource = ( apiRef.current.setLoading(true); } + const requestIdentifier = JSON.stringify(fetchParams); + lastRequestIdentifier.current = requestIdentifier; + try { const getRowsResponse = await getRows(fetchParams); @@ -160,18 +164,25 @@ export const useGridDataSource = ( cache.set(key, response); }); - apiRef.current.applyStrategyProcessor('dataSourceRowsUpdate', { - response: getRowsResponse, - fetchParams, - }); + if (lastRequestIdentifier.current === requestIdentifier) { + apiRef.current.applyStrategyProcessor('dataSourceRowsUpdate', { + response: getRowsResponse, + fetchParams, + }); + } } catch (error) { - apiRef.current.applyStrategyProcessor('dataSourceRowsUpdate', { - error: error as Error, - fetchParams, - }); - onError?.(error as Error, fetchParams); + if (lastRequestIdentifier.current === requestIdentifier) { + apiRef.current.applyStrategyProcessor('dataSourceRowsUpdate', { + error: error as Error, + fetchParams, + }); + onError?.(error as Error, fetchParams); + } } finally { - if (defaultRowsUpdateStrategyActive) { + if ( + defaultRowsUpdateStrategyActive && + lastRequestIdentifier.current === requestIdentifier + ) { apiRef.current.setLoading(false); } } From 5e6c42f7141275c06fec9237c7a52e4a522f714f Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Tue, 26 Nov 2024 10:57:43 +0100 Subject: [PATCH 087/121] Strategy group name as enum --- .../rowGrouping/gridRowGroupingUtils.ts | 3 ++- .../rowGrouping/useGridRowGrouping.tsx | 5 +++- .../features/dataSource/useGridDataSource.ts | 6 +++-- .../useGridDataSourceLazyLoader.ts | 5 ++-- ...useGridDataSourceTreeDataPreProcessors.tsx | 3 ++- .../treeData/useGridTreeDataPreProcessors.tsx | 3 ++- .../gridStrategyProcessingApi.ts | 26 +++++++++++-------- .../useGridStrategyProcessing.ts | 18 ++++++------- .../src/hooks/features/rows/useGridRows.ts | 6 ++++- packages/x-data-grid/src/internals/index.ts | 1 + 10 files changed, 47 insertions(+), 29 deletions(-) diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/gridRowGroupingUtils.ts b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/gridRowGroupingUtils.ts index 0310fb7b338d6..353b51936bd4a 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/gridRowGroupingUtils.ts +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/gridRowGroupingUtils.ts @@ -19,6 +19,7 @@ import { GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, getRowGroupingCriteriaFromGroupingField, isGroupingColumn, + GridStrategyGroup, } from '@mui/x-data-grid-pro/internals'; import { DataGridPremiumProcessedProps } from '../../../models/dataGridPremiumProps'; import { @@ -211,7 +212,7 @@ export const setStrategyAvailability = ( const strategy = dataSource ? RowGroupingStrategy.DataSource : RowGroupingStrategy.Default; - privateApiRef.current.setStrategyAvailability('rowTree', strategy, isAvailable); + privateApiRef.current.setStrategyAvailability(GridStrategyGroup.RowTree, strategy, isAvailable); }; export const getCellGroupingCriteria = ({ diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGrouping.tsx b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGrouping.tsx index fe562ab900c3e..b8b6779f14016 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGrouping.tsx +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGrouping.tsx @@ -10,6 +10,7 @@ import { GridPipeProcessor, GridRestoreStatePreProcessingContext, GridStateInitializer, + GridStrategyGroup, } from '@mui/x-data-grid-pro/internals'; import { GridPrivateApiPremium } from '../../../models/gridApiPremium'; import { @@ -275,7 +276,9 @@ export const useGridRowGrouping = ( // Refresh the row tree creation strategy processing // TODO: Add a clean way to re-run a strategy processing without publishing a private event - if (apiRef.current.getActiveStrategy('rowTree') === RowGroupingStrategy.Default) { + if ( + apiRef.current.getActiveStrategy(GridStrategyGroup.RowTree) === RowGroupingStrategy.Default + ) { apiRef.current.publishEvent('activeStrategyProcessorChange', 'rowTreeCreation'); } } diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index 44f33f222fa67..b42b12b6d49be 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -14,6 +14,7 @@ import { GridGetRowsResponse, gridRowGroupsToFetchSelector, GridStateInitializer, + GridStrategyGroup, GridStrategyProcessor, useGridRegisterStrategyProcessor, } from '@mui/x-data-grid/internals'; @@ -76,7 +77,7 @@ export const useGridDataSource = ( ) => { const setStrategyAvailability = React.useCallback(() => { apiRef.current.setStrategyAvailability( - 'dataSource', + GridStrategyGroup.DataSource, DataSourceRowsUpdateStrategy.Default, props.unstable_dataSource && !props.lazyLoading ? () => true : () => false, ); @@ -336,7 +337,8 @@ export const useGridDataSource = ( GridEventListener<'strategyAvailabilityChange'> >(() => { setDefaultRowsUpdateStrategyActive( - apiRef.current.getActiveStrategy('dataSource') === DataSourceRowsUpdateStrategy.Default, + apiRef.current.getActiveStrategy(GridStrategyGroup.DataSource) === + DataSourceRowsUpdateStrategy.Default, ); }, [apiRef]); 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 750be8e5d9312..21a3ab268dd61 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 @@ -18,6 +18,7 @@ import { getVisibleRows, GridGetRowsParams, gridRenderContextSelector, + GridStrategyGroup, GridStrategyProcessor, useGridRegisterStrategyProcessor, } from '@mui/x-data-grid/internals'; @@ -59,7 +60,7 @@ export const useGridDataSourceLazyLoader = ( ): void => { const setStrategyAvailability = React.useCallback(() => { privateApiRef.current.setStrategyAvailability( - 'dataSource', + GridStrategyGroup.DataSource, DataSourceRowsUpdateStrategy.LazyLoading, props.unstable_dataSource && props.lazyLoading ? () => true : () => false, ); @@ -428,7 +429,7 @@ export const useGridDataSourceLazyLoader = ( GridEventListener<'strategyAvailabilityChange'> >(() => { setLazyLoadingRowsUpdateStrategyActive( - privateApiRef.current.getActiveStrategy('dataSource') === + privateApiRef.current.getActiveStrategy(GridStrategyGroup.DataSource) === DataSourceRowsUpdateStrategy.LazyLoading, ); }, [privateApiRef]); diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridDataSourceTreeDataPreProcessors.tsx b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridDataSourceTreeDataPreProcessors.tsx index 0c6b841f939d8..f2bc190bade4a 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridDataSourceTreeDataPreProcessors.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridDataSourceTreeDataPreProcessors.tsx @@ -11,6 +11,7 @@ import { import { GridPipeProcessor, GridRowsPartialUpdates, + GridStrategyGroup, GridStrategyProcessor, useGridRegisterPipeProcessor, useGridRegisterStrategyProcessor, @@ -51,7 +52,7 @@ export const useGridDataSourceTreeDataPreProcessors = ( ) => { const setStrategyAvailability = React.useCallback(() => { privateApiRef.current.setStrategyAvailability( - 'rowTree', + GridStrategyGroup.RowTree, TreeDataStrategy.DataSource, props.treeData && props.unstable_dataSource ? () => true : () => false, ); diff --git a/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeDataPreProcessors.tsx b/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeDataPreProcessors.tsx index 6a5753c9eb60f..82ae012ad8512 100644 --- a/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeDataPreProcessors.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeDataPreProcessors.tsx @@ -10,6 +10,7 @@ import { } from '@mui/x-data-grid'; import { GridPipeProcessor, + GridStrategyGroup, GridStrategyProcessor, useGridRegisterPipeProcessor, useGridRegisterStrategyProcessor, @@ -51,7 +52,7 @@ export const useGridTreeDataPreProcessors = ( ) => { const setStrategyAvailability = React.useCallback(() => { privateApiRef.current.setStrategyAvailability( - 'rowTree', + GridStrategyGroup.RowTree, TreeDataStrategy.Default, props.treeData && !props.unstable_dataSource ? () => true : () => false, ); diff --git a/packages/x-data-grid/src/hooks/core/strategyProcessing/gridStrategyProcessingApi.ts b/packages/x-data-grid/src/hooks/core/strategyProcessing/gridStrategyProcessingApi.ts index 6ad1fb8134e9b..3daf33790fe03 100644 --- a/packages/x-data-grid/src/hooks/core/strategyProcessing/gridStrategyProcessingApi.ts +++ b/packages/x-data-grid/src/hooks/core/strategyProcessing/gridStrategyProcessingApi.ts @@ -17,12 +17,16 @@ import { GridGetRowsParams, GridGetRowsResponse } from '../../../models/gridData export type GridStrategyProcessorName = keyof GridStrategyProcessingLookup; -export type GridStrategyGroup = - GridStrategyProcessingLookup[keyof GridStrategyProcessingLookup]['group']; +export enum GridStrategyGroup { + DataSource = 'dataSource', + RowTree = 'rowTree', +} + +export type GridStrategyGroupValue = `${GridStrategyGroup}`; export interface GridStrategyProcessingLookup { dataSourceRowsUpdate: { - group: 'dataSource'; + group: GridStrategyGroup.DataSource; params: | { response: GridGetRowsResponse; @@ -35,22 +39,22 @@ export interface GridStrategyProcessingLookup { value: void; }; rowTreeCreation: { - group: 'rowTree'; + group: GridStrategyGroup.RowTree; params: GridRowTreeCreationParams; value: GridRowTreeCreationValue; }; filtering: { - group: 'rowTree'; + group: GridStrategyGroup.RowTree; params: GridFilteringMethodParams; value: GridFilteringMethodValue; }; sorting: { - group: 'rowTree'; + group: GridStrategyGroup.RowTree; params: GridSortingMethodParams; value: GridSortingMethodValue; }; visibleRowsLookupCreation: { - group: 'rowTree'; + group: GridStrategyGroup.RowTree; params: { tree: GridRowsState['tree']; filteredRowsLookup: GridFilterState['filteredRowsLookup']; @@ -80,21 +84,21 @@ export interface GridStrategyProcessingApi { ) => () => void; /** * Set a callback to know if a strategy is available. - * @param {GridStrategyGroup} strategyGroup The group for which we set strategy availability. + * @param {GridStrategyGroupValue} strategyGroup The group for which we set strategy availability. * @param {string} strategyName The name of the strategy. * @param {boolean} callback A callback to know if this strategy is available. */ setStrategyAvailability: ( - strategyGroup: GridStrategyGroup, + strategyGroup: GridStrategyGroupValue, strategyName: string, callback: () => boolean, ) => void; /** * Returns the name of the active strategy of a given strategy group - * @param {GridStrategyGroup} strategyGroup The group from which we want the active strategy. + * @param {GridStrategyGroupValue} strategyGroup The group from which we want the active strategy. * @returns {string} The name of the active strategy. */ - getActiveStrategy: (strategyGroup: GridStrategyGroup) => string; + getActiveStrategy: (strategyGroup: GridStrategyGroupValue) => string; /** * Run the processor registered for the active strategy. * @param {GridStrategyProcessorName} processorName The name of the processor to run. diff --git a/packages/x-data-grid/src/hooks/core/strategyProcessing/useGridStrategyProcessing.ts b/packages/x-data-grid/src/hooks/core/strategyProcessing/useGridStrategyProcessing.ts index 69d88c9a163c9..1fc83517a1fcb 100644 --- a/packages/x-data-grid/src/hooks/core/strategyProcessing/useGridStrategyProcessing.ts +++ b/packages/x-data-grid/src/hooks/core/strategyProcessing/useGridStrategyProcessing.ts @@ -4,7 +4,7 @@ import { GridStrategyProcessor, GridStrategyProcessorName, GridStrategyProcessingApi, - GridStrategyProcessingLookup, + GridStrategyGroupValue, GridStrategyGroup, } from './gridStrategyProcessingApi'; import { useGridApiMethod } from '../../utils/useGridApiMethod'; @@ -12,13 +12,13 @@ import { useGridApiMethod } from '../../utils/useGridApiMethod'; export const GRID_DEFAULT_STRATEGY = 'none'; export const GRID_STRATEGIES_PROCESSORS: { - [P in GridStrategyProcessorName]: GridStrategyProcessingLookup[P]['group']; + [P in GridStrategyProcessorName]: GridStrategyGroupValue; } = { - dataSourceRowsUpdate: 'dataSource', - rowTreeCreation: 'rowTree', - filtering: 'rowTree', - sorting: 'rowTree', - visibleRowsLookupCreation: 'rowTree', + dataSourceRowsUpdate: GridStrategyGroup.DataSource, + rowTreeCreation: GridStrategyGroup.RowTree, + filtering: GridStrategyGroup.RowTree, + sorting: GridStrategyGroup.RowTree, + visibleRowsLookupCreation: GridStrategyGroup.RowTree, }; type UntypedStrategyProcessors = { @@ -60,11 +60,11 @@ type UntypedStrategyProcessors = { * ===================================================================================================================== * * Each processor name is part of a strategy group which can only have one active strategy at the time. - * For now, there are two groupes named `rowTree` and `dataSource`. + * There are two active groups named `rowTree` and `dataSource`. */ export const useGridStrategyProcessing = (apiRef: React.MutableRefObject) => { const availableStrategies = React.useRef( - new Map boolean }>(), + new Map boolean }>(), ); const strategiesCache = React.useRef<{ [P in GridStrategyProcessorName]?: { [strategyName: string]: GridStrategyProcessor }; diff --git a/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts b/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts index 4e4653f9a172b..d5f4a7cdf9619 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts @@ -38,6 +38,7 @@ import { computeRowsUpdates, } from './gridRowsUtils'; import { useGridRegisterPipeApplier } from '../../core/pipeProcessing'; +import { GridStrategyGroup } from '../../core/strategyProcessing'; export const rowsStateInitializer: GridStateInitializer< Pick @@ -561,7 +562,10 @@ export const useGridRows = ( >(() => { // `rowTreeCreation` is the only processor ran when `strategyAvailabilityChange` is fired. // All the other processors listen to `rowsSet` which will be published by the `groupRows` method below. - if (apiRef.current.getActiveStrategy('rowTree') !== gridRowGroupingNameSelector(apiRef)) { + if ( + apiRef.current.getActiveStrategy(GridStrategyGroup.RowTree) !== + gridRowGroupingNameSelector(apiRef) + ) { groupRows(); } }, [apiRef, groupRows]); diff --git a/packages/x-data-grid/src/internals/index.ts b/packages/x-data-grid/src/internals/index.ts index f12ed42b1a35c..6988f682d1691 100644 --- a/packages/x-data-grid/src/internals/index.ts +++ b/packages/x-data-grid/src/internals/index.ts @@ -17,6 +17,7 @@ export { getValueOptions } from '../components/panel/filterPanel/filterPanelUtil export { useGridRegisterPipeProcessor } from '../hooks/core/pipeProcessing'; export type { GridPipeProcessor } from '../hooks/core/pipeProcessing'; export { + GridStrategyGroup, useGridRegisterStrategyProcessor, GRID_DEFAULT_STRATEGY, } from '../hooks/core/strategyProcessing'; From 53767cbaeb525864f5e5e17683e2e96d97b83e28 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Tue, 26 Nov 2024 11:37:26 +0100 Subject: [PATCH 088/121] Deprecate lazy loading and infinite loading sections for the manual row updates --- docs/data/data-grid/row-updates/row-updates.md | 16 ++++++++++++++++ .../pages/x/api/data-grid/data-grid-premium.json | 9 ++++++++- docs/pages/x/api/data-grid/data-grid-pro.json | 9 ++++++++- .../data-grid-premium/data-grid-premium.json | 4 ++-- .../data-grid/data-grid-pro/data-grid-pro.json | 4 ++-- .../src/DataGridPremium/DataGridPremium.tsx | 7 +++++-- .../src/DataGridPro/DataGridPro.tsx | 7 +++++-- .../src/models/dataGridProProps.ts | 7 +++++-- 8 files changed, 51 insertions(+), 12 deletions(-) diff --git a/docs/data/data-grid/row-updates/row-updates.md b/docs/data/data-grid/row-updates/row-updates.md index 043969d910c09..ee9e5176147ae 100644 --- a/docs/data/data-grid/row-updates/row-updates.md +++ b/docs/data/data-grid/row-updates/row-updates.md @@ -39,6 +39,14 @@ Multiple row updates at a time are supported in [Pro](/x/introduction/licensing/ ## Infinite loading [](/x/introduction/licensing/#pro-plan 'Pro plan') +:::warning +This feature is deprecated and will be removed in `v9.x`. + +Use [Server-side data infinite loading](/x/react-data-grid/server-side-data/lazy-loading/#infinite-loading) instead. + +If you are unable to migrate for some reason, please open an issue to describe what is missing in the new API so that we can improve it before this feature is removed. +::: + The grid provides a `onRowsScrollEnd` prop that can be used to load additional rows when the scroll reaches the bottom of the viewport area. In addition, the area in which `onRowsScrollEnd` is called can be changed using `scrollEndThreshold`. @@ -52,6 +60,14 @@ Otherwise, the sorting and filtering will only be applied to the subset of rows ## Lazy loading [](/x/introduction/licensing/#pro-plan 'Pro plan') +:::warning +This feature is deprecated and will be removed in `v9.x`. + +Use [Server-side data viewport loading](/x/react-data-grid/server-side-data/lazy-loading/#viewport-loading) instead. + +If you are unable to migrate for some reason, please open an issue to describe what is missing in the new API so that we can improve it before this feature is removed. +::: + Lazy Loading works like a pagination system, but instead of loading new rows based on pages, it loads them based on the viewport. It loads new rows in chunks, as the user scrolls through the Data Grid and reveals empty rows. diff --git a/docs/pages/x/api/data-grid/data-grid-premium.json b/docs/pages/x/api/data-grid/data-grid-premium.json index 2e5c46e9149ff..18fb46c0d1b2c 100644 --- a/docs/pages/x/api/data-grid/data-grid-premium.json +++ b/docs/pages/x/api/data-grid/data-grid-premium.json @@ -396,6 +396,8 @@ }, "onFetchRows": { "type": { "name": "func" }, + "deprecated": true, + "deprecationInfo": "Use Server-side data lazyLoading instead.", "signature": { "type": "function(params: GridFetchRowsParams, event: MuiEvent<{}>, details: GridCallbackDetails) => void", "describedArgs": ["params", "event", "details"] @@ -530,6 +532,8 @@ }, "onRowsScrollEnd": { "type": { "name": "func" }, + "deprecated": true, + "deprecationInfo": "Use Server-side data lazyLoading instead.", "signature": { "type": "function(params: GridRowScrollEndParams, event: MuiEvent<{}>, details: GridCallbackDetails) => void", "describedArgs": ["params", "event", "details"] @@ -605,7 +609,10 @@ "default": "{ parents: true, descendants: true }" }, "rowsLoadingMode": { - "type": { "name": "enum", "description": "'client'
| 'server'" } + "type": { "name": "enum", "description": "'client'
| 'server'" }, + "default": "\"client\"", + "deprecated": true, + "deprecationInfo": "Use Server-side data lazyLoading instead." }, "rowSpacingType": { "type": { "name": "enum", "description": "'border'
| 'margin'" }, diff --git a/docs/pages/x/api/data-grid/data-grid-pro.json b/docs/pages/x/api/data-grid/data-grid-pro.json index 704b1bf0f714b..cd9b934579261 100644 --- a/docs/pages/x/api/data-grid/data-grid-pro.json +++ b/docs/pages/x/api/data-grid/data-grid-pro.json @@ -346,6 +346,8 @@ }, "onFetchRows": { "type": { "name": "func" }, + "deprecated": true, + "deprecationInfo": "Use Server-side data lazyLoading instead.", "signature": { "type": "function(params: GridFetchRowsParams, event: MuiEvent<{}>, details: GridCallbackDetails) => void", "describedArgs": ["params", "event", "details"] @@ -473,6 +475,8 @@ }, "onRowsScrollEnd": { "type": { "name": "func" }, + "deprecated": true, + "deprecationInfo": "Use Server-side data lazyLoading instead.", "signature": { "type": "function(params: GridRowScrollEndParams, event: MuiEvent<{}>, details: GridCallbackDetails) => void", "describedArgs": ["params", "event", "details"] @@ -543,7 +547,10 @@ "default": "{ parents: true, descendants: true }" }, "rowsLoadingMode": { - "type": { "name": "enum", "description": "'client'
| 'server'" } + "type": { "name": "enum", "description": "'client'
| 'server'" }, + "default": "\"client\"", + "deprecated": true, + "deprecationInfo": "Use Server-side data lazyLoading instead." }, "rowSpacingType": { "type": { "name": "enum", "description": "'border'
| 'margin'" }, diff --git a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json index f0852be9f7443..795c0b383edef 100644 --- a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json +++ b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json @@ -625,7 +625,7 @@ "description": "When rowSelectionPropagation.descendants is set to true. - Selecting a parent selects all its filtered descendants automatically. - Deselecting a parent row deselects all its filtered descendants automatically.
When rowSelectionPropagation.parents is set to true - Selecting all the filtered descendants of a parent selects the parent automatically. - Deselecting a descendant of a selected parent deselects the parent automatically.
Works with tree data and row grouping on the client-side only." }, "rowsLoadingMode": { - "description": "Loading rows can be processed on the server or client-side. Set it to 'client' if you would like enable infnite loading. Set it to 'server' if you would like to enable lazy loading. * @default "client"" + "description": "Loading rows can be processed on the server or client-side. Set it to 'client' if you would like enable infnite loading. Set it to 'server' if you would like to enable lazy loading." }, "rowSpacingType": { "description": "Sets the type of space between rows added by getRowSpacing." @@ -634,7 +634,7 @@ "description": "Override the height/width of the Data Grid inner scrollbar." }, "scrollEndThreshold": { - "description": "Set the area in px at the bottom of the grid viewport where onRowsScrollEnd is called. If combined with lazyLoading, it defines the area where the next data request is triggered." + "description": "Set the area in px at the bottom of the grid viewport where onRowsScrollEnd (deprecated) is called. If combined with lazyLoading, it defines the area where the next data request is triggered." }, "showCellVerticalBorder": { "description": "If true, vertical borders will be displayed between cells." diff --git a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json index ed4118fb02036..b61c1de7ec901 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json +++ b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json @@ -567,7 +567,7 @@ "description": "When rowSelectionPropagation.descendants is set to true. - Selecting a parent selects all its filtered descendants automatically. - Deselecting a parent row deselects all its filtered descendants automatically.
When rowSelectionPropagation.parents is set to true - Selecting all the filtered descendants of a parent selects the parent automatically. - Deselecting a descendant of a selected parent deselects the parent automatically.
Works with tree data and row grouping on the client-side only." }, "rowsLoadingMode": { - "description": "Loading rows can be processed on the server or client-side. Set it to 'client' if you would like enable infnite loading. Set it to 'server' if you would like to enable lazy loading. * @default "client"" + "description": "Loading rows can be processed on the server or client-side. Set it to 'client' if you would like enable infnite loading. Set it to 'server' if you would like to enable lazy loading." }, "rowSpacingType": { "description": "Sets the type of space between rows added by getRowSpacing." @@ -576,7 +576,7 @@ "description": "Override the height/width of the Data Grid inner scrollbar." }, "scrollEndThreshold": { - "description": "Set the area in px at the bottom of the grid viewport where onRowsScrollEnd is called. If combined with lazyLoading, it defines the area where the next data request is triggered." + "description": "Set the area in px at the bottom of the grid viewport where onRowsScrollEnd (deprecated) is called. If combined with lazyLoading, it defines the area where the next data request is triggered." }, "showCellVerticalBorder": { "description": "If true, vertical borders will be displayed between cells." diff --git a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx index a3bb3feeeea85..85d6fe21e4a1e 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx @@ -728,6 +728,7 @@ DataGridPremiumRaw.propTypes = { * @param {GridFetchRowsParams} params With all properties from [[GridFetchRowsParams]]. * @param {MuiEvent<{}>} event The event object. * @param {GridCallbackDetails} details Additional details for this callback. + * @deprecated Use Server-side data `lazyLoading` instead. */ onFetchRows: PropTypes.func, /** @@ -855,6 +856,7 @@ DataGridPremiumRaw.propTypes = { * @param {GridRowScrollEndParams} params With all properties from [[GridRowScrollEndParams]]. * @param {MuiEvent<{}>} event The event object. * @param {GridCallbackDetails} details Additional details for this callback. + * @deprecated Use Server-side data `lazyLoading` instead. */ onRowsScrollEnd: PropTypes.func, /** @@ -1016,7 +1018,8 @@ DataGridPremiumRaw.propTypes = { * Loading rows can be processed on the server or client-side. * Set it to 'client' if you would like enable infnite loading. * Set it to 'server' if you would like to enable lazy loading. - * * @default "client" + * @default "client" + * @deprecated Use Server-side data `lazyLoading` instead. */ rowsLoadingMode: PropTypes.oneOf(['client', 'server']), /** @@ -1029,7 +1032,7 @@ DataGridPremiumRaw.propTypes = { */ scrollbarSize: PropTypes.number, /** - * Set the area in `px` at the bottom of the grid viewport where onRowsScrollEnd is called. + * Set the area in `px` at the bottom of the grid viewport where onRowsScrollEnd (deprecated) is called. * If combined with `lazyLoading`, it defines the area where the next data request is triggered. * @default 80 */ diff --git a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx index f14ea100cf80a..ec2fa5596ebd7 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx @@ -651,6 +651,7 @@ DataGridProRaw.propTypes = { * @param {GridFetchRowsParams} params With all properties from [[GridFetchRowsParams]]. * @param {MuiEvent<{}>} event The event object. * @param {GridCallbackDetails} details Additional details for this callback. + * @deprecated Use Server-side data `lazyLoading` instead. */ onFetchRows: PropTypes.func, /** @@ -772,6 +773,7 @@ DataGridProRaw.propTypes = { * @param {GridRowScrollEndParams} params With all properties from [[GridRowScrollEndParams]]. * @param {MuiEvent<{}>} event The event object. * @param {GridCallbackDetails} details Additional details for this callback. + * @deprecated Use Server-side data `lazyLoading` instead. */ onRowsScrollEnd: PropTypes.func, /** @@ -923,7 +925,8 @@ DataGridProRaw.propTypes = { * Loading rows can be processed on the server or client-side. * Set it to 'client' if you would like enable infnite loading. * Set it to 'server' if you would like to enable lazy loading. - * * @default "client" + * @default "client" + * @deprecated Use Server-side data `lazyLoading` instead. */ rowsLoadingMode: PropTypes.oneOf(['client', 'server']), /** @@ -936,7 +939,7 @@ DataGridProRaw.propTypes = { */ scrollbarSize: PropTypes.number, /** - * Set the area in `px` at the bottom of the grid viewport where onRowsScrollEnd is called. + * Set the area in `px` at the bottom of the grid viewport where onRowsScrollEnd (deprecated) is called. * If combined with `lazyLoading`, it defines the area where the next data request is triggered. * @default 80 */ diff --git a/packages/x-data-grid-pro/src/models/dataGridProProps.ts b/packages/x-data-grid-pro/src/models/dataGridProProps.ts index 8676702920213..4f25f96ffaac7 100644 --- a/packages/x-data-grid-pro/src/models/dataGridProProps.ts +++ b/packages/x-data-grid-pro/src/models/dataGridProProps.ts @@ -76,7 +76,7 @@ export interface DataGridProPropsWithDefaultValue, DataGridProSharedPropsWithDefaultValue { /** - * Set the area in `px` at the bottom of the grid viewport where onRowsScrollEnd is called. + * Set the area in `px` at the bottom of the grid viewport where onRowsScrollEnd (deprecated) is called. * If combined with `lazyLoading`, it defines the area where the next data request is triggered. * @default 80 */ @@ -130,7 +130,8 @@ export interface DataGridProPropsWithDefaultValue} event The event object. * @param {GridCallbackDetails} details Additional details for this callback. + * @deprecated Use Server-side data `lazyLoading` instead. */ onRowsScrollEnd?: GridEventListener<'rowsScrollEnd'>; /** @@ -254,6 +256,7 @@ export interface DataGridProPropsWithoutDefaultValue} event The event object. * @param {GridCallbackDetails} details Additional details for this callback. + * @deprecated Use Server-side data `lazyLoading` instead. */ onFetchRows?: GridEventListener<'fetchRows'>; /** From 5946b7e8ed1efa07c5524a67d082c874639c80e9 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Tue, 26 Nov 2024 12:41:31 +0100 Subject: [PATCH 089/121] Add chunk manager docs --- docs/data/data-grid/server-side-data/index.md | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index 035be2e8fdf6f..7c62d15a9180f 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -189,6 +189,43 @@ This means that if the user navigates to a page or expands a node that has alrea The `GridDataSourceCacheDefault` is used by default which is a simple in-memory cache that stores the data in a plain object. It can be seen in action in the [demo above](#with-data-source). +### Improving the cache-hit ratio + +To increase the cache-hit ratio, Data Grid splits `getRows` results into chunks before storing them in cache. For the next request one or more chunks are combined to recreate the response. +This means that a single request can make multiple calls to the `get` or `set` method of `GridDataSourceCache`. + +Chunk size is the lowest expected amount of records per request based on `pageSize` from `paginationModel` and `pageSizeOptions` prop. + +Because of this, values in the `pageSizeOptions` prop play a big role in the cache-hit ratio. +It is recommended to have values that are multiples of the lowest value. +Even better if every value is a multiple of the previous value. + +Here are some examples. + +1. Best scenario - `pageSizeOptions={[5, 10, 50, 100]}` + + In this case the chunk size is 5, which means that for `pageSize={100}` 20 cache records are stored. + + Retrieving data for any other `pageSize` up to the first 100 records results in a cache hit, since the whole dataset can be made of the existing chunks. + +2. Parts of the data missing - `pageSizeOptions={[10, 20, 50]}` + + Loading the first page with the `pageSize={50}` results in 5 cache records. + This works well with the `pageSize={10}`, but not all the time with `pageSize={20}`. + + Loading the third page with `pageSize={20}` results in a new request being made, even though half of the data is in the cache. + +3. Not compatible page sizes - `pageSizeOptions={[7, 15, 40]}` + + In this situation, the chunk size is 7. Retrieving the first page with `pageSize={15}` creates chunks with `[7, 7, 1]` record(s). + Loading the second page creates another 3 chunks (again `[7, 7, 1]` record(s)), but now the third chunk from the first request has an overlap of 1 record with the first chunk of the second request. + These chunks with 1 record can only be used as a last piece of a request for `pageSize={15}` and are useless in any other case. + +:::info +Keep in mind that in the examples above `sortModel` and `filterModel` remained unchanged. +Changing those would require a new response to be retrieved and stored in the chunks. +::: + ### Customize the cache lifetime The `GridDataSourceCacheDefault` has a default Time To Live (`ttl`) of 5 minutes. To customize it, pass the `ttl` option in milliseconds to the `GridDataSourceCacheDefault` constructor, and then pass it as the `unstable_dataSourceCache` prop. From d40893a891ca7c4db221d8e65205be9d4bbf3ed9 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Tue, 26 Nov 2024 15:12:01 +0100 Subject: [PATCH 090/121] Fix pages after rebase --- docs/data/pages.ts | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/docs/data/pages.ts b/docs/data/pages.ts index 11cf7ac2d8bf6..a2eb4cbbad740 100644 --- a/docs/data/pages.ts +++ b/docs/data/pages.ts @@ -90,6 +90,7 @@ const pages: MuiPage[] = [ { pathname: '/x/react-data-grid/filtering/header-filters', plan: 'pro', + newFeature: true, }, { pathname: '/x/react-data-grid/filtering-recipes', title: 'Recipes' }, ], @@ -99,7 +100,7 @@ const pages: MuiPage[] = [ pathname: '/x/react-data-grid/selection', children: [ { pathname: '/x/react-data-grid/row-selection' }, - { pathname: '/x/react-data-grid/cell-selection', plan: 'premium' }, + { pathname: '/x/react-data-grid/cell-selection', plan: 'premium', newFeature: true }, ], }, { pathname: '/x/react-data-grid/virtualization' }, @@ -113,16 +114,8 @@ const pages: MuiPage[] = [ children: [ { pathname: '/x/react-data-grid/tree-data', plan: 'pro' }, { - pathname: '/x/react-data-grid/server-side-data/lazy-loading', - plan: 'pro', - }, - { pathname: '/x/react-data-grid/aggregation', plan: 'premium' }, - { pathname: '/x/react-data-grid/pivoting', plan: 'premium', planned: true }, - { pathname: '/x/react-data-grid/export' }, - { pathname: '/x/react-data-grid/clipboard', title: 'Copy and paste' }, - { pathname: '/x/react-data-grid/scrolling' }, - { - pathname: '/x/react-data-grid/server-side-data/row-grouping', + pathname: '/x/react-data-grid/row-grouping-group', + title: 'Row grouping', plan: 'premium', children: [ { pathname: '/x/react-data-grid/row-grouping', title: 'Overview' }, @@ -352,6 +345,7 @@ const pages: MuiPage[] = [ { pathname: '/x/react-date-pickers/date-time-range-picker', title: 'Date Time Range Picker', + newFeature: true, }, { pathname: '/x/react-date-pickers/date-time-range-field', @@ -421,6 +415,7 @@ const pages: MuiPage[] = [ { pathname: '/x/react-charts-group', title: 'Charts', + newFeature: true, children: [ { pathname: '/x/react-charts', title: 'Overview' }, { pathname: '/x/react-charts/getting-started' }, @@ -470,6 +465,7 @@ const pages: MuiPage[] = [ pathname: '/x/react-charts/heatmap', title: 'Heatmap', plan: 'pro', + unstable: true, }, { pathname: '/x/react-charts/main-features', @@ -488,6 +484,7 @@ const pages: MuiPage[] = [ pathname: '/x/react-charts/zoom-and-pan', title: 'Zoom and pan', plan: 'pro', + unstable: true, }, ], }, @@ -533,6 +530,7 @@ const pages: MuiPage[] = [ { pathname: '/x/react-tree-view-group', title: 'Tree View', + newFeature: true, children: [ { pathname: '/x/react-tree-view', title: 'Overview' }, { pathname: '/x/react-tree-view/getting-started' }, @@ -556,8 +554,8 @@ const pages: MuiPage[] = [ { pathname: '/x/react-tree-view/rich-tree-view/expansion' }, { pathname: '/x/react-tree-view/rich-tree-view/customization' }, { pathname: '/x/react-tree-view/rich-tree-view/focus' }, - { pathname: '/x/react-tree-view/rich-tree-view/editing', newFeature: true }, - { pathname: '/x/react-tree-view/rich-tree-view/ordering', plan: 'pro', newFeature: true }, + { pathname: '/x/react-tree-view/rich-tree-view/editing' }, + { pathname: '/x/react-tree-view/rich-tree-view/ordering', plan: 'pro' }, ], }, { From 47c436aab373713532f11c02415926bf98f64c36 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Tue, 26 Nov 2024 15:13:37 +0100 Subject: [PATCH 091/121] Fix pages again --- docs/data/pages.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/docs/data/pages.ts b/docs/data/pages.ts index a2eb4cbbad740..2479daebb4edd 100644 --- a/docs/data/pages.ts +++ b/docs/data/pages.ts @@ -90,7 +90,6 @@ const pages: MuiPage[] = [ { pathname: '/x/react-data-grid/filtering/header-filters', plan: 'pro', - newFeature: true, }, { pathname: '/x/react-data-grid/filtering-recipes', title: 'Recipes' }, ], @@ -100,7 +99,7 @@ const pages: MuiPage[] = [ pathname: '/x/react-data-grid/selection', children: [ { pathname: '/x/react-data-grid/row-selection' }, - { pathname: '/x/react-data-grid/cell-selection', plan: 'premium', newFeature: true }, + { pathname: '/x/react-data-grid/cell-selection', plan: 'premium' }, ], }, { pathname: '/x/react-data-grid/virtualization' }, @@ -128,7 +127,7 @@ const pages: MuiPage[] = [ { pathname: '/x/react-data-grid/aggregation', plan: 'premium' }, { pathname: '/x/react-data-grid/pivoting', plan: 'premium', planned: true }, { pathname: '/x/react-data-grid/export' }, - { pathname: '/x/react-data-grid/clipboard', title: 'Copy and paste', newFeature: true }, + { pathname: '/x/react-data-grid/clipboard', title: 'Copy and paste' }, { pathname: '/x/react-data-grid/scrolling' }, { @@ -345,7 +344,6 @@ const pages: MuiPage[] = [ { pathname: '/x/react-date-pickers/date-time-range-picker', title: 'Date Time Range Picker', - newFeature: true, }, { pathname: '/x/react-date-pickers/date-time-range-field', @@ -415,7 +413,6 @@ const pages: MuiPage[] = [ { pathname: '/x/react-charts-group', title: 'Charts', - newFeature: true, children: [ { pathname: '/x/react-charts', title: 'Overview' }, { pathname: '/x/react-charts/getting-started' }, @@ -465,7 +462,6 @@ const pages: MuiPage[] = [ pathname: '/x/react-charts/heatmap', title: 'Heatmap', plan: 'pro', - unstable: true, }, { pathname: '/x/react-charts/main-features', @@ -484,7 +480,6 @@ const pages: MuiPage[] = [ pathname: '/x/react-charts/zoom-and-pan', title: 'Zoom and pan', plan: 'pro', - unstable: true, }, ], }, @@ -530,7 +525,6 @@ const pages: MuiPage[] = [ { pathname: '/x/react-tree-view-group', title: 'Tree View', - newFeature: true, children: [ { pathname: '/x/react-tree-view', title: 'Overview' }, { pathname: '/x/react-tree-view/getting-started' }, @@ -554,8 +548,8 @@ const pages: MuiPage[] = [ { pathname: '/x/react-tree-view/rich-tree-view/expansion' }, { pathname: '/x/react-tree-view/rich-tree-view/customization' }, { pathname: '/x/react-tree-view/rich-tree-view/focus' }, - { pathname: '/x/react-tree-view/rich-tree-view/editing' }, - { pathname: '/x/react-tree-view/rich-tree-view/ordering', plan: 'pro' }, + { pathname: '/x/react-tree-view/rich-tree-view/editing', newFeature: true }, + { pathname: '/x/react-tree-view/rich-tree-view/ordering', plan: 'pro', newFeature: true }, ], }, { From 0c0ec6a0e9026772cfbcc9938be1d34506ae66ce Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 27 Nov 2024 09:52:48 +0100 Subject: [PATCH 092/121] Do not clear existing row count if the new response does not have a new row count --- docs/data/data-grid/server-side-data/lazy-loading.md | 2 +- .../serverSideLazyLoader/useGridDataSourceLazyLoader.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/data/data-grid/server-side-data/lazy-loading.md b/docs/data/data-grid/server-side-data/lazy-loading.md index e8f7c5c1c4d64..8b83c5c43da40 100644 --- a/docs/data/data-grid/server-side-data/lazy-loading.md +++ b/docs/data/data-grid/server-side-data/lazy-loading.md @@ -24,7 +24,7 @@ Row count can be provided in either of the following ways. - Return `rowCount` in the `getRows` method of the [data source](/x/react-data-grid/server-side-data/#data-source) - Set the `rowCount` using the [`setRowCount`](/x/api/data-grid/grid-api/#grid-api-prop-setRowCount) API method. -The above list is given in the order of precedence, which means if the row count is set using the API, that value gets overridden once a new value is returned by the `getRows` method, even if it is `undefined`. +The above list is given in the order of precedence, which means if the row count is set using the API, that value gets overridden once a new value is returned by the `getRows` method, unless if it is `undefined`. ::: ## Viewport loading 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 21a3ab268dd61..18631374f1e29 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 @@ -219,7 +219,10 @@ export const useGridDataSourceLazyLoader = ( } const { response, fetchParams } = params; - privateApiRef.current.setRowCount(response.rowCount === undefined ? -1 : response.rowCount); + const pageRowCount = privateApiRef.current.state.pagination.rowCount; + if (response.rowCount !== undefined || pageRowCount === undefined) { + privateApiRef.current.setRowCount(response.rowCount === undefined ? -1 : response.rowCount); + } if (rowsStale.current) { rowsStale.current = false; From 771e4e13bb664da0781b3cfa07fd9f63724a0af9 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 27 Nov 2024 10:18:55 +0100 Subject: [PATCH 093/121] Align wording --- docs/data/data-grid/server-side-data/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index 7c62d15a9180f..c4cd7b533e5d7 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -189,14 +189,14 @@ This means that if the user navigates to a page or expands a node that has alrea The `GridDataSourceCacheDefault` is used by default which is a simple in-memory cache that stores the data in a plain object. It can be seen in action in the [demo above](#with-data-source). -### Improving the cache-hit ratio +### Improving the cache hit rate -To increase the cache-hit ratio, Data Grid splits `getRows` results into chunks before storing them in cache. For the next request one or more chunks are combined to recreate the response. +To increase the cache hit rate, Data Grid splits `getRows` results into chunks before storing them in cache. For the next request one or more chunks are combined to recreate the response. This means that a single request can make multiple calls to the `get` or `set` method of `GridDataSourceCache`. Chunk size is the lowest expected amount of records per request based on `pageSize` from `paginationModel` and `pageSizeOptions` prop. -Because of this, values in the `pageSizeOptions` prop play a big role in the cache-hit ratio. +Because of this, values in the `pageSizeOptions` prop play a big role in the cache hit rate. It is recommended to have values that are multiples of the lowest value. Even better if every value is a multiple of the previous value. From 2f80265e7806e7469cc172d8fc7356c1b870d773 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 27 Nov 2024 10:44:08 +0100 Subject: [PATCH 094/121] Revert deprecations. They will be handled in a separate PR --- docs/data/data-grid/row-updates/row-updates.md | 16 ---------------- .../pages/x/api/data-grid/data-grid-premium.json | 8 +------- docs/pages/x/api/data-grid/data-grid-pro.json | 8 +------- .../data-grid-premium/data-grid-premium.json | 2 +- .../data-grid/data-grid-pro/data-grid-pro.json | 2 +- .../src/DataGridPremium/DataGridPremium.tsx | 5 +---- .../src/DataGridPro/DataGridPro.tsx | 5 +---- .../src/models/dataGridProProps.ts | 5 +---- 8 files changed, 7 insertions(+), 44 deletions(-) diff --git a/docs/data/data-grid/row-updates/row-updates.md b/docs/data/data-grid/row-updates/row-updates.md index ee9e5176147ae..043969d910c09 100644 --- a/docs/data/data-grid/row-updates/row-updates.md +++ b/docs/data/data-grid/row-updates/row-updates.md @@ -39,14 +39,6 @@ Multiple row updates at a time are supported in [Pro](/x/introduction/licensing/ ## Infinite loading [](/x/introduction/licensing/#pro-plan 'Pro plan') -:::warning -This feature is deprecated and will be removed in `v9.x`. - -Use [Server-side data infinite loading](/x/react-data-grid/server-side-data/lazy-loading/#infinite-loading) instead. - -If you are unable to migrate for some reason, please open an issue to describe what is missing in the new API so that we can improve it before this feature is removed. -::: - The grid provides a `onRowsScrollEnd` prop that can be used to load additional rows when the scroll reaches the bottom of the viewport area. In addition, the area in which `onRowsScrollEnd` is called can be changed using `scrollEndThreshold`. @@ -60,14 +52,6 @@ Otherwise, the sorting and filtering will only be applied to the subset of rows ## Lazy loading [](/x/introduction/licensing/#pro-plan 'Pro plan') -:::warning -This feature is deprecated and will be removed in `v9.x`. - -Use [Server-side data viewport loading](/x/react-data-grid/server-side-data/lazy-loading/#viewport-loading) instead. - -If you are unable to migrate for some reason, please open an issue to describe what is missing in the new API so that we can improve it before this feature is removed. -::: - Lazy Loading works like a pagination system, but instead of loading new rows based on pages, it loads them based on the viewport. It loads new rows in chunks, as the user scrolls through the Data Grid and reveals empty rows. diff --git a/docs/pages/x/api/data-grid/data-grid-premium.json b/docs/pages/x/api/data-grid/data-grid-premium.json index 18fb46c0d1b2c..67b2adcf948b0 100644 --- a/docs/pages/x/api/data-grid/data-grid-premium.json +++ b/docs/pages/x/api/data-grid/data-grid-premium.json @@ -396,8 +396,6 @@ }, "onFetchRows": { "type": { "name": "func" }, - "deprecated": true, - "deprecationInfo": "Use Server-side data lazyLoading instead.", "signature": { "type": "function(params: GridFetchRowsParams, event: MuiEvent<{}>, details: GridCallbackDetails) => void", "describedArgs": ["params", "event", "details"] @@ -532,8 +530,6 @@ }, "onRowsScrollEnd": { "type": { "name": "func" }, - "deprecated": true, - "deprecationInfo": "Use Server-side data lazyLoading instead.", "signature": { "type": "function(params: GridRowScrollEndParams, event: MuiEvent<{}>, details: GridCallbackDetails) => void", "describedArgs": ["params", "event", "details"] @@ -610,9 +606,7 @@ }, "rowsLoadingMode": { "type": { "name": "enum", "description": "'client'
| 'server'" }, - "default": "\"client\"", - "deprecated": true, - "deprecationInfo": "Use Server-side data lazyLoading instead." + "default": "\"client\"" }, "rowSpacingType": { "type": { "name": "enum", "description": "'border'
| 'margin'" }, diff --git a/docs/pages/x/api/data-grid/data-grid-pro.json b/docs/pages/x/api/data-grid/data-grid-pro.json index cd9b934579261..363bb108eea28 100644 --- a/docs/pages/x/api/data-grid/data-grid-pro.json +++ b/docs/pages/x/api/data-grid/data-grid-pro.json @@ -346,8 +346,6 @@ }, "onFetchRows": { "type": { "name": "func" }, - "deprecated": true, - "deprecationInfo": "Use Server-side data lazyLoading instead.", "signature": { "type": "function(params: GridFetchRowsParams, event: MuiEvent<{}>, details: GridCallbackDetails) => void", "describedArgs": ["params", "event", "details"] @@ -475,8 +473,6 @@ }, "onRowsScrollEnd": { "type": { "name": "func" }, - "deprecated": true, - "deprecationInfo": "Use Server-side data lazyLoading instead.", "signature": { "type": "function(params: GridRowScrollEndParams, event: MuiEvent<{}>, details: GridCallbackDetails) => void", "describedArgs": ["params", "event", "details"] @@ -548,9 +544,7 @@ }, "rowsLoadingMode": { "type": { "name": "enum", "description": "'client'
| 'server'" }, - "default": "\"client\"", - "deprecated": true, - "deprecationInfo": "Use Server-side data lazyLoading instead." + "default": "\"client\"" }, "rowSpacingType": { "type": { "name": "enum", "description": "'border'
| 'margin'" }, diff --git a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json index 795c0b383edef..2e638fee6f663 100644 --- a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json +++ b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json @@ -634,7 +634,7 @@ "description": "Override the height/width of the Data Grid inner scrollbar." }, "scrollEndThreshold": { - "description": "Set the area in px at the bottom of the grid viewport where onRowsScrollEnd (deprecated) is called. If combined with lazyLoading, it defines the area where the next data request is triggered." + "description": "Set the area in px at the bottom of the grid viewport where onRowsScrollEnd is called. If combined with lazyLoading, it defines the area where the next data request is triggered." }, "showCellVerticalBorder": { "description": "If true, vertical borders will be displayed between cells." diff --git a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json index b61c1de7ec901..c479e06313622 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json +++ b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json @@ -576,7 +576,7 @@ "description": "Override the height/width of the Data Grid inner scrollbar." }, "scrollEndThreshold": { - "description": "Set the area in px at the bottom of the grid viewport where onRowsScrollEnd (deprecated) is called. If combined with lazyLoading, it defines the area where the next data request is triggered." + "description": "Set the area in px at the bottom of the grid viewport where onRowsScrollEnd is called. If combined with lazyLoading, it defines the area where the next data request is triggered." }, "showCellVerticalBorder": { "description": "If true, vertical borders will be displayed between cells." diff --git a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx index 85d6fe21e4a1e..fa80c399a0f78 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx @@ -728,7 +728,6 @@ DataGridPremiumRaw.propTypes = { * @param {GridFetchRowsParams} params With all properties from [[GridFetchRowsParams]]. * @param {MuiEvent<{}>} event The event object. * @param {GridCallbackDetails} details Additional details for this callback. - * @deprecated Use Server-side data `lazyLoading` instead. */ onFetchRows: PropTypes.func, /** @@ -856,7 +855,6 @@ DataGridPremiumRaw.propTypes = { * @param {GridRowScrollEndParams} params With all properties from [[GridRowScrollEndParams]]. * @param {MuiEvent<{}>} event The event object. * @param {GridCallbackDetails} details Additional details for this callback. - * @deprecated Use Server-side data `lazyLoading` instead. */ onRowsScrollEnd: PropTypes.func, /** @@ -1019,7 +1017,6 @@ DataGridPremiumRaw.propTypes = { * Set it to 'client' if you would like enable infnite loading. * Set it to 'server' if you would like to enable lazy loading. * @default "client" - * @deprecated Use Server-side data `lazyLoading` instead. */ rowsLoadingMode: PropTypes.oneOf(['client', 'server']), /** @@ -1032,7 +1029,7 @@ DataGridPremiumRaw.propTypes = { */ scrollbarSize: PropTypes.number, /** - * Set the area in `px` at the bottom of the grid viewport where onRowsScrollEnd (deprecated) is called. + * Set the area in `px` at the bottom of the grid viewport where onRowsScrollEnd is called. * If combined with `lazyLoading`, it defines the area where the next data request is triggered. * @default 80 */ diff --git a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx index ec2fa5596ebd7..98ab95462e8f1 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx @@ -651,7 +651,6 @@ DataGridProRaw.propTypes = { * @param {GridFetchRowsParams} params With all properties from [[GridFetchRowsParams]]. * @param {MuiEvent<{}>} event The event object. * @param {GridCallbackDetails} details Additional details for this callback. - * @deprecated Use Server-side data `lazyLoading` instead. */ onFetchRows: PropTypes.func, /** @@ -773,7 +772,6 @@ DataGridProRaw.propTypes = { * @param {GridRowScrollEndParams} params With all properties from [[GridRowScrollEndParams]]. * @param {MuiEvent<{}>} event The event object. * @param {GridCallbackDetails} details Additional details for this callback. - * @deprecated Use Server-side data `lazyLoading` instead. */ onRowsScrollEnd: PropTypes.func, /** @@ -926,7 +924,6 @@ DataGridProRaw.propTypes = { * Set it to 'client' if you would like enable infnite loading. * Set it to 'server' if you would like to enable lazy loading. * @default "client" - * @deprecated Use Server-side data `lazyLoading` instead. */ rowsLoadingMode: PropTypes.oneOf(['client', 'server']), /** @@ -939,7 +936,7 @@ DataGridProRaw.propTypes = { */ scrollbarSize: PropTypes.number, /** - * Set the area in `px` at the bottom of the grid viewport where onRowsScrollEnd (deprecated) is called. + * Set the area in `px` at the bottom of the grid viewport where onRowsScrollEnd is called. * If combined with `lazyLoading`, it defines the area where the next data request is triggered. * @default 80 */ diff --git a/packages/x-data-grid-pro/src/models/dataGridProProps.ts b/packages/x-data-grid-pro/src/models/dataGridProProps.ts index 4f25f96ffaac7..3594873e34d53 100644 --- a/packages/x-data-grid-pro/src/models/dataGridProProps.ts +++ b/packages/x-data-grid-pro/src/models/dataGridProProps.ts @@ -76,7 +76,7 @@ export interface DataGridProPropsWithDefaultValue, DataGridProSharedPropsWithDefaultValue { /** - * Set the area in `px` at the bottom of the grid viewport where onRowsScrollEnd (deprecated) is called. + * Set the area in `px` at the bottom of the grid viewport where onRowsScrollEnd is called. * If combined with `lazyLoading`, it defines the area where the next data request is triggered. * @default 80 */ @@ -131,7 +131,6 @@ export interface DataGridProPropsWithDefaultValue} event The event object. * @param {GridCallbackDetails} details Additional details for this callback. - * @deprecated Use Server-side data `lazyLoading` instead. */ onRowsScrollEnd?: GridEventListener<'rowsScrollEnd'>; /** @@ -256,7 +254,6 @@ export interface DataGridProPropsWithoutDefaultValue} event The event object. * @param {GridCallbackDetails} details Additional details for this callback. - * @deprecated Use Server-side data `lazyLoading` instead. */ onFetchRows?: GridEventListener<'fetchRows'>; /** From 516d2c842dbc16ca7fa5bc0dd26ac47957851351 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 27 Nov 2024 12:44:48 +0100 Subject: [PATCH 095/121] Performance improvements --- .../useGridDataSourceLazyLoader.ts | 133 +++++++++++------- 1 file changed, 83 insertions(+), 50 deletions(-) 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 18631374f1e29..200d62add3284 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 @@ -140,61 +140,95 @@ export const useGridDataSourceLazyLoader = ( [privateApiRef, resetGrid], ); - const addSkeletonRows = React.useCallback( - (fillEmptyGrid = false) => { - const tree = privateApiRef.current.state.rows.tree; - const rootGroup = tree[GRID_ROOT_GROUP_ID] as GridGroupNode; - const rootGroupChildren = [...rootGroup.children]; + const addSkeletonRows = React.useCallback(() => { + const tree = privateApiRef.current.state.rows.tree; + const rootGroup = tree[GRID_ROOT_GROUP_ID] as GridGroupNode; + const rootGroupChildren = [...rootGroup.children]; + + const pageRowCount = privateApiRef.current.state.pagination.rowCount; + const rootChildrenCount = rootGroupChildren.length; + + /** + * Do nothing if + * - rowCount is unknown + * - children count is 0 + * - children count is equal to rowCount + */ + if ( + pageRowCount === -1 || + pageRowCount === undefined || + rootChildrenCount === 0 || + rootChildrenCount === pageRowCount + ) { + return; + } - const pageRowCount = privateApiRef.current.state.pagination.rowCount; - const rootChildrenCount = rootGroupChildren.length; + // fill the grid with skeleton rows + for (let i = 0; i < pageRowCount - rootChildrenCount; i += 1) { + const skeletonId = getSkeletonRowId(i); + rootGroupChildren.push(skeletonId); - /** - * Do nothing if - * - rowCount is unknown - * - children count is 0 and empty grid should not be filled - * - children count is equal to rowCount - */ - if ( - pageRowCount === -1 || - pageRowCount === undefined || - (!fillEmptyGrid && rootChildrenCount === 0) || - rootChildrenCount === pageRowCount - ) { - return; - } + const skeletonRowNode: GridSkeletonRowNode = { + type: 'skeletonRow', + id: skeletonId, + parent: GRID_ROOT_GROUP_ID, + depth: 0, + }; - // fill the grid with skeleton rows - for (let i = 0; i < pageRowCount - rootChildrenCount; i += 1) { - const skeletonId = getSkeletonRowId(i); + tree[skeletonId] = skeletonRowNode; + } - rootGroupChildren.push(skeletonId); + tree[GRID_ROOT_GROUP_ID] = { ...rootGroup, children: rootGroupChildren }; - const skeletonRowNode: GridSkeletonRowNode = { - type: 'skeletonRow', - id: skeletonId, - parent: GRID_ROOT_GROUP_ID, - depth: 0, - }; + privateApiRef.current.setState( + (state) => ({ + ...state, + rows: { + ...state.rows, + tree, + }, + }), + 'addSkeletonRows', + ); + }, [privateApiRef]); - tree[skeletonId] = skeletonRowNode; + const rebuildSkeletonRows = React.useCallback(() => { + // replace all data rows with skeleton rows. After that, fill the grid with the new skeleton rows + const tree = privateApiRef.current.state.rows.tree; + const rootGroup = tree[GRID_ROOT_GROUP_ID] as GridGroupNode; + const rootGroupChildren = [...rootGroup.children]; + + for (let i = 0; i < rootGroupChildren.length; i += 1) { + if (tree[rootGroupChildren[i]]?.type === 'skeletonRow') { + continue; } - tree[GRID_ROOT_GROUP_ID] = { ...rootGroup, children: rootGroupChildren }; - - privateApiRef.current.setState( - (state) => ({ - ...state, - rows: { - ...state.rows, - tree, - }, - }), - 'addSkeletonRows', - ); - }, - [privateApiRef], - ); + const skeletonId = getSkeletonRowId(i); + rootGroupChildren[i] = skeletonId; + + const skeletonRowNode: GridSkeletonRowNode = { + type: 'skeletonRow', + id: skeletonId, + parent: GRID_ROOT_GROUP_ID, + depth: 0, + }; + + tree[rootGroupChildren[i]] = skeletonRowNode; + } + + tree[GRID_ROOT_GROUP_ID] = { ...rootGroup, children: rootGroupChildren }; + + privateApiRef.current.setState( + (state) => ({ + ...state, + rows: { + ...state.rows, + tree, + }, + }), + 'addSkeletonRows', + ); + }, [privateApiRef]); const updateLoadingTrigger = React.useCallback( (rowCount: number) => { @@ -390,8 +424,7 @@ export const useGridDataSourceLazyLoader = ( privateApiRef.current.setLoading(true); if (loadingTrigger.current === LoadingTrigger.VIEWPORT) { // replace all rows with skeletons to maintain the same scroll position - privateApiRef.current.setRows([]); - addSkeletonRows(true); + rebuildSkeletonRows(); } else { rowsStale.current = true; } @@ -406,7 +439,7 @@ export const useGridDataSourceLazyLoader = ( filterModel, paginationModel.pageSize, renderContext, - addSkeletonRows, + rebuildSkeletonRows, adjustRowParams, ], ); From 9aa1825d373a84615033f1b8316c8ea24f82cb21 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic <4390250+arminmeh@users.noreply.github.com> Date: Wed, 27 Nov 2024 12:56:46 +0100 Subject: [PATCH 096/121] Update docs Co-authored-by: Bilal Shafi Signed-off-by: Armin Mehinovic <4390250+arminmeh@users.noreply.github.com> --- .../data/data-grid/server-side-data/lazy-loading.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/data/data-grid/server-side-data/lazy-loading.md b/docs/data/data-grid/server-side-data/lazy-loading.md index 8b83c5c43da40..fa7ad38e31f94 100644 --- a/docs/data/data-grid/server-side-data/lazy-loading.md +++ b/docs/data/data-grid/server-side-data/lazy-loading.md @@ -46,9 +46,9 @@ Open info section of the browser console to see the requests being made and the ### Request throttling -While user is scrolling through the grid, rendering context changes and the Data Grid tries to fill in any missing rows by making a new data source request. This avoid making huge amount of request, Data Grid throttles new data fetches on the rendering context change. By default throttle is set to 500 ms, but the time can be controlled with `lazyLoadingRequestThrottleMs` prop. - -The demo below shows the difference in behavior for different values of the `lazyLoadingRequestThrottleMs` prop. In the footer of the Data Grid, you can see the amount of requests made while scrolling. +While user is scrolling through the grid, rendering context changes and the Data Grid tries to fill in any missing rows by making a new server request. +The Data Grid throttles new data fetches on the rendering context change to avoid doing unnecessary requests. +The default throttle time is 500 milliseconds, use `lazyLoadingRequestThrottleMs` prop to customize it, as the following example demonstrates. {{"demo": "ServerSideLazyLoadingRequestThrottle.js", "bg": "inline"}} @@ -75,14 +75,14 @@ Based on the previous and the new value for the total row count, the following s - **Known `rowCount` greater than the actual row count**: This can happen either by reducing the value of the row count after more rows were already fetched or if the row count was unknown and the grid in the inifite loading mode already fetched more rows. In this case, the grid resets, fetches the first page and continues in one of the modes depending on the new value of the `rowCount`. :::warning -`rowCount` is expected to be static. Changing its value can cause the grid to reset and the cache to be cleared which leads to poor performance and user experience. +`rowCount` is expected to be static. Changing its value can cause the grid to reset and the cache to be cleared which may lead to performance and UX degradation. ::: The demo below serves more as a showcase of the behavior described above and is not representing something you would implement in a real-world scenario. {{"demo": "ServerSideLazyLoadingModeUpdate.js", "bg": "inline"}} -## Nested rows 🚧 +## Nested lazy loading 🚧 :::warning This feature isn't implemented yet. It's coming. @@ -98,7 +98,8 @@ When completed, it would be possible to use `lazyLoading` flag in combination wi To handle errors, use `unstable_onDataSourceError` prop as described in the [Error handling](/x/react-data-grid/server-side-data/#error-handling) section of the data source overview page. -Second parameter of type `GridGetRowsParams` can be passed to `getRows` method of the [`unstable_dataSource`](/x/api/data-grid/grid-api/#grid-api-prop-unstable_dataSource) to retry the request. If successful, the grid uses `rows` and `rowCount` data to determine if the rows should be appended at the end of the grid or if the skeleton rows should be replaced. +Second parameter of type `GridGetRowsParams` can be passed to `getRows` method of the [`unstable_dataSource`](/x/api/data-grid/grid-api/#grid-api-prop-unstable_dataSource) to retry the request. +If successful, the Data Grid uses `rows` and `rowCount` data to determine if the rows should be appended at the end of the grid or if the skeleton rows should be replaced. The following demo gives an example how to use `GridGetRowsParams` to retry a failed request. From 0bd329e29f88d4f1a164db4d3fedda8e9d265ebc Mon Sep 17 00:00:00 2001 From: Armin Mehinovic <4390250+arminmeh@users.noreply.github.com> Date: Wed, 27 Nov 2024 12:57:31 +0100 Subject: [PATCH 097/121] Code cleanup Co-authored-by: Bilal Shafi Signed-off-by: Armin Mehinovic <4390250+arminmeh@users.noreply.github.com> --- .../src/hooks/features/dataSource/useGridDataSource.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index b42b12b6d49be..3ad2ee660b8d2 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -104,7 +104,7 @@ export const useGridDataSource = ( }, [paginationModel.pageSize, props.pageSizeOptions]); const cacheChunkManager = useLazyRef( - () => new CacheChunkManager({ chunkSize: cacheChunkSize }), + () => new CacheChunkManager(cacheChunkSize), ).current; const [cache, setCache] = React.useState(() => getCache(props.unstable_dataSourceCache), @@ -136,7 +136,7 @@ export const useGridDataSource = ( }; const cacheKeys = cacheChunkManager.getCacheKeys(fetchParams); - const responses = cacheKeys.map((cacheKey) => cache.get(cacheKey)); + const responses = cacheKeys.map(cache.get); const cachedData = responses.some((response) => response === undefined) ? undefined : CacheChunkManager.mergeResponses(responses as GridGetRowsResponse[]); From a15210c3da3fb3f9bbc55a1e59eafae8e55e129c Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 27 Nov 2024 12:55:45 +0100 Subject: [PATCH 098/121] Request identifier as a number --- .../features/dataSource/useGridDataSource.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index 3ad2ee660b8d2..32082c5b55667 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -91,7 +91,7 @@ export const useGridDataSource = ( const paginationModel = useGridSelector(apiRef, gridPaginationModelSelector); const groupsToAutoFetch = useGridSelector(apiRef, gridRowGroupsToFetchSelector); const scheduledGroups = React.useRef(0); - const lastRequestIdentifier = React.useRef(null); + const lastRequestId = React.useRef(0); const onError = props.unstable_onDataSourceError; @@ -154,8 +154,8 @@ export const useGridDataSource = ( apiRef.current.setLoading(true); } - const requestIdentifier = JSON.stringify(fetchParams); - lastRequestIdentifier.current = requestIdentifier; + const requestId = lastRequestId.current + 1; + lastRequestId.current = requestId; try { const getRowsResponse = await getRows(fetchParams); @@ -165,14 +165,14 @@ export const useGridDataSource = ( cache.set(key, response); }); - if (lastRequestIdentifier.current === requestIdentifier) { + if (lastRequestId.current === requestId) { apiRef.current.applyStrategyProcessor('dataSourceRowsUpdate', { response: getRowsResponse, fetchParams, }); } } catch (error) { - if (lastRequestIdentifier.current === requestIdentifier) { + if (lastRequestId.current === requestId) { apiRef.current.applyStrategyProcessor('dataSourceRowsUpdate', { error: error as Error, fetchParams, @@ -180,10 +180,7 @@ export const useGridDataSource = ( onError?.(error as Error, fetchParams); } } finally { - if ( - defaultRowsUpdateStrategyActive && - lastRequestIdentifier.current === requestIdentifier - ) { + if (defaultRowsUpdateStrategyActive && lastRequestId.current === requestId) { apiRef.current.setLoading(false); } } From d932f23084825d5388253f2d39c321fb7c140baf Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 27 Nov 2024 13:02:09 +0100 Subject: [PATCH 099/121] More code cleanup --- .../src/hooks/features/dataSource/utils.ts | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts index 79bb08ebbd938..110f6efecf776 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts @@ -9,15 +9,6 @@ export const runIf = (condition: boolean, fn: Function) => (params: unknown) => } }; -export type GridDataSourceCacheChunkManagerConfig = { - /** - * The number of rows to store in each cache entry. If not set, the whole array will be stored in a single cache entry. - * Setting this value to smallest page size will result in better cache hit rate. - * Has no effect if cursor pagination is used. - */ - chunkSize: number; -}; - export enum RequestStatus { QUEUED, PENDING, @@ -135,8 +126,14 @@ export class NestedDataManager { export class CacheChunkManager { private chunkSize: number; - constructor(config: GridDataSourceCacheChunkManagerConfig) { - this.chunkSize = config.chunkSize; + /** + * @param chunkSize The number of rows to store in each cache entry. + * If not set, the whole array will be stored in a single cache entry. + * Setting this value to smallest page size will result in better cache hit rate. + * Has no effect if cursor pagination is used. + */ + constructor(chunkSize: number) { + this.chunkSize = chunkSize; } public getCacheKeys = (key: GridGetRowsParams) => { @@ -190,13 +187,11 @@ export class CacheChunkManager { } return responses.reduce( - (acc, response) => { - return { - rows: [...acc.rows, ...response.rows], - rowCount: response.rowCount, - pageInfo: response.pageInfo, - }; - }, + (acc, response) => ({ + rows: [...acc.rows, ...response.rows], + rowCount: response.rowCount, + pageInfo: response.pageInfo, + }), { rows: [], rowCount: 0, pageInfo: {} }, ); }; From 514a786ba36f4f6f546602df6b09f535688f97cc Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 27 Nov 2024 13:07:59 +0100 Subject: [PATCH 100/121] Fix build issue --- .../src/hooks/features/dataSource/useGridDataSource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index 32082c5b55667..b5ea8ad0201fe 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -136,7 +136,7 @@ export const useGridDataSource = ( }; const cacheKeys = cacheChunkManager.getCacheKeys(fetchParams); - const responses = cacheKeys.map(cache.get); + const responses = cacheKeys.map((cacheKey) => cache.get(cacheKey)); const cachedData = responses.some((response) => response === undefined) ? undefined : CacheChunkManager.mergeResponses(responses as GridGetRowsResponse[]); From 60621dee348e5f3ac3015cbe46964c7f1586dc41 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 27 Nov 2024 13:15:03 +0100 Subject: [PATCH 101/121] Static row count for better UI --- .../server-side-data/ServerSideLazyLoadingRequestThrottle.js | 5 +++-- .../ServerSideLazyLoadingRequestThrottle.tsx | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingRequestThrottle.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingRequestThrottle.js index aadaa223aeb58..f002a48660a85 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingRequestThrottle.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingRequestThrottle.js @@ -49,8 +49,9 @@ function GridCustomToolbar({ throttleMs, setThrottleMs }) { } function ServerSideLazyLoadingRequestThrottle() { + const rowCount = 1000; const { fetchRows, ...props } = useMockServer( - { rowLength: 1000 }, + { rowLength: rowCount }, { useCursorPagination: false, minDelay: 200, maxDelay: 500 }, ); @@ -73,7 +74,6 @@ function ServerSideLazyLoadingRequestThrottle() { setRequestCount((prev) => prev + 1); return { rows: getRowsResponse.rows, - rowCount: getRowsResponse.rowCount, }; }, }), @@ -89,6 +89,7 @@ function ServerSideLazyLoadingRequestThrottle() { prev + 1); return { rows: getRowsResponse.rows, - rowCount: getRowsResponse.rowCount, }; }, }), @@ -108,6 +108,7 @@ function ServerSideLazyLoadingRequestThrottle() { Date: Wed, 27 Nov 2024 13:16:44 +0100 Subject: [PATCH 102/121] Update cache manager description Co-authored-by: Bilal Shafi Signed-off-by: Armin Mehinovic <4390250+arminmeh@users.noreply.github.com> --- .../x-data-grid-pro/src/hooks/features/dataSource/utils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts index 110f6efecf776..f2bf45455b818 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts @@ -119,9 +119,9 @@ export class NestedDataManager { } /** - * Provides better cache hit rate by splitting the data into smaller chunks - * Splits the data into smaller chunks to be stored in the cache - * Merges multiple cache entries into a single response + * Provides better cache hit rate by: + * 1. Splitting the data into smaller chunks to be stored in the cache (cache `set`) + * 2. Merging multiple cache entries into a single response to get the required chunk (cache `get`) */ export class CacheChunkManager { private chunkSize: number; From 9c3bf3d2988bf14490a3494bc38e116b3e32c6fa Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 27 Nov 2024 13:18:51 +0100 Subject: [PATCH 103/121] Remove empty space --- docs/data/pages.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/data/pages.ts b/docs/data/pages.ts index 2479daebb4edd..ffd848c5fa46b 100644 --- a/docs/data/pages.ts +++ b/docs/data/pages.ts @@ -129,7 +129,6 @@ const pages: MuiPage[] = [ { pathname: '/x/react-data-grid/export' }, { pathname: '/x/react-data-grid/clipboard', title: 'Copy and paste' }, { pathname: '/x/react-data-grid/scrolling' }, - { pathname: '/x/react-data-grid/list-view', title: 'List view', From 490aa90f83daef77b1320d0c431c351b8678a294 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 27 Nov 2024 13:31:13 +0100 Subject: [PATCH 104/121] Fix duplicate skeleton row key issue --- .../serverSideLazyLoader/useGridDataSourceLazyLoader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 200d62add3284..ff380083377cd 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 @@ -165,7 +165,7 @@ export const useGridDataSourceLazyLoader = ( // fill the grid with skeleton rows for (let i = 0; i < pageRowCount - rootChildrenCount; i += 1) { - const skeletonId = getSkeletonRowId(i); + const skeletonId = getSkeletonRowId(i + rootChildrenCount); // to avoid duplicate keys on rebuild rootGroupChildren.push(skeletonId); const skeletonRowNode: GridSkeletonRowNode = { From 107563b68502e728f286a49883d70c60b9875ad2 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 27 Nov 2024 13:35:21 +0100 Subject: [PATCH 105/121] Add unstable prefix to the lazyLoading flag --- .../ServerSideLazyLoadingErrorHandling.js | 2 +- .../ServerSideLazyLoadingErrorHandling.tsx | 2 +- .../ServerSideLazyLoadingInfinite.js | 2 +- .../ServerSideLazyLoadingInfinite.tsx | 2 +- .../ServerSideLazyLoadingModeUpdate.js | 2 +- .../ServerSideLazyLoadingModeUpdate.tsx | 2 +- .../ServerSideLazyLoadingRequestThrottle.js | 2 +- .../ServerSideLazyLoadingRequestThrottle.tsx | 2 +- .../ServerSideLazyLoadingViewport.js | 2 +- .../ServerSideLazyLoadingViewport.tsx | 2 +- .../data-grid/server-side-data/lazy-loading.md | 4 ++-- .../pages/x/api/data-grid/data-grid-premium.json | 2 +- docs/pages/x/api/data-grid/data-grid-pro.json | 2 +- .../data-grid-premium/data-grid-premium.json | 8 ++++---- .../data-grid/data-grid-pro/data-grid-pro.json | 8 ++++---- .../src/DataGridPremium/DataGridPremium.tsx | 16 ++++++++-------- .../src/DataGridPro/DataGridPro.tsx | 16 ++++++++-------- .../src/DataGridPro/useDataGridProProps.ts | 2 +- .../features/dataSource/useGridDataSource.ts | 6 +++--- .../useGridDataSourceLazyLoader.ts | 6 +++--- .../src/internals/propValidation.ts | 4 ++-- .../src/models/dataGridProProps.ts | 4 ++-- .../dataSourceLazyLoader.DataGridPro.test.tsx | 2 +- 23 files changed, 50 insertions(+), 50 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.js index 5aeda136a2c2e..4f890e86fe5ee 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.js @@ -97,7 +97,7 @@ function ServerSideLazyLoadingErrorHandling() { unstable_dataSource={dataSource} unstable_onDataSourceError={(_, params) => setRetryParams(params)} unstable_dataSourceCache={null} - lazyLoading + unstable_lazyLoading paginationModel={{ page: 0, pageSize: 10 }} slots={{ toolbar: GridToolbar }} /> diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.tsx index ca836369d7444..53d4519d1fa2b 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingErrorHandling.tsx @@ -101,7 +101,7 @@ function ServerSideLazyLoadingErrorHandling() { unstable_dataSource={dataSource} unstable_onDataSourceError={(_, params) => setRetryParams(params)} unstable_dataSourceCache={null} - lazyLoading + unstable_lazyLoading paginationModel={{ page: 0, pageSize: 10 }} slots={{ toolbar: GridToolbar }} /> diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js index c2a75162e751a..59ff744f4e438 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.js @@ -34,7 +34,7 @@ function ServerSideLazyLoadingInfinite() {
diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.tsx index 05b5486df6a89..b842453551b76 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingInfinite.tsx @@ -38,7 +38,7 @@ function ServerSideLazyLoadingInfinite() {
diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.js index dc1204f74f02c..ca6447d8e7e08 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingModeUpdate.js @@ -64,7 +64,7 @@ function ServerSideLazyLoadingModeUpdate() {
diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.tsx index 1d6c08815ec87..df60d3b0e3dc1 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingViewport.tsx @@ -39,7 +39,7 @@ function ServerSideLazyLoadingViewport() {
diff --git a/docs/data/data-grid/server-side-data/lazy-loading.md b/docs/data/data-grid/server-side-data/lazy-loading.md index fa7ad38e31f94..c092721d11460 100644 --- a/docs/data/data-grid/server-side-data/lazy-loading.md +++ b/docs/data/data-grid/server-side-data/lazy-loading.md @@ -8,7 +8,7 @@ title: React Data Grid - Server-side lazy loading Lazy Loading changes the way pagination works by removing page controls and loading data dynamically (in a single list) as the user scrolls through the grid. -It is enabled by adding `lazyLoading` prop in combination with `unstable_dataSource` prop. +It is enabled by adding `unstable_lazyLoading` prop in combination with `unstable_dataSource` prop. Initially, the first page data is fetched and displayed in the grid. What triggers the loading of next page data depends on the value of the total row count. @@ -92,7 +92,7 @@ This feature isn't implemented yet. It's coming. Don't hesitate to leave a comment on the same issue to influence what gets built. Especially if you already have a use case for this feature, or if you are facing a pain point with your current solution. ::: -When completed, it would be possible to use `lazyLoading` flag in combination with [Tree data](/x/react-data-grid/server-side-data/tree-data/) and [Row grouping](/x/react-data-grid/server-side-data/row-grouping/). +When completed, it would be possible to use `unstable_lazyLoading` flag in combination with [Tree data](/x/react-data-grid/server-side-data/tree-data/) and [Row grouping](/x/react-data-grid/server-side-data/row-grouping/). ## Error handling diff --git a/docs/pages/x/api/data-grid/data-grid-premium.json b/docs/pages/x/api/data-grid/data-grid-premium.json index 67b2adcf948b0..d6d1bc238de5e 100644 --- a/docs/pages/x/api/data-grid/data-grid-premium.json +++ b/docs/pages/x/api/data-grid/data-grid-premium.json @@ -211,7 +211,6 @@ }, "keepColumnPositionIfDraggedOutside": { "type": { "name": "bool" }, "default": "false" }, "keepNonExistentRowsSelected": { "type": { "name": "bool" }, "default": "false" }, - "lazyLoading": { "type": { "name": "bool" }, "default": "false" }, "lazyLoadingRequestThrottleMs": { "type": { "name": "number" }, "default": "500" }, "loading": { "type": { "name": "bool" }, "default": "false" }, "localeText": { "type": { "name": "object" } }, @@ -645,6 +644,7 @@ }, "throttleRowsMs": { "type": { "name": "number" }, "default": "0" }, "treeData": { "type": { "name": "bool" }, "default": "false" }, + "unstable_lazyLoading": { "type": { "name": "bool" }, "default": "false" }, "unstable_listColumn": { "type": { "name": "shape", diff --git a/docs/pages/x/api/data-grid/data-grid-pro.json b/docs/pages/x/api/data-grid/data-grid-pro.json index 363bb108eea28..652489b4e5326 100644 --- a/docs/pages/x/api/data-grid/data-grid-pro.json +++ b/docs/pages/x/api/data-grid/data-grid-pro.json @@ -188,7 +188,6 @@ }, "keepColumnPositionIfDraggedOutside": { "type": { "name": "bool" }, "default": "false" }, "keepNonExistentRowsSelected": { "type": { "name": "bool" }, "default": "false" }, - "lazyLoading": { "type": { "name": "bool" }, "default": "false" }, "lazyLoadingRequestThrottleMs": { "type": { "name": "number" }, "default": "500" }, "loading": { "type": { "name": "bool" }, "default": "false" }, "localeText": { "type": { "name": "object" } }, @@ -579,6 +578,7 @@ }, "throttleRowsMs": { "type": { "name": "number" }, "default": "0" }, "treeData": { "type": { "name": "bool" }, "default": "false" }, + "unstable_lazyLoading": { "type": { "name": "bool" }, "default": "false" }, "unstable_listColumn": { "type": { "name": "shape", diff --git a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json index 2e638fee6f663..587127b74c115 100644 --- a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json +++ b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json @@ -235,9 +235,6 @@ "keepNonExistentRowsSelected": { "description": "If true, the selection model will retain selected rows that do not exist. Useful when using server side pagination and row selections need to be retained when changing pages." }, - "lazyLoading": { - "description": "Used together with unstable_dataSource to enable lazy loading. If enabled, the grid stops adding paginationModel to the data requests (getRows) and starts sending start and end values depending on the loading mode and the scroll position." - }, "lazyLoadingRequestThrottleMs": { "description": "If positive, the Data Grid will throttle data source requests on rendered rows interval change." }, @@ -634,7 +631,7 @@ "description": "Override the height/width of the Data Grid inner scrollbar." }, "scrollEndThreshold": { - "description": "Set the area in px at the bottom of the grid viewport where onRowsScrollEnd is called. If combined with lazyLoading, it defines the area where the next data request is triggered." + "description": "Set the area in px at the bottom of the grid viewport where onRowsScrollEnd is called. If combined with unstable_lazyLoading, it defines the area where the next data request is triggered." }, "showCellVerticalBorder": { "description": "If true, vertical borders will be displayed between cells." @@ -664,6 +661,9 @@ "treeData": { "description": "If true, the rows will be gathered in a tree structure according to the getTreeDataPath prop." }, + "unstable_lazyLoading": { + "description": "Used together with unstable_dataSource to enable lazy loading. If enabled, the grid stops adding paginationModel to the data requests (getRows) and starts sending start and end values depending on the loading mode and the scroll position." + }, "unstable_listColumn": { "description": "Definition of the column rendered when the unstable_listView prop is enabled." }, diff --git a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json index c479e06313622..13553fa84abb8 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json +++ b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json @@ -216,9 +216,6 @@ "keepNonExistentRowsSelected": { "description": "If true, the selection model will retain selected rows that do not exist. Useful when using server side pagination and row selections need to be retained when changing pages." }, - "lazyLoading": { - "description": "Used together with unstable_dataSource to enable lazy loading. If enabled, the grid stops adding paginationModel to the data requests (getRows) and starts sending start and end values depending on the loading mode and the scroll position." - }, "lazyLoadingRequestThrottleMs": { "description": "If positive, the Data Grid will throttle data source requests on rendered rows interval change." }, @@ -576,7 +573,7 @@ "description": "Override the height/width of the Data Grid inner scrollbar." }, "scrollEndThreshold": { - "description": "Set the area in px at the bottom of the grid viewport where onRowsScrollEnd is called. If combined with lazyLoading, it defines the area where the next data request is triggered." + "description": "Set the area in px at the bottom of the grid viewport where onRowsScrollEnd is called. If combined with unstable_lazyLoading, it defines the area where the next data request is triggered." }, "showCellVerticalBorder": { "description": "If true, vertical borders will be displayed between cells." @@ -602,6 +599,9 @@ "treeData": { "description": "If true, the rows will be gathered in a tree structure according to the getTreeDataPath prop." }, + "unstable_lazyLoading": { + "description": "Used together with unstable_dataSource to enable lazy loading. If enabled, the grid stops adding paginationModel to the data requests (getRows) and starts sending start and end values depending on the loading mode and the scroll position." + }, "unstable_listColumn": { "description": "Definition of the column rendered when the unstable_listView prop is enabled." }, diff --git a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx index fa80c399a0f78..2de6db7013662 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx @@ -519,13 +519,6 @@ DataGridPremiumRaw.propTypes = { * @default false */ keepNonExistentRowsSelected: PropTypes.bool, - /** - * Used together with `unstable_dataSource` to enable lazy loading. - * If enabled, the grid stops adding `paginationModel` to the data requests (`getRows`) - * and starts sending `start` and `end` values depending on the loading mode and the scroll position. - * @default false - */ - lazyLoading: PropTypes.bool, /** * If positive, the Data Grid will throttle data source requests on rendered rows interval change. * @default 500 @@ -1030,7 +1023,7 @@ DataGridPremiumRaw.propTypes = { scrollbarSize: PropTypes.number, /** * Set the area in `px` at the bottom of the grid viewport where onRowsScrollEnd is called. - * If combined with `lazyLoading`, it defines the area where the next data request is triggered. + * If combined with `unstable_lazyLoading`, it defines the area where the next data request is triggered. * @default 80 */ scrollEndThreshold: PropTypes.number, @@ -1110,6 +1103,13 @@ DataGridPremiumRaw.propTypes = { get: PropTypes.func.isRequired, set: PropTypes.func.isRequired, }), + /** + * Used together with `unstable_dataSource` to enable lazy loading. + * If enabled, the grid stops adding `paginationModel` to the data requests (`getRows`) + * and starts sending `start` and `end` values depending on the loading mode and the scroll position. + * @default false + */ + unstable_lazyLoading: PropTypes.bool, /** * Definition of the column rendered when the `unstable_listView` prop is enabled. */ diff --git a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx index 98ab95462e8f1..f67b148522fe4 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx @@ -475,13 +475,6 @@ DataGridProRaw.propTypes = { * @default false */ keepNonExistentRowsSelected: PropTypes.bool, - /** - * Used together with `unstable_dataSource` to enable lazy loading. - * If enabled, the grid stops adding `paginationModel` to the data requests (`getRows`) - * and starts sending `start` and `end` values depending on the loading mode and the scroll position. - * @default false - */ - lazyLoading: PropTypes.bool, /** * If positive, the Data Grid will throttle data source requests on rendered rows interval change. * @default 500 @@ -937,7 +930,7 @@ DataGridProRaw.propTypes = { scrollbarSize: PropTypes.number, /** * Set the area in `px` at the bottom of the grid viewport where onRowsScrollEnd is called. - * If combined with `lazyLoading`, it defines the area where the next data request is triggered. + * If combined with `unstable_lazyLoading`, it defines the area where the next data request is triggered. * @default 80 */ scrollEndThreshold: PropTypes.number, @@ -1010,6 +1003,13 @@ DataGridProRaw.propTypes = { get: PropTypes.func.isRequired, set: PropTypes.func.isRequired, }), + /** + * Used together with `unstable_dataSource` to enable lazy loading. + * If enabled, the grid stops adding `paginationModel` to the data requests (`getRows`) + * and starts sending `start` and `end` values depending on the loading mode and the scroll position. + * @default false + */ + unstable_lazyLoading: PropTypes.bool, /** * Definition of the column rendered when the `unstable_listView` prop is enabled. */ diff --git a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts index 0d100ebdcec75..9bdc14d71b11b 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts +++ b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts @@ -56,7 +56,7 @@ export const DATA_GRID_PRO_PROPS_DEFAULT_VALUES: DataGridProPropsWithDefaultValu scrollEndThreshold: 80, treeData: false, unstable_listView: false, - lazyLoading: false, + unstable_lazyLoading: false, lazyLoadingRequestThrottleMs: 500, }; diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index b5ea8ad0201fe..781e3ec0e9c44 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -72,16 +72,16 @@ export const useGridDataSource = ( | 'paginationMode' | 'pageSizeOptions' | 'treeData' - | 'lazyLoading' + | 'unstable_lazyLoading' >, ) => { const setStrategyAvailability = React.useCallback(() => { apiRef.current.setStrategyAvailability( GridStrategyGroup.DataSource, DataSourceRowsUpdateStrategy.Default, - props.unstable_dataSource && !props.lazyLoading ? () => true : () => false, + props.unstable_dataSource && !props.unstable_lazyLoading ? () => true : () => false, ); - }, [apiRef, props.lazyLoading, props.unstable_dataSource]); + }, [apiRef, props.unstable_lazyLoading, props.unstable_dataSource]); const [defaultRowsUpdateStrategyActive, setDefaultRowsUpdateStrategyActive] = React.useState(false); 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 ff380083377cd..e4474de296e9c 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 @@ -53,7 +53,7 @@ export const useGridDataSourceLazyLoader = ( | 'pagination' | 'paginationMode' | 'unstable_dataSource' - | 'lazyLoading' + | 'unstable_lazyLoading' | 'lazyLoadingRequestThrottleMs' | 'scrollEndThreshold' >, @@ -62,9 +62,9 @@ export const useGridDataSourceLazyLoader = ( privateApiRef.current.setStrategyAvailability( GridStrategyGroup.DataSource, DataSourceRowsUpdateStrategy.LazyLoading, - props.unstable_dataSource && props.lazyLoading ? () => true : () => false, + props.unstable_dataSource && props.unstable_lazyLoading ? () => true : () => false, ); - }, [privateApiRef, props.lazyLoading, props.unstable_dataSource]); + }, [privateApiRef, props.unstable_lazyLoading, props.unstable_dataSource]); const [lazyLoadingRowsUpdateStrategyActive, setLazyLoadingRowsUpdateStrategyActive] = React.useState(false); diff --git a/packages/x-data-grid-pro/src/internals/propValidation.ts b/packages/x-data-grid-pro/src/internals/propValidation.ts index 06e607b2b3de1..06367108d22e9 100644 --- a/packages/x-data-grid-pro/src/internals/propValidation.ts +++ b/packages/x-data-grid-pro/src/internals/propValidation.ts @@ -34,7 +34,7 @@ export const propValidatorsDataGridPro: PropValidator (props) => (props.signature !== GridSignature.DataGrid && (props.rowsLoadingMode === 'server' || props.onRowsScrollEnd) && - props.lazyLoading && - 'MUI X: Usage of the client side lazy loading (`rowsLoadingMode="server"` or `onRowsScrollEnd=...`) cannot be used together with server side lazy loading `lazyLoading="true"`.') || + props.unstable_lazyLoading && + 'MUI X: Usage of the client side lazy loading (`rowsLoadingMode="server"` or `onRowsScrollEnd=...`) cannot be used together with server side lazy loading `unstable_lazyLoading="true"`.') || undefined, ]; diff --git a/packages/x-data-grid-pro/src/models/dataGridProProps.ts b/packages/x-data-grid-pro/src/models/dataGridProProps.ts index 3594873e34d53..d37eea12e2b47 100644 --- a/packages/x-data-grid-pro/src/models/dataGridProProps.ts +++ b/packages/x-data-grid-pro/src/models/dataGridProProps.ts @@ -77,7 +77,7 @@ export interface DataGridProPropsWithDefaultValue - Data source lazy loader', () => { const baselineProps = { unstable_dataSource: dataSource, columns: mockServer.columns, - lazyLoading: true, + unstable_lazyLoading: true, paginationModel: { page: 0, pageSize: 10 }, disableVirtualization: true, }; From 5a3bead6f006cc1e7bfef11e962469ba5f680578 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 27 Nov 2024 13:46:37 +0100 Subject: [PATCH 106/121] Code cleanup --- .../src/hooks/features/dataSource/useGridDataSource.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index 781e3ec0e9c44..cf73674bdc8aa 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -137,13 +137,10 @@ export const useGridDataSource = ( const cacheKeys = cacheChunkManager.getCacheKeys(fetchParams); const responses = cacheKeys.map((cacheKey) => cache.get(cacheKey)); - const cachedData = responses.some((response) => response === undefined) - ? undefined - : CacheChunkManager.mergeResponses(responses as GridGetRowsResponse[]); - if (cachedData !== undefined) { + if (responses.every((response) => response !== undefined)) { apiRef.current.applyStrategyProcessor('dataSourceRowsUpdate', { - response: cachedData, + response: CacheChunkManager.mergeResponses(responses as GridGetRowsResponse[]), fetchParams, }); return; From 3e53369ffb458779d29e44c7587613bbef07245e Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 27 Nov 2024 14:04:43 +0100 Subject: [PATCH 107/121] Move chunk size calculation in lazyRef --- .../hooks/features/dataSource/useGridDataSource.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index cf73674bdc8aa..cbad1b026201b 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -95,17 +95,14 @@ export const useGridDataSource = ( const onError = props.unstable_onDataSourceError; - const cacheChunkSize = React.useMemo(() => { + const cacheChunkManager = useLazyRef(() => { const sortedPageSizeOptions = props.pageSizeOptions .map((option) => (typeof option === 'number' ? option : option.value)) .sort((a, b) => a - b); + const cacheChunkSize = Math.min(paginationModel.pageSize, sortedPageSizeOptions[0]); - return Math.min(paginationModel.pageSize, sortedPageSizeOptions[0]); - }, [paginationModel.pageSize, props.pageSizeOptions]); - - const cacheChunkManager = useLazyRef( - () => new CacheChunkManager(cacheChunkSize), - ).current; + return new CacheChunkManager(cacheChunkSize); + }).current; const [cache, setCache] = React.useState(() => getCache(props.unstable_dataSourceCache), ); From cfcaf2c565c7b6b534d68b79e26434c1feacb784 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 27 Nov 2024 14:17:46 +0100 Subject: [PATCH 108/121] Fix condition --- .../x-data-grid-pro/src/hooks/features/dataSource/utils.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts index f2bf45455b818..ce2d8de133391 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts @@ -162,8 +162,9 @@ export class CacheChunkManager { ...response.pageInfo, // If the original response had page info, update that information for all but last chunk and keep the original value for the last chunk hasNextPage: - (response.pageInfo?.hasNextPage !== undefined && !isLastChunk) || - response.pageInfo?.hasNextPage, + response.pageInfo?.hasNextPage !== undefined && !isLastChunk + ? true + : response.pageInfo?.hasNextPage, nextCursor: response.pageInfo?.nextCursor !== undefined && !isLastChunk ? response.rows[chunkKey.end + 1].id From 653665e2985458acada5e0b530fc0f73416cd801 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 27 Nov 2024 14:55:16 +0100 Subject: [PATCH 109/121] Revert pagination label updates --- docs/data/data-grid/pagination/pagination.md | 4 +--- packages/x-data-grid/src/components/GridPagination.tsx | 3 +-- packages/x-data-grid/src/tests/pagination.DataGrid.test.tsx | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/data/data-grid/pagination/pagination.md b/docs/data/data-grid/pagination/pagination.md index 4e3dbaba85050..55d4414cdcb73 100644 --- a/docs/data/data-grid/pagination/pagination.md +++ b/docs/data/data-grid/pagination/pagination.md @@ -237,9 +237,7 @@ const labelDisplayedRows = ({ from, to, count, estimated }) => { if (!estimated) { return `${from}–${to} od ${count !== -1 ? count : `više nego ${to}`}`; } - const estimateLabel = - estimated && estimated > to ? `oko ${estimated}` : `više nego ${to}`; - return `${from}–${to} od ${count !== -1 ? count : estimateLabel}`; + return `${from}–${to} od ${count !== -1 ? count : `više nego ${estimated > to ? estimated : to}`}`; }; to ? `around ${estimated}` : `more than ${to}`; - return `${from}–${to} of ${count !== -1 ? count : estimateLabel}`; + return `${from}–${to} of ${count !== -1 ? count : `more than ${estimated > to ? estimated : to}`}`; }; // A mutable version of a readonly array. diff --git a/packages/x-data-grid/src/tests/pagination.DataGrid.test.tsx b/packages/x-data-grid/src/tests/pagination.DataGrid.test.tsx index 3bcd4f342042b..002a0d18ccad8 100644 --- a/packages/x-data-grid/src/tests/pagination.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/pagination.DataGrid.test.tsx @@ -613,7 +613,7 @@ describe(' - Pagination', () => { it('should support server side pagination with estimated row count', () => { const { setProps } = render(); expect(getColumnValues(0)).to.deep.equal(['0']); - expect(screen.getByText('1–1 of around 2')).not.to.equal(null); + expect(screen.getByText('1–1 of more than 2')).not.to.equal(null); fireEvent.click(screen.getByRole('button', { name: /next page/i })); expect(getColumnValues(0)).to.deep.equal(['1']); expect(screen.getByText('2–2 of more than 2')).not.to.equal(null); From 02e910f0f272c4cd351a11758dbb93e3f5fdbea1 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 27 Nov 2024 15:13:14 +0100 Subject: [PATCH 110/121] Update test to reflect skeleton row index change --- .../src/tests/dataSourceLazyLoader.DataGridPro.test.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx index 5180bdbd9c68c..9053706deeb6a 100644 --- a/packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx @@ -95,7 +95,7 @@ describe(' - Data source lazy loader', () => { await waitFor(() => expect(getRow(0)).not.to.be.undefined); // The 11th row should be a skeleton - expect(getRow(10).dataset.id).to.equal('auto-generated-skeleton-row-root-0'); + expect(getRow(10).dataset.id).to.equal('auto-generated-skeleton-row-root-10'); }); it('should make a new data source request once the skeleton rows are in the render context', async () => { @@ -274,7 +274,7 @@ describe(' - Data source lazy loader', () => { setProps({ rowCount: 100 }); // The 11th row should be a skeleton - expect(getRow(10).dataset.id).to.equal('auto-generated-skeleton-row-root-0'); + expect(getRow(10).dataset.id).to.equal('auto-generated-skeleton-row-root-10'); }); it('should reset the grid if the rowCount becomes unknown', async () => { @@ -285,7 +285,7 @@ describe(' - Data source lazy loader', () => { await waitFor(() => expect(getRow(0)).not.to.be.undefined); // The 11th row should not exist - expect(getRow(10).dataset.id).to.equal('auto-generated-skeleton-row-root-0'); + expect(getRow(10).dataset.id).to.equal('auto-generated-skeleton-row-root-10'); // make the rowCount unknown setProps({ rowCount: -1 }); @@ -331,7 +331,7 @@ describe(' - Data source lazy loader', () => { // wait until the rows are added await waitFor(() => expect(getRow(10)).not.to.be.undefined); // The 11th row should be a skeleton - expect(getRow(10).dataset.id).to.equal('auto-generated-skeleton-row-root-0'); + expect(getRow(10).dataset.id).to.equal('auto-generated-skeleton-row-root-10'); }); }); }); From c6e47f1f9d3bf5091f069bbb82749e125ff56192 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Wed, 27 Nov 2024 19:48:12 +0100 Subject: [PATCH 111/121] Fix rebase --- docs/data/data-grid/pagination/pagination.md | 4 +++- packages/x-data-grid/src/components/GridPagination.tsx | 3 ++- packages/x-data-grid/src/tests/pagination.DataGrid.test.tsx | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/data/data-grid/pagination/pagination.md b/docs/data/data-grid/pagination/pagination.md index 55d4414cdcb73..4e3dbaba85050 100644 --- a/docs/data/data-grid/pagination/pagination.md +++ b/docs/data/data-grid/pagination/pagination.md @@ -237,7 +237,9 @@ const labelDisplayedRows = ({ from, to, count, estimated }) => { if (!estimated) { return `${from}–${to} od ${count !== -1 ? count : `više nego ${to}`}`; } - return `${from}–${to} od ${count !== -1 ? count : `više nego ${estimated > to ? estimated : to}`}`; + const estimateLabel = + estimated && estimated > to ? `oko ${estimated}` : `više nego ${to}`; + return `${from}–${to} od ${count !== -1 ? count : estimateLabel}`; }; to ? estimated : to}`}`; + const estimateLabel = estimated && estimated > to ? `around ${estimated}` : `more than ${to}`; + return `${from}–${to} of ${count !== -1 ? count : estimateLabel}`; }; // A mutable version of a readonly array. diff --git a/packages/x-data-grid/src/tests/pagination.DataGrid.test.tsx b/packages/x-data-grid/src/tests/pagination.DataGrid.test.tsx index 002a0d18ccad8..3bcd4f342042b 100644 --- a/packages/x-data-grid/src/tests/pagination.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/pagination.DataGrid.test.tsx @@ -613,7 +613,7 @@ describe(' - Pagination', () => { it('should support server side pagination with estimated row count', () => { const { setProps } = render(); expect(getColumnValues(0)).to.deep.equal(['0']); - expect(screen.getByText('1–1 of more than 2')).not.to.equal(null); + expect(screen.getByText('1–1 of around 2')).not.to.equal(null); fireEvent.click(screen.getByRole('button', { name: /next page/i })); expect(getColumnValues(0)).to.deep.equal(['1']); expect(screen.getByText('2–2 of more than 2')).not.to.equal(null); From 1309c7c79a9bb6a59c157a17c459d0ac0d072313 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 28 Nov 2024 07:29:21 +0100 Subject: [PATCH 112/121] make lazyLoadingRequestThrottleMs unstable --- .../ServerSideLazyLoadingRequestThrottle.js | 2 +- .../ServerSideLazyLoadingRequestThrottle.tsx | 2 +- docs/data/data-grid/server-side-data/lazy-loading.md | 2 +- docs/pages/x/api/data-grid/data-grid-premium.json | 2 +- docs/pages/x/api/data-grid/data-grid-pro.json | 2 +- .../data-grid/data-grid-premium/data-grid-premium.json | 6 +++--- .../data-grid/data-grid-pro/data-grid-pro.json | 6 +++--- .../src/DataGridPremium/DataGridPremium.tsx | 10 +++++----- .../x-data-grid-pro/src/DataGridPro/DataGridPro.tsx | 10 +++++----- .../src/DataGridPro/useDataGridProProps.ts | 2 +- .../useGridDataSourceLazyLoader.ts | 6 +++--- .../x-data-grid-pro/src/models/dataGridProProps.ts | 2 +- 12 files changed, 26 insertions(+), 26 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingRequestThrottle.js b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingRequestThrottle.js index 22866384f6f90..2a826d5dc52ab 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingRequestThrottle.js +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingRequestThrottle.js @@ -91,7 +91,7 @@ function ServerSideLazyLoadingRequestThrottle() { unstable_dataSource={dataSource} rowCount={rowCount} unstable_lazyLoading - lazyLoadingRequestThrottleMs={throttleMs} + unstable_lazyLoadingRequestThrottleMs={throttleMs} paginationModel={{ page: 0, pageSize: 10 }} slots={{ footerRowCount: GridCustomFooterRowCount, diff --git a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingRequestThrottle.tsx b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingRequestThrottle.tsx index c980240d59bb8..75056075affc9 100644 --- a/docs/data/data-grid/server-side-data/ServerSideLazyLoadingRequestThrottle.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideLazyLoadingRequestThrottle.tsx @@ -110,7 +110,7 @@ function ServerSideLazyLoadingRequestThrottle() { unstable_dataSource={dataSource} rowCount={rowCount} unstable_lazyLoading - lazyLoadingRequestThrottleMs={throttleMs} + unstable_lazyLoadingRequestThrottleMs={throttleMs} paginationModel={{ page: 0, pageSize: 10 }} slots={{ footerRowCount: GridCustomFooterRowCount, diff --git a/docs/data/data-grid/server-side-data/lazy-loading.md b/docs/data/data-grid/server-side-data/lazy-loading.md index c092721d11460..af222679b41ea 100644 --- a/docs/data/data-grid/server-side-data/lazy-loading.md +++ b/docs/data/data-grid/server-side-data/lazy-loading.md @@ -48,7 +48,7 @@ Open info section of the browser console to see the requests being made and the While user is scrolling through the grid, rendering context changes and the Data Grid tries to fill in any missing rows by making a new server request. The Data Grid throttles new data fetches on the rendering context change to avoid doing unnecessary requests. -The default throttle time is 500 milliseconds, use `lazyLoadingRequestThrottleMs` prop to customize it, as the following example demonstrates. +The default throttle time is 500 milliseconds, use `unstable_lazyLoadingRequestThrottleMs` prop to customize it, as the following example demonstrates. {{"demo": "ServerSideLazyLoadingRequestThrottle.js", "bg": "inline"}} diff --git a/docs/pages/x/api/data-grid/data-grid-premium.json b/docs/pages/x/api/data-grid/data-grid-premium.json index d6d1bc238de5e..c19a664541cd4 100644 --- a/docs/pages/x/api/data-grid/data-grid-premium.json +++ b/docs/pages/x/api/data-grid/data-grid-premium.json @@ -211,7 +211,6 @@ }, "keepColumnPositionIfDraggedOutside": { "type": { "name": "bool" }, "default": "false" }, "keepNonExistentRowsSelected": { "type": { "name": "bool" }, "default": "false" }, - "lazyLoadingRequestThrottleMs": { "type": { "name": "number" }, "default": "500" }, "loading": { "type": { "name": "bool" }, "default": "false" }, "localeText": { "type": { "name": "object" } }, "logger": { @@ -645,6 +644,7 @@ "throttleRowsMs": { "type": { "name": "number" }, "default": "0" }, "treeData": { "type": { "name": "bool" }, "default": "false" }, "unstable_lazyLoading": { "type": { "name": "bool" }, "default": "false" }, + "unstable_lazyLoadingRequestThrottleMs": { "type": { "name": "number" }, "default": "500" }, "unstable_listColumn": { "type": { "name": "shape", diff --git a/docs/pages/x/api/data-grid/data-grid-pro.json b/docs/pages/x/api/data-grid/data-grid-pro.json index 652489b4e5326..677e8939b0957 100644 --- a/docs/pages/x/api/data-grid/data-grid-pro.json +++ b/docs/pages/x/api/data-grid/data-grid-pro.json @@ -188,7 +188,6 @@ }, "keepColumnPositionIfDraggedOutside": { "type": { "name": "bool" }, "default": "false" }, "keepNonExistentRowsSelected": { "type": { "name": "bool" }, "default": "false" }, - "lazyLoadingRequestThrottleMs": { "type": { "name": "number" }, "default": "500" }, "loading": { "type": { "name": "bool" }, "default": "false" }, "localeText": { "type": { "name": "object" } }, "logger": { @@ -579,6 +578,7 @@ "throttleRowsMs": { "type": { "name": "number" }, "default": "0" }, "treeData": { "type": { "name": "bool" }, "default": "false" }, "unstable_lazyLoading": { "type": { "name": "bool" }, "default": "false" }, + "unstable_lazyLoadingRequestThrottleMs": { "type": { "name": "number" }, "default": "500" }, "unstable_listColumn": { "type": { "name": "shape", diff --git a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json index 587127b74c115..65776cfbe2bb4 100644 --- a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json +++ b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json @@ -235,9 +235,6 @@ "keepNonExistentRowsSelected": { "description": "If true, the selection model will retain selected rows that do not exist. Useful when using server side pagination and row selections need to be retained when changing pages." }, - "lazyLoadingRequestThrottleMs": { - "description": "If positive, the Data Grid will throttle data source requests on rendered rows interval change." - }, "loading": { "description": "If true, a loading overlay is displayed." }, "localeText": { "description": "Set the locale text of the Data Grid. You can find all the translation keys supported in the source in the GitHub repository." @@ -664,6 +661,9 @@ "unstable_lazyLoading": { "description": "Used together with unstable_dataSource to enable lazy loading. If enabled, the grid stops adding paginationModel to the data requests (getRows) and starts sending start and end values depending on the loading mode and the scroll position." }, + "unstable_lazyLoadingRequestThrottleMs": { + "description": "If positive, the Data Grid will throttle data source requests on rendered rows interval change." + }, "unstable_listColumn": { "description": "Definition of the column rendered when the unstable_listView prop is enabled." }, diff --git a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json index 13553fa84abb8..ee17a6dd230ce 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json +++ b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json @@ -216,9 +216,6 @@ "keepNonExistentRowsSelected": { "description": "If true, the selection model will retain selected rows that do not exist. Useful when using server side pagination and row selections need to be retained when changing pages." }, - "lazyLoadingRequestThrottleMs": { - "description": "If positive, the Data Grid will throttle data source requests on rendered rows interval change." - }, "loading": { "description": "If true, a loading overlay is displayed." }, "localeText": { "description": "Set the locale text of the Data Grid. You can find all the translation keys supported in the source in the GitHub repository." @@ -602,6 +599,9 @@ "unstable_lazyLoading": { "description": "Used together with unstable_dataSource to enable lazy loading. If enabled, the grid stops adding paginationModel to the data requests (getRows) and starts sending start and end values depending on the loading mode and the scroll position." }, + "unstable_lazyLoadingRequestThrottleMs": { + "description": "If positive, the Data Grid will throttle data source requests on rendered rows interval change." + }, "unstable_listColumn": { "description": "Definition of the column rendered when the unstable_listView prop is enabled." }, diff --git a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx index 2de6db7013662..45b734be6760f 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx @@ -519,11 +519,6 @@ DataGridPremiumRaw.propTypes = { * @default false */ keepNonExistentRowsSelected: PropTypes.bool, - /** - * If positive, the Data Grid will throttle data source requests on rendered rows interval change. - * @default 500 - */ - lazyLoadingRequestThrottleMs: PropTypes.number, /** * If `true`, a loading overlay is displayed. * @default false @@ -1110,6 +1105,11 @@ DataGridPremiumRaw.propTypes = { * @default false */ unstable_lazyLoading: PropTypes.bool, + /** + * If positive, the Data Grid will throttle data source requests on rendered rows interval change. + * @default 500 + */ + unstable_lazyLoadingRequestThrottleMs: PropTypes.number, /** * Definition of the column rendered when the `unstable_listView` prop is enabled. */ diff --git a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx index f67b148522fe4..4a57c8bd84834 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx @@ -475,11 +475,6 @@ DataGridProRaw.propTypes = { * @default false */ keepNonExistentRowsSelected: PropTypes.bool, - /** - * If positive, the Data Grid will throttle data source requests on rendered rows interval change. - * @default 500 - */ - lazyLoadingRequestThrottleMs: PropTypes.number, /** * If `true`, a loading overlay is displayed. * @default false @@ -1010,6 +1005,11 @@ DataGridProRaw.propTypes = { * @default false */ unstable_lazyLoading: PropTypes.bool, + /** + * If positive, the Data Grid will throttle data source requests on rendered rows interval change. + * @default 500 + */ + unstable_lazyLoadingRequestThrottleMs: PropTypes.number, /** * Definition of the column rendered when the `unstable_listView` prop is enabled. */ diff --git a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts index 9bdc14d71b11b..fffa481874e6e 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts +++ b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts @@ -57,7 +57,7 @@ export const DATA_GRID_PRO_PROPS_DEFAULT_VALUES: DataGridProPropsWithDefaultValu treeData: false, unstable_listView: false, unstable_lazyLoading: false, - lazyLoadingRequestThrottleMs: 500, + unstable_lazyLoadingRequestThrottleMs: 500, }; const defaultSlots = DATA_GRID_PRO_DEFAULT_SLOTS_COMPONENTS; 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 e4474de296e9c..720ee7c5fe73b 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 @@ -54,7 +54,7 @@ export const useGridDataSourceLazyLoader = ( | 'paginationMode' | 'unstable_dataSource' | 'unstable_lazyLoading' - | 'lazyLoadingRequestThrottleMs' + | 'unstable_lazyLoadingRequestThrottleMs' | 'scrollEndThreshold' >, ): void => { @@ -397,8 +397,8 @@ export const useGridDataSourceLazyLoader = ( ); const throttledHandleRenderedRowsIntervalChange = React.useMemo( - () => throttle(handleRenderedRowsIntervalChange, props.lazyLoadingRequestThrottleMs), - [props.lazyLoadingRequestThrottleMs, handleRenderedRowsIntervalChange], + () => throttle(handleRenderedRowsIntervalChange, props.unstable_lazyLoadingRequestThrottleMs), + [props.unstable_lazyLoadingRequestThrottleMs, handleRenderedRowsIntervalChange], ); const handleGridSortModelChange = React.useCallback>( diff --git a/packages/x-data-grid-pro/src/models/dataGridProProps.ts b/packages/x-data-grid-pro/src/models/dataGridProProps.ts index d37eea12e2b47..af091b4e93408 100644 --- a/packages/x-data-grid-pro/src/models/dataGridProProps.ts +++ b/packages/x-data-grid-pro/src/models/dataGridProProps.ts @@ -155,7 +155,7 @@ export interface DataGridProPropsWithDefaultValue Date: Thu, 28 Nov 2024 11:48:52 +0100 Subject: [PATCH 113/121] Performance improvements --- .../useGridDataSourceLazyLoader.ts | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) 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 720ee7c5fe73b..a3d5f5af60c92 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 @@ -193,7 +193,7 @@ export const useGridDataSourceLazyLoader = ( }, [privateApiRef]); const rebuildSkeletonRows = React.useCallback(() => { - // replace all data rows with skeleton rows. After that, fill the grid with the new skeleton rows + // replace all data rows with skeleton rows. const tree = privateApiRef.current.state.rows.tree; const rootGroup = tree[GRID_ROOT_GROUP_ID] as GridGroupNode; const rootGroupChildren = [...rootGroup.children]; @@ -258,11 +258,18 @@ export const useGridDataSourceLazyLoader = ( privateApiRef.current.setRowCount(response.rowCount === undefined ? -1 : response.rowCount); } - if (rowsStale.current) { - rowsStale.current = false; + // scroll to the top if the rows are stale and the new request is for the first page + if (rowsStale.current && params.fetchParams.start === 0) { privateApiRef.current.scroll({ top: 0 }); + // the rows can safely be replaced. skeleton rows will be added later privateApiRef.current.setRows(response.rows); } else { + // having stale rows while not having a request for the first page means that the scroll position should be maintained + // convert all existing data to skeleton rows to avoid duplicate keys + if (rowsStale.current) { + rebuildSkeletonRows(); + } + const startingIndex = typeof fetchParams.start === 'string' ? Math.max(filteredSortedRowIds.indexOf(fetchParams.start), 0) @@ -271,6 +278,8 @@ export const useGridDataSourceLazyLoader = ( privateApiRef.current.unstable_replaceRows(startingIndex, response.rows); } + rowsStale.current = false; + if (loadingTrigger.current === null) { updateLoadingTrigger(privateApiRef.current.state.pagination.rowCount); } @@ -279,7 +288,13 @@ export const useGridDataSourceLazyLoader = ( privateApiRef.current.setLoading(false); privateApiRef.current.requestPipeProcessorsApplication('hydrateRows'); }, - [privateApiRef, filteredSortedRowIds, updateLoadingTrigger, addSkeletonRows], + [ + privateApiRef, + filteredSortedRowIds, + updateLoadingTrigger, + rebuildSkeletonRows, + addSkeletonRows, + ], ); const handleRowCountChange = React.useCallback(() => { @@ -403,6 +418,7 @@ export const useGridDataSourceLazyLoader = ( const handleGridSortModelChange = React.useCallback>( (newSortModel) => { + rowsStale.current = true; previousLastRowIndex.current = 0; const rangeParams = loadingTrigger.current === LoadingTrigger.VIEWPORT @@ -422,26 +438,12 @@ export const useGridDataSourceLazyLoader = ( }; privateApiRef.current.setLoading(true); - if (loadingTrigger.current === LoadingTrigger.VIEWPORT) { - // replace all rows with skeletons to maintain the same scroll position - rebuildSkeletonRows(); - } else { - rowsStale.current = true; - } - privateApiRef.current.unstable_dataSource.fetchRows( GRID_ROOT_GROUP_ID, adjustRowParams(getRowsParams), ); }, - [ - privateApiRef, - filterModel, - paginationModel.pageSize, - renderContext, - rebuildSkeletonRows, - adjustRowParams, - ], + [privateApiRef, filterModel, paginationModel.pageSize, renderContext, adjustRowParams], ); const handleGridFilterModelChange = React.useCallback>( From 19302adfa77f11cac9f3fca30d10d8504ff978a2 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Thu, 28 Nov 2024 14:56:33 +0100 Subject: [PATCH 114/121] Cleanup effects --- .../hooks/features/dataSource/useGridDataSource.ts | 11 +---------- .../useGridDataSourceLazyLoader.ts | 12 +----------- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index cbad1b026201b..243af1a9e34ce 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -7,7 +7,6 @@ import { useGridSelector, gridPaginationModelSelector, GRID_ROOT_GROUP_ID, - useFirstRender, GridEventListener, } from '@mui/x-data-grid'; import { @@ -410,11 +409,7 @@ export const useGridDataSource = ( }, [props.unstable_dataSourceCache]); React.useEffect(() => { - if (!isFirstRender.current) { - setStrategyAvailability(); - } else { - isFirstRender.current = false; - } + setStrategyAvailability(); }, [setStrategyAvailability]); React.useEffect(() => { @@ -435,8 +430,4 @@ export const useGridDataSource = ( scheduledGroups.current = groupsToAutoFetch.length; } }, [apiRef, nestedDataManager, groupsToAutoFetch]); - - useFirstRender(() => { - setStrategyAvailability(); - }); }; 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 a3d5f5af60c92..2b940e816308c 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 @@ -12,7 +12,6 @@ import { gridPaginationModelSelector, gridDimensionsSelector, gridFilteredSortedRowIdsSelector, - useFirstRender, } from '@mui/x-data-grid'; import { getVisibleRows, @@ -507,16 +506,7 @@ export const useGridDataSourceLazyLoader = ( runIf(lazyLoadingRowsUpdateStrategyActive, handleGridFilterModelChange), ); - useFirstRender(() => { - setStrategyAvailability(); - }); - - const isFirstRender = React.useRef(true); React.useEffect(() => { - if (!isFirstRender.current) { - setStrategyAvailability(); - } else { - isFirstRender.current = false; - } + setStrategyAvailability(); }, [setStrategyAvailability]); }; From 6425bf232c24b70e9d84d03da830b98ffe247faa Mon Sep 17 00:00:00 2001 From: Armin Mehinovic <4390250+arminmeh@users.noreply.github.com> Date: Fri, 29 Nov 2024 09:18:28 +0100 Subject: [PATCH 115/121] Update documentation Co-authored-by: Sycamore <71297412+samuelsycamore@users.noreply.github.com> Signed-off-by: Armin Mehinovic <4390250+arminmeh@users.noreply.github.com> --- docs/data/data-grid/server-side-data/index.md | 30 ++++---- .../server-side-data/lazy-loading.md | 73 +++++++++++-------- 2 files changed, 56 insertions(+), 47 deletions(-) diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index c4cd7b533e5d7..39ddb322d8a1f 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -191,38 +191,38 @@ The `GridDataSourceCacheDefault` is used by default which is a simple in-memory ### Improving the cache hit rate -To increase the cache hit rate, Data Grid splits `getRows` results into chunks before storing them in cache. For the next request one or more chunks are combined to recreate the response. +To increase the cache hit rate, Data Grid splits `getRows()` results into chunks before storing them in cache. +For the requests that follow, chunks are combined as needed to recreate the response. This means that a single request can make multiple calls to the `get` or `set` method of `GridDataSourceCache`. -Chunk size is the lowest expected amount of records per request based on `pageSize` from `paginationModel` and `pageSizeOptions` prop. +Chunk size is the lowest expected amount of records per request based on the `pageSize` value from the `paginationModel` and `pageSizeOptions` props. Because of this, values in the `pageSizeOptions` prop play a big role in the cache hit rate. -It is recommended to have values that are multiples of the lowest value. -Even better if every value is a multiple of the previous value. +We recommend using values that are multiples of the lowest value; even better if each subsequent value is a multiple of the previous value. -Here are some examples. +Here are some examples: 1. Best scenario - `pageSizeOptions={[5, 10, 50, 100]}` - In this case the chunk size is 5, which means that for `pageSize={100}` 20 cache records are stored. + In this case the chunk size is 5, which means that with `pageSize={100}` there are 20 cache records stored. Retrieving data for any other `pageSize` up to the first 100 records results in a cache hit, since the whole dataset can be made of the existing chunks. 2. Parts of the data missing - `pageSizeOptions={[10, 20, 50]}` - Loading the first page with the `pageSize={50}` results in 5 cache records. - This works well with the `pageSize={10}`, but not all the time with `pageSize={20}`. + Loading the first page with `pageSize={50}` results in 5 cache records. + This works well with `pageSize={10}`, but not as well with `pageSize={20}`. + Loading the third page with `pageSize={20}` results in a new request being made, even though half of the data is already in the cache. - Loading the third page with `pageSize={20}` results in a new request being made, even though half of the data is in the cache. +3. Incompatible page sizes - `pageSizeOptions={[7, 15, 40]}` -3. Not compatible page sizes - `pageSizeOptions={[7, 15, 40]}` - - In this situation, the chunk size is 7. Retrieving the first page with `pageSize={15}` creates chunks with `[7, 7, 1]` record(s). - Loading the second page creates another 3 chunks (again `[7, 7, 1]` record(s)), but now the third chunk from the first request has an overlap of 1 record with the first chunk of the second request. - These chunks with 1 record can only be used as a last piece of a request for `pageSize={15}` and are useless in any other case. + In this situation, the chunk size is 7. + Retrieving the first page with `pageSize={15}` creates chunks split into `[7, 7, 1]` records. + Loading the second page creates 3 new chunks (again `[7, 7, 1]`), but now the third chunk from the first request has an overlap of 1 record with the first chunk of the second request. + These chunks with 1 record can only be used as the last piece of a request for `pageSize={15}` and are useless in all other cases. :::info -Keep in mind that in the examples above `sortModel` and `filterModel` remained unchanged. +In the examples above, `sortModel` and `filterModel` remained unchanged. Changing those would require a new response to be retrieved and stored in the chunks. ::: diff --git a/docs/data/data-grid/server-side-data/lazy-loading.md b/docs/data/data-grid/server-side-data/lazy-loading.md index af222679b41ea..154e1d2228ce5 100644 --- a/docs/data/data-grid/server-side-data/lazy-loading.md +++ b/docs/data/data-grid/server-side-data/lazy-loading.md @@ -4,61 +4,69 @@ title: React Data Grid - Server-side lazy loading # Data Grid - Server-side lazy loading [](/x/introduction/licensing/#pro-plan 'Pro plan') -

Row lazy-loading with server-side data source.

+

Learn how to implement lazy-loading rows with a server-side data source.

-Lazy Loading changes the way pagination works by removing page controls and loading data dynamically (in a single list) as the user scrolls through the grid. +Lazy loading changes the way pagination works by removing page controls and loading data dynamically (in a single list) as the user scrolls through the grid. -It is enabled by adding `unstable_lazyLoading` prop in combination with `unstable_dataSource` prop. +You can enable it with the `unstable_lazyLoading` prop paired with the `unstable_dataSource` prop. -Initially, the first page data is fetched and displayed in the grid. What triggers the loading of next page data depends on the value of the total row count. +Initially, data for the first page is fetched and displayed in the grid. +The value of the total row count determines when the next page's data is loaded: -If the total row count is known, the Data Grid gets filled with skeleton rows and fetches more data if one of the skeleton rows falls into the rendering context. -This loading strategy is often referred to as [**viewport loading**](#viewport-loading). +- If the total row count is known, the Data Grid is filled with skeleton rows and fetches more data if one of the skeleton rows falls into the rendering context. + This loading strategy is often referred to as [**viewport loading**](#viewport-loading). -If the total row count is unknown, the Data Grid fetches more data when the user scrolls to the bottom. This loading strategy is often referred to as [**infinite loading**](#infinite-loading). +- If the total row count is unknown, the Data Grid fetches more data when the user scrolls to the bottom. + This loading strategy is often referred to as [**infinite loading**](#infinite-loading). :::info -Row count can be provided in either of the following ways. +You can provide the row count through one of the following ways: -- Pass as [`rowCount`](/x/api/data-grid/data-grid/#data-grid-prop-rowCount) prop +- Pass it as the [`rowCount`](/x/api/data-grid/data-grid/#data-grid-prop-rowCount) prop - Return `rowCount` in the `getRows` method of the [data source](/x/react-data-grid/server-side-data/#data-source) -- Set the `rowCount` using the [`setRowCount`](/x/api/data-grid/grid-api/#grid-api-prop-setRowCount) API method. +- Set the `rowCount` using the [`setRowCount`](/x/api/data-grid/grid-api/#grid-api-prop-setRowCount) API method -The above list is given in the order of precedence, which means if the row count is set using the API, that value gets overridden once a new value is returned by the `getRows` method, unless if it is `undefined`. +These options are presented in order of precedence, which means if the row count is set using the API, that value is overridden once a new value is returned by the `getRows` method unless it's `undefined`. ::: ## Viewport loading -The viewport loading mode is enabled when the row count is known (`rowCount >= 0`). Grid fetches the first page immediately and adds skeleton rows to match the total row count. Other pages are fetched once the user starts scrolling and moves a skeleton row inside the rendering context (index range defined by [Virtualization](/x/react-data-grid/virtualization/)). +Viewport loading mode is enabled when the row count is known (and is greater than or equal to zero). +The Grid fetches the first page immediately and adds skeleton rows to match the total row count. +Other pages are fetched once the user starts scrolling and moves a skeleton row inside the rendering context (with the index range defined by [virtualization](/x/react-data-grid/virtualization/)). -If the user scrolls too fast, the grid loads multiple pages with one request (by adjusting `start` and `end` param) in order to reduce the server load. +If the user scrolls too fast, the Grid loads multiple pages with one request (by adjusting `start` and `end` parameters) to reduce the server load. -The demo below shows the viewport loading mode. +The demo below shows how viewport loading mode works: {{"demo": "ServerSideLazyLoadingViewport.js", "bg": "inline"}} :::info -The data source demos use a utility function `useMockServer` to simulate the server-side data fetching. -In a real-world scenario, you should replace this with your own server-side data-fetching logic. +The data source demos use a `useMockServer` utility function to simulate server-side data fetching. +In a real-world scenario you would replace this with your own server-side data-fetching logic. -Open info section of the browser console to see the requests being made and the data being fetched in response. +Open the Info section of your browser console to see the requests being made and the data being fetched in response. ::: ### Request throttling -While user is scrolling through the grid, rendering context changes and the Data Grid tries to fill in any missing rows by making a new server request. -The Data Grid throttles new data fetches on the rendering context change to avoid doing unnecessary requests. -The default throttle time is 500 milliseconds, use `unstable_lazyLoadingRequestThrottleMs` prop to customize it, as the following example demonstrates. +As a user scrolls through the Grid, the rendering context changes and the Grid tries to fill in any missing rows by making a new server request. +It also throttles new data fetches to avoid making unnecessary requests. +The default throttle time is 500 milliseconds. +Use the `unstable_lazyLoadingRequestThrottleMs` prop to set a custom time, as shown below: {{"demo": "ServerSideLazyLoadingRequestThrottle.js", "bg": "inline"}} ## Infinite loading -The infinite loading mode is enabled when the row count is unknown (`-1` or `undefined`). New page is loaded when the scroll reaches the bottom of the viewport area. +Infinite loading mode is enabled when the row count is unknown (either `-1` or `undefined`). +A new page is loaded when the scroll reaches the bottom of the viewport area. -The area which triggers the new request can be changed using `scrollEndThreshold`. +You can use the `scrollEndThreshold` prop to change the area that triggers new requests. -The demo below shows the infinite loading mode. Page size is set to `15` and the mock server is configured to return a total of `100` rows. Once the response does not contain any new rows, the grid stops requesting new data. +The demo below shows how infinite loading mode works. +Page size is set to `15` and the mock server is configured to return a total of 100 rows. +When the response contains no new rows, the Grid stops requesting new data. {{"demo": "ServerSideLazyLoadingInfinite.js", "bg": "inline"}} @@ -68,17 +76,18 @@ The grid changes the loading mode dynamically if the total row count gets update Based on the previous and the new value for the total row count, the following scenarios are possible: -- **Unknown `rowCount` to known `rowCount`**: When the row count is set to a valid value from an unknown value, the Data Grid switches to the viewport loading mode. It checks the number of already fetched rows and adds skeleton rows to match the provided row count. +- **Unknown `rowCount` to known `rowCount`**: When the row count is set to a valid value from an unknown value, the Data Grid switches to viewport loading mode. It checks the number of already fetched rows and adds skeleton rows to match the provided row count. -- **Known `rowCount` to unknown `rowCount`**: If the row count is updated and set to `-1`, the Data Grid resets, fetches the first page, and sets itself in the infinite loading mode. +- **Known `rowCount` to unknown `rowCount`**: If the row count is updated and set to `-1`, the Data Grid resets, fetches the first page, then sets itself to infinite loading mode. -- **Known `rowCount` greater than the actual row count**: This can happen either by reducing the value of the row count after more rows were already fetched or if the row count was unknown and the grid in the inifite loading mode already fetched more rows. In this case, the grid resets, fetches the first page and continues in one of the modes depending on the new value of the `rowCount`. +- **Known `rowCount` greater than the actual row count**: This can happen either by reducing the value of the row count after more rows were already fetched, or if the row count was unknown and the Grid (while in the infinite loading mode) already fetched more rows. In this case, the Grid resets, fetches the first page, and then continues in one mode or the other depending on the new value of the `rowCount`. :::warning -`rowCount` is expected to be static. Changing its value can cause the grid to reset and the cache to be cleared which may lead to performance and UX degradation. +`rowCount` is expected to be static. +Changing its value can cause the Grid to reset and the cache to be cleared which may lead to performance and UX degradation. ::: -The demo below serves more as a showcase of the behavior described above and is not representing something you would implement in a real-world scenario. +The demo below serves as a showcase of the behavior described above, and is not representative of something you would implement in a real-world scenario. {{"demo": "ServerSideLazyLoadingModeUpdate.js", "bg": "inline"}} @@ -89,16 +98,16 @@ This feature isn't implemented yet. It's coming. 👍 Upvote [issue #14527](https://github.com/mui/mui-x/issues/14527) if you want to see it land faster. -Don't hesitate to leave a comment on the same issue to influence what gets built. Especially if you already have a use case for this feature, or if you are facing a pain point with your current solution. +Don't hesitate to leave a comment on the issue to help influence what gets built—especially if you already have a use case for this feature, or if you're facing a specific pain point with your current solution. ::: -When completed, it would be possible to use `unstable_lazyLoading` flag in combination with [Tree data](/x/react-data-grid/server-side-data/tree-data/) and [Row grouping](/x/react-data-grid/server-side-data/row-grouping/). +When completed, it will be possible to use the `unstable_lazyLoading` flag in combination with [tree data](/x/react-data-grid/server-side-data/tree-data/) and [row grouping](/x/react-data-grid/server-side-data/row-grouping/). ## Error handling -To handle errors, use `unstable_onDataSourceError` prop as described in the [Error handling](/x/react-data-grid/server-side-data/#error-handling) section of the data source overview page. +To handle errors, use the `unstable_onDataSourceError` prop as described in [Server-side data—Error handling](/x/react-data-grid/server-side-data/#error-handling). -Second parameter of type `GridGetRowsParams` can be passed to `getRows` method of the [`unstable_dataSource`](/x/api/data-grid/grid-api/#grid-api-prop-unstable_dataSource) to retry the request. +You can pass the second parameter of type `GridGetRowsParams` to the `getRows` method of the [`unstable_dataSource`](/x/api/data-grid/grid-api/#grid-api-prop-unstable_dataSource) to retry the request. If successful, the Data Grid uses `rows` and `rowCount` data to determine if the rows should be appended at the end of the grid or if the skeleton rows should be replaced. The following demo gives an example how to use `GridGetRowsParams` to retry a failed request. From e233143fdd322707753d290d8d9fdd6ecdd963ea Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Fri, 29 Nov 2024 09:57:27 +0100 Subject: [PATCH 116/121] Additional fixes to the overview page --- docs/data/data-grid/server-side-data/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index 39ddb322d8a1f..03a207437e0e2 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -191,9 +191,9 @@ The `GridDataSourceCacheDefault` is used by default which is a simple in-memory ### Improving the cache hit rate -To increase the cache hit rate, Data Grid splits `getRows()` results into chunks before storing them in cache. +To increase the cache hit rate, Data Grid splits `getRows()` results into chunks before storing them in cache. For the requests that follow, chunks are combined as needed to recreate the response. -This means that a single request can make multiple calls to the `get` or `set` method of `GridDataSourceCache`. +This means that a single request can make multiple calls to the `get()` or `set()` method of `GridDataSourceCache`. Chunk size is the lowest expected amount of records per request based on the `pageSize` value from the `paginationModel` and `pageSizeOptions` props. @@ -216,7 +216,7 @@ Here are some examples: 3. Incompatible page sizes - `pageSizeOptions={[7, 15, 40]}` - In this situation, the chunk size is 7. + In this situation, the chunk size is 7. Retrieving the first page with `pageSize={15}` creates chunks split into `[7, 7, 1]` records. Loading the second page creates 3 new chunks (again `[7, 7, 1]`), but now the third chunk from the first request has an overlap of 1 record with the first chunk of the second request. These chunks with 1 record can only be used as the last piece of a request for `pageSize={15}` and are useless in all other cases. From b3dee7a0df054aacd72490d343e7b0e0cd33b547 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Fri, 29 Nov 2024 10:01:49 +0100 Subject: [PATCH 117/121] Align info boxes for the mock function --- docs/data/data-grid/server-side-data/index.md | 6 +++--- docs/data/data-grid/server-side-data/tree-data.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index 03a207437e0e2..02906265e955a 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -176,10 +176,10 @@ The following demo showcases this behavior. {{"demo": "ServerSideDataGrid.js", "bg": "inline"}} :::info -The data source demos use a utility function `useMockServer` to simulate the server-side data fetching. -In a real-world scenario, you should replace this with your own server-side data-fetching logic. +The data source demos use a `useMockServer` utility function to simulate server-side data fetching. +In a real-world scenario you would replace this with your own server-side data-fetching logic. -Open info section of the browser console to see the requests being made and the data being fetched in response. +Open the Info section of your browser console to see the requests being made and the data being fetched in response. ::: ## Data caching diff --git a/docs/data/data-grid/server-side-data/tree-data.md b/docs/data/data-grid/server-side-data/tree-data.md index cc0d15854c585..b4af9c3548391 100644 --- a/docs/data/data-grid/server-side-data/tree-data.md +++ b/docs/data/data-grid/server-side-data/tree-data.md @@ -59,10 +59,10 @@ It also caches the data by default. {{"demo": "ServerSideTreeData.js", "bg": "inline"}} :::info -The data source demos use a utility function `useMockServer` to simulate the server-side data fetching. -In a real-world scenario, you would replace this with your own server-side data fetching logic. +The data source demos use a `useMockServer` utility function to simulate server-side data fetching. +In a real-world scenario you would replace this with your own server-side data-fetching logic. -Open the info section of the browser console to see the requests being made and the data being fetched in response. +Open the Info section of your browser console to see the requests being made and the data being fetched in response. ::: ## Error handling From 593c9070099133f599716c578f58acb91242a647 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Fri, 29 Nov 2024 11:14:34 +0100 Subject: [PATCH 118/121] Improve lazy loading docs --- .../server-side-data/lazy-loading.md | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/data/data-grid/server-side-data/lazy-loading.md b/docs/data/data-grid/server-side-data/lazy-loading.md index 154e1d2228ce5..6942b7880c0ad 100644 --- a/docs/data/data-grid/server-side-data/lazy-loading.md +++ b/docs/data/data-grid/server-side-data/lazy-loading.md @@ -14,25 +14,25 @@ Initially, data for the first page is fetched and displayed in the grid. The value of the total row count determines when the next page's data is loaded: - If the total row count is known, the Data Grid is filled with skeleton rows and fetches more data if one of the skeleton rows falls into the rendering context. - This loading strategy is often referred to as [**viewport loading**](#viewport-loading). + This loading strategy is often referred to as [**viewport loading**](#viewport-loading). -- If the total row count is unknown, the Data Grid fetches more data when the user scrolls to the bottom. - This loading strategy is often referred to as [**infinite loading**](#infinite-loading). +- If the total row count is unknown, the Data Grid fetches more data when the user scrolls to the bottom. + This loading strategy is often referred to as [**infinite loading**](#infinite-loading). :::info You can provide the row count through one of the following ways: - Pass it as the [`rowCount`](/x/api/data-grid/data-grid/#data-grid-prop-rowCount) prop -- Return `rowCount` in the `getRows` method of the [data source](/x/react-data-grid/server-side-data/#data-source) -- Set the `rowCount` using the [`setRowCount`](/x/api/data-grid/grid-api/#grid-api-prop-setRowCount) API method +- Return `rowCount` in the `getRows()` method of the [data source](/x/react-data-grid/server-side-data/#data-source) +- Set the `rowCount` using the [`setRowCount()`](/x/api/data-grid/grid-api/#grid-api-prop-setRowCount) API method -These options are presented in order of precedence, which means if the row count is set using the API, that value is overridden once a new value is returned by the `getRows` method unless it's `undefined`. +These options are presented in order of precedence, which means if the row count is set using the API, that value is overridden once a new value is returned by the `getRows()` method unless it's `undefined`. ::: ## Viewport loading Viewport loading mode is enabled when the row count is known (and is greater than or equal to zero). -The Grid fetches the first page immediately and adds skeleton rows to match the total row count. +The Grid fetches the first page immediately and adds skeleton rows to match the total row count. Other pages are fetched once the user starts scrolling and moves a skeleton row inside the rendering context (with the index range defined by [virtualization](/x/react-data-grid/virtualization/)). If the user scrolls too fast, the Grid loads multiple pages with one request (by adjusting `start` and `end` parameters) to reduce the server load. @@ -59,20 +59,20 @@ Use the `unstable_lazyLoadingRequestThrottleMs` prop to set a custom time, as sh ## Infinite loading -Infinite loading mode is enabled when the row count is unknown (either `-1` or `undefined`). +Infinite loading mode is enabled when the row count is unknown (either `-1` or `undefined`). A new page is loaded when the scroll reaches the bottom of the viewport area. You can use the `scrollEndThreshold` prop to change the area that triggers new requests. -The demo below shows how infinite loading mode works. -Page size is set to `15` and the mock server is configured to return a total of 100 rows. +The demo below shows how infinite loading mode works. +Page size is set to `15` and the mock server is configured to return a total of 100 rows. When the response contains no new rows, the Grid stops requesting new data. {{"demo": "ServerSideLazyLoadingInfinite.js", "bg": "inline"}} ## Updating the loading mode -The grid changes the loading mode dynamically if the total row count gets updated in any of the three ways described above. +The Grid changes the loading mode dynamically if the total row count gets updated by changing the `rowCount` prop, returning different `rowCount` in `GridGetRowsResponse` or via `setRowCount()` API. Based on the previous and the new value for the total row count, the following scenarios are possible: @@ -83,7 +83,7 @@ Based on the previous and the new value for the total row count, the following s - **Known `rowCount` greater than the actual row count**: This can happen either by reducing the value of the row count after more rows were already fetched, or if the row count was unknown and the Grid (while in the infinite loading mode) already fetched more rows. In this case, the Grid resets, fetches the first page, and then continues in one mode or the other depending on the new value of the `rowCount`. :::warning -`rowCount` is expected to be static. +`rowCount` is expected to be static. Changing its value can cause the Grid to reset and the cache to be cleared which may lead to performance and UX degradation. ::: @@ -105,9 +105,9 @@ When completed, it will be possible to use the `unstable_lazyLoading` flag in co ## Error handling -To handle errors, use the `unstable_onDataSourceError` prop as described in [Server-side data—Error handling](/x/react-data-grid/server-side-data/#error-handling). +To handle errors, use the `unstable_onDataSourceError()` prop as described in [Server-side data—Error handling](/x/react-data-grid/server-side-data/#error-handling). -You can pass the second parameter of type `GridGetRowsParams` to the `getRows` method of the [`unstable_dataSource`](/x/api/data-grid/grid-api/#grid-api-prop-unstable_dataSource) to retry the request. +You can pass the second parameter of type `GridGetRowsParams` to the `getRows()` method of the [`unstable_dataSource`](/x/api/data-grid/grid-api/#grid-api-prop-unstable_dataSource) to retry the request. If successful, the Data Grid uses `rows` and `rowCount` data to determine if the rows should be appended at the end of the grid or if the skeleton rows should be replaced. The following demo gives an example how to use `GridGetRowsParams` to retry a failed request. From 6a964232264eeff8782a1c737ba58de96bf53153 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Fri, 29 Nov 2024 11:25:17 +0100 Subject: [PATCH 119/121] Trigger build From 96063f03c1057d97c98ca46f6c7892003af10c32 Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Fri, 29 Nov 2024 13:27:57 +0100 Subject: [PATCH 120/121] Trigger build (again) From 78cfc73e0924701b22e8fc757160b215491fe8ff Mon Sep 17 00:00:00 2001 From: Armin Mehinovic Date: Mon, 2 Dec 2024 13:42:16 +0100 Subject: [PATCH 121/121] Add icon to the title --- docs/data/data-grid/server-side-data/lazy-loading.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/data-grid/server-side-data/lazy-loading.md b/docs/data/data-grid/server-side-data/lazy-loading.md index 6942b7880c0ad..176ddeff29133 100644 --- a/docs/data/data-grid/server-side-data/lazy-loading.md +++ b/docs/data/data-grid/server-side-data/lazy-loading.md @@ -2,7 +2,7 @@ title: React Data Grid - Server-side lazy loading --- -# Data Grid - Server-side lazy loading [](/x/introduction/licensing/#pro-plan 'Pro plan') +# Data Grid - Server-side lazy loading [](/x/introduction/licensing/#pro-plan 'Pro plan')🧪

Learn how to implement lazy-loading rows with a server-side data source.