From 3d71bf4ffc406643e0567aeafd90cdc7894dfb44 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 4 Mar 2024 16:16:54 +0500 Subject: [PATCH 01/90] First rough implementation of data source with tree data --- docs/data/data-grid/events/events.json | 7 + .../server-side-data/ServerSideDataGrid.js | 31 ++ .../server-side-data/ServerSideDataGrid.tsx | 31 ++ .../server-side-data/ServerSideTreeData.js | 68 +++ .../server-side-data/ServerSideTreeData.tsx | 74 +++ .../ServerSideTreeData.tsx.preview | 13 + docs/data/data-grid/server-side-data/index.md | 2 + .../data-grid/server-side-data/tree-data.md | 10 +- docs/data/pages.ts | 3 +- docs/package.json | 1 + .../x/api/data-grid/data-grid-premium.json | 9 +- docs/pages/x/api/data-grid/data-grid-pro.json | 9 +- docs/pages/x/api/data-grid/data-grid.json | 9 +- docs/pages/x/api/data-grid/grid-api.md | 5 + .../data-grid/grid-pagination-model-api.json | 21 + docs/pages/x/api/data-grid/selectors.json | 7 + .../api/buildInterfacesDocumentation.ts | 3 +- .../data-grid-premium/data-grid-premium.json | 4 + .../data-grid-pro/data-grid-pro.json | 4 + .../data-grid/data-grid/data-grid.json | 4 + .../src/hooks/createDummyDataSource.ts | 124 +++++ .../x-data-grid-generator/src/hooks/index.ts | 2 + .../src/hooks/serverUtils.ts | 493 ++++++++++++++++++ .../src/hooks/useDemoData.ts | 5 +- .../src/hooks/useQuery.ts | 201 +------ .../src/DataGridPremium/DataGridPremium.tsx | 51 +- .../src/DataGridPremium/index.ts | 2 +- .../useDataGridPremiumComponent.tsx | 2 + .../useDataGridPremiumProps.ts | 18 +- .../src/models/gridApiPremium.ts | 2 + .../src/DataGridPro/DataGridPro.tsx | 20 +- .../x-data-grid-pro/src/DataGridPro/index.ts | 2 +- .../DataGridPro/useDataGridProComponent.tsx | 4 + .../src/DataGridPro/useDataGridProProps.ts | 18 +- .../GridServerSideTreeDataGroupingCell.tsx | 203 ++++++++ .../src/hooks/features/index.ts | 1 + .../features/serverSideData/dataSourceApi.ts | 24 + .../serverSideData/useGridDataSource.ts | 261 ++++++++++ ...useGridServerSideTreeDataPreProcessors.tsx | 261 ++++++++++ .../features/serverSideTreeData/utils.ts | 18 + .../treeData/useGridTreeDataPreProcessors.tsx | 5 +- .../x-data-grid-pro/src/internals/index.ts | 1 + .../src/internals/propValidation.ts | 11 + .../src/models/dataGridProProps.ts | 38 +- .../x-data-grid-pro/src/models/gridApiPro.ts | 2 + .../{dataSource.ts => gridDataSource.ts} | 19 +- packages/x-data-grid-pro/src/models/index.ts | 1 + .../src/tests/filtering.DataGridPro.test.tsx | 6 +- .../src/tests/rows.DataGridPro.test.tsx | 6 +- .../src/utils/tree/createRowTree.ts | 1 + .../src/utils/tree/insertDataRowInTree.ts | 39 +- .../x-data-grid-pro/src/utils/tree/models.ts | 1 + .../src/utils/tree/updateRowTree.ts | 6 +- .../x-data-grid/src/DataGrid/DataGrid.tsx | 8 +- .../src/DataGrid/useDataGridProps.ts | 1 + .../src/components/GridPagination.tsx | 14 +- .../features/export/useGridPrintExport.tsx | 19 +- .../pagination/gridPaginationInterfaces.ts | 19 +- .../pagination/gridPaginationSelector.ts | 9 + .../src/hooks/features/pagination/index.ts | 3 +- .../features/pagination/useGridPagination.ts | 263 +--------- .../pagination/useGridPaginationModel.ts | 257 +++++++++ .../features/pagination/useGridRowCount.ts | 131 +++++ .../hooks/features/rows/gridRowsInterfaces.ts | 2 +- .../hooks/features/rows/useGridParamsApi.ts | 3 + .../src/hooks/features/rows/useGridRows.ts | 29 +- .../src/models/api/gridApiCommon.ts | 8 +- .../x-data-grid/src/models/api/gridRowApi.ts | 5 + .../src/models/events/gridEventLookup.ts | 4 + .../x-data-grid/src/models/gridApiCaches.ts | 1 + packages/x-data-grid/src/models/gridRows.ts | 19 + .../src/models/props/DataGridProps.ts | 16 +- scripts/x-data-grid-generator.exports.json | 3 +- scripts/x-data-grid-premium.exports.json | 12 +- scripts/x-data-grid-pro.exports.json | 12 +- scripts/x-data-grid.exports.json | 5 +- yarn.lock | 5 + 77 files changed, 2437 insertions(+), 574 deletions(-) create mode 100644 docs/data/data-grid/server-side-data/ServerSideDataGrid.js create mode 100644 docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx create mode 100644 docs/data/data-grid/server-side-data/ServerSideTreeData.js create mode 100644 docs/data/data-grid/server-side-data/ServerSideTreeData.tsx create mode 100644 docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview create mode 100644 docs/pages/x/api/data-grid/grid-pagination-model-api.json create mode 100644 packages/x-data-grid-generator/src/hooks/createDummyDataSource.ts create mode 100644 packages/x-data-grid-generator/src/hooks/serverUtils.ts create mode 100644 packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx create mode 100644 packages/x-data-grid-pro/src/hooks/features/serverSideData/dataSourceApi.ts create mode 100644 packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts create mode 100644 packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx create mode 100644 packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/utils.ts rename packages/x-data-grid-pro/src/models/{dataSource.ts => gridDataSource.ts} (84%) create mode 100644 packages/x-data-grid/src/hooks/features/pagination/useGridPaginationModel.ts create mode 100644 packages/x-data-grid/src/hooks/features/pagination/useGridRowCount.ts diff --git a/docs/data/data-grid/events/events.json b/docs/data/data-grid/events/events.json index a4277ebbd834..6bb6029718fa 100644 --- a/docs/data/data-grid/events/events.json +++ b/docs/data/data-grid/events/events.json @@ -292,6 +292,13 @@ "event": "MuiEvent>", "componentProp": "onRowClick" }, + { + "projects": ["x-data-grid", "x-data-grid-pro", "x-data-grid-premium"], + "name": "rowCountChange", + "description": "Fired when the row count change.", + "params": "number", + "event": "MuiEvent<{}>" + }, { "projects": ["x-data-grid", "x-data-grid-pro", "x-data-grid-premium"], "name": "rowDoubleClick", diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGrid.js b/docs/data/data-grid/server-side-data/ServerSideDataGrid.js new file mode 100644 index 000000000000..fe290b4a834c --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideDataGrid.js @@ -0,0 +1,31 @@ +import * as React from 'react'; +import { DataGridPro } from '@mui/x-data-grid-pro'; +import { createDummyDataSource } from '@mui/x-data-grid-generator'; + +const [dataSource, { initialState, columns }] = createDummyDataSource( + {}, + { useCursorPagination: false }, +); + +const initialStateWithPagination = { + ...initialState, + pagination: { + paginationModel: { pageSize: 10, page: 0 }, + }, +}; + +function ServerSideDataGrid() { + return ( +
+ +
+ ); +} + +export default ServerSideDataGrid; diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx new file mode 100644 index 000000000000..c8e23b74ec2f --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx @@ -0,0 +1,31 @@ +import * as React from 'react'; +import { DataGridPro } from '@mui/x-data-grid-pro'; +import { createDummyDataSource } from '@mui/x-data-grid-generator'; + +const [dataSource, { initialState, columns }] = createDummyDataSource( + {}, + { useCursorPagination: false }, +); + +const initialStateWithPagination = { + ...initialState, + pagination: { + paginationModel: { pageSize: 10, page: 0 }, + }, +}; + +function ServerSideDataGrid() { + return ( +
+ +
+ ); +} + +export default ServerSideDataGrid; diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.js b/docs/data/data-grid/server-side-data/ServerSideTreeData.js new file mode 100644 index 000000000000..b94b2efa6fc0 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.js @@ -0,0 +1,68 @@ +import * as React from 'react'; +import { DataGridPro, useGridApiRef, GridToolbar } from '@mui/x-data-grid-pro'; +import { createDummyDataSource } from '@mui/x-data-grid-generator'; +import { QueryClient } from '@tanstack/query-core'; + +const [dataSource, props] = createDummyDataSource({ + dataSet: 'Employee', + rowLength: 1000, + treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, +}); + +const initialState = { + ...props.initialState, + pagination: { + paginationModel: { + pageSize: 5, + }, + }, +}; + +const cacheInstance = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 1000 * 60 * 5, + }, + }, +}); + +const cache = { + set: (key, value) => { + cacheInstance.setQueryData(key, value); + console.log('setting cache', key, value); + }, + get: (key) => { + console.log('getting cache', key, cacheInstance.getQueryData(key)); + return cacheInstance.getQueryData(key); + }, + invalidate: (queryKey) => { + if (queryKey) { + cacheInstance.invalidateQueries({ queryKey }); + } + cacheInstance.invalidateQueries(); + }, +}; + +const pageSizeOptions = [5, 10, 50]; + +export default function ServerSideTreeData() { + const apiRef = useGridApiRef(); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx new file mode 100644 index 000000000000..ad49721f91c2 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx @@ -0,0 +1,74 @@ +import * as React from 'react'; +import { + DataGridPro, + useGridApiRef, + GridInitialState, + GridToolbar, + GridDataSourceCache, +} from '@mui/x-data-grid-pro'; +import { createDummyDataSource } from '@mui/x-data-grid-generator'; +import { QueryClient } from '@tanstack/query-core'; + +const [dataSource, props] = createDummyDataSource({ + dataSet: 'Employee', + rowLength: 1000, + treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, +}); + +const initialState: GridInitialState = { + ...props.initialState, + pagination: { + paginationModel: { + pageSize: 5, + }, + }, +}; + +const cacheInstance = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 1000 * 60 * 5, + }, + }, +}); + +const cache: GridDataSourceCache = { + set: (key, value) => { + cacheInstance.setQueryData(key, value); + console.log('setting cache', key, value); + }, + get: (key) => { + console.log('getting cache', key, cacheInstance.getQueryData(key)); + return cacheInstance.getQueryData(key); + }, + invalidate: (queryKey) => { + if (queryKey) { + cacheInstance.invalidateQueries({ queryKey }); + } + cacheInstance.invalidateQueries(); + }, +}; + +const pageSizeOptions = [5, 10, 50]; + +export default function ServerSideTreeData() { + const apiRef = useGridApiRef(); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview new file mode 100644 index 000000000000..7e5643237f56 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index 13ee31d9b005..540ad4a494a0 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -131,6 +131,8 @@ const customDataSource: DataSource = { /> ``` +{{"demo": "ServerSideDataGrid.js", "bg": "inline"}} + Not only the code has been reduced significantly, it has removed the hassle of managing controlled states and data fetching logic too. On top of that, the data source will also handle a lot of other aspects like caching and deduping of requests. 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 7ce5524f9c16..1f2f5f71c06b 100644 --- a/docs/data/data-grid/server-side-data/tree-data.md +++ b/docs/data/data-grid/server-side-data/tree-data.md @@ -2,14 +2,12 @@ title: React Server-side tree data --- -# Data Grid - Server-side tree data [](/x/introduction/licensing/#pro-plan 'Pro plan')🚧 +# Data Grid - Server-side tree data [](/x/introduction/licensing/#pro-plan 'Pro plan')

Tree data lazy-loading with server side data source.

-:::warning -This feature isn't implemented yet. It's coming. +To use the server-side tree data, pass the `unstable_dataSource` prop as explained in the overview section, in addition to that passing of some additional props is required for the server-side tree data to work properly. -👍 Upvote [issue #3377](https://github.com/mui/mui-x/issues/3377) if you want to see it land faster. +Following is a demo of the server-side tree data working with server side data source. It supports server side filtering, sorting and pagination. It also uses the `unstable_dataSourceCache` prop to pass a cache object based on the `QueryClient` exposed by `@tanstack/query-core`. -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 [currently proposed workaround](https://mui.com/x/react-data-grid/tree-data/#children-lazy-loading). -::: +{{"demo": "ServerSideTreeData.js", "bg": "inline"}} diff --git a/docs/data/pages.ts b/docs/data/pages.ts index d8fe2a79ce4f..7d63f2aaec02 100644 --- a/docs/data/pages.ts +++ b/docs/data/pages.ts @@ -122,7 +122,7 @@ const pages: MuiPage[] = [ title: 'Server-side data', planned: true, children: [ - { pathname: '/x/react-data-grid/server-side-data', title: 'Overview', planned: true }, + { pathname: '/x/react-data-grid/server-side-data', title: 'Overview' }, { pathname: '/x/react-data-grid/server-side-data/lazy-loading', title: 'Lazy loading', @@ -139,7 +139,6 @@ const pages: MuiPage[] = [ pathname: '/x/react-data-grid/server-side-data/tree-data', title: 'Tree data', plan: 'pro', - planned: true, }, { pathname: '/x/react-data-grid/server-side-data/row-grouping', diff --git a/docs/package.json b/docs/package.json index bd0d1c9149a3..290f4d5173c5 100644 --- a/docs/package.json +++ b/docs/package.json @@ -33,6 +33,7 @@ "@mui/styles": "^5.15.9", "@mui/utils": "^5.15.9", "@react-spring/web": "^9.7.3", + "@tanstack/query-core": "^5.24.8", "@trendmicro/react-interpolate": "^0.5.5", "@types/lodash": "^4.14.202", "@types/moment-hijri": "^2.1.4", 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 f6aab9649ea0..08831bfde61e 100644 --- a/docs/pages/x/api/data-grid/data-grid-premium.json +++ b/docs/pages/x/api/data-grid/data-grid-premium.json @@ -4,10 +4,6 @@ "type": { "name": "arrayOf", "description": "Array<object>" }, "required": true }, - "rows": { - "type": { "name": "arrayOf", "description": "Array<object>" }, - "required": true - }, "aggregationFunctions": { "type": { "name": "object" }, "default": "GRID_AGGREGATION_FUNCTIONS" @@ -448,6 +444,10 @@ "describedArgs": ["params", "event", "details"] } }, + "onRowCountChange": { + "type": { "name": "func" }, + "signature": { "type": "function(count: number) => void", "describedArgs": ["count"] } + }, "onRowDoubleClick": { "type": { "name": "func" }, "signature": { @@ -552,6 +552,7 @@ "rowModesModel": { "type": { "name": "object" } }, "rowPositionsDebounceMs": { "type": { "name": "number" }, "default": "166" }, "rowReordering": { "type": { "name": "bool" }, "default": "false" }, + "rows": { "type": { "name": "array" }, "default": "[]" }, "rowSelection": { "type": { "name": "bool" }, "default": "true" }, "rowSelectionModel": { "type": { 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 0ae775c41b77..8e7272e8c2f0 100644 --- a/docs/pages/x/api/data-grid/data-grid-pro.json +++ b/docs/pages/x/api/data-grid/data-grid-pro.json @@ -4,10 +4,6 @@ "type": { "name": "arrayOf", "description": "Array<object>" }, "required": true }, - "rows": { - "type": { "name": "arrayOf", "description": "Array<object>" }, - "required": true - }, "apiRef": { "type": { "name": "shape", "description": "{ current: object }" } }, "aria-label": { "type": { "name": "string" } }, "aria-labelledby": { "type": { "name": "string" } }, @@ -402,6 +398,10 @@ "describedArgs": ["params", "event", "details"] } }, + "onRowCountChange": { + "type": { "name": "func" }, + "signature": { "type": "function(count: number) => void", "describedArgs": ["count"] } + }, "onRowDoubleClick": { "type": { "name": "func" }, "signature": { @@ -494,6 +494,7 @@ "rowModesModel": { "type": { "name": "object" } }, "rowPositionsDebounceMs": { "type": { "name": "number" }, "default": "166" }, "rowReordering": { "type": { "name": "bool" }, "default": "false" }, + "rows": { "type": { "name": "array" }, "default": "[]" }, "rowSelection": { "type": { "name": "bool" }, "default": "true" }, "rowSelectionModel": { "type": { diff --git a/docs/pages/x/api/data-grid/data-grid.json b/docs/pages/x/api/data-grid/data-grid.json index 6daecb51bd8a..85d08d151cc3 100644 --- a/docs/pages/x/api/data-grid/data-grid.json +++ b/docs/pages/x/api/data-grid/data-grid.json @@ -4,10 +4,6 @@ "type": { "name": "arrayOf", "description": "Array<object>" }, "required": true }, - "rows": { - "type": { "name": "arrayOf", "description": "Array<object>" }, - "required": true - }, "apiRef": { "type": { "name": "shape", "description": "{ current: object }" } }, "aria-label": { "type": { "name": "string" } }, "aria-labelledby": { "type": { "name": "string" } }, @@ -315,6 +311,10 @@ "describedArgs": ["params", "event", "details"] } }, + "onRowCountChange": { + "type": { "name": "func" }, + "signature": { "type": "function(count: number) => void", "describedArgs": ["count"] } + }, "onRowDoubleClick": { "type": { "name": "func" }, "signature": { @@ -384,6 +384,7 @@ "rowHeight": { "type": { "name": "number" }, "default": "52" }, "rowModesModel": { "type": { "name": "object" } }, "rowPositionsDebounceMs": { "type": { "name": "number" }, "default": "166" }, + "rows": { "type": { "name": "array" }, "default": "[]" }, "rowSelection": { "type": { "name": "bool" }, "default": "true" }, "rowSelectionModel": { "type": { diff --git a/docs/pages/x/api/data-grid/grid-api.md b/docs/pages/x/api/data-grid/grid-api.md index 67b125ccb392..b703b0542b20 100644 --- a/docs/pages/x/api/data-grid/grid-api.md +++ b/docs/pages/x/api/data-grid/grid-api.md @@ -33,6 +33,7 @@ import { GridApi } from '@mui/x-data-grid'; | exportDataAsExcel [](/x/introduction/licensing/#premium-plan) | (options?: GridExcelExportOptions) => Promise<void> | Downloads and exports an Excel file of the grid's data. | | exportDataAsPrint | (options?: GridPrintExportOptions) => void | Print the grid's data. | | exportState | (params?: GridExportStateParams) => InitialState | Generates a serializable object containing the exportable parts of the DataGrid state.
These values can then be passed to the `initialState` prop or injected using the `restoreState` method. | +| fetchRowChildren [](/x/introduction/licensing/#pro-plan) | (id: GridRowId) => void | Initiates the fetch of the children of a row. | | forceUpdate | () => void | Forces the grid to rerender. It's often used after a state update. | | getAllColumns | () => GridStateColDef[] | Returns an array of [GridColDef](/x/api/data-grid/grid-col-def/) containing all the column definitions. | | getAllGroupDetails | () => GridColumnGroupLookup | Returns the column group lookup. | @@ -99,6 +100,7 @@ import { GridApi } from '@mui/x-data-grid'; | setAggregationModel [](/x/introduction/licensing/#premium-plan) | (model: GridAggregationModel) => void | Sets the aggregation model to the one given by `model`. | | setCellFocus | (id: GridRowId, field: string) => void | Sets the focus to the cell at the given `id` and `field`. | | setCellSelectionModel [](/x/introduction/licensing/#premium-plan) | (newModel: GridCellSelectionModel) => void | Updates the selected cells to be those passed to the `newModel` argument.
Any cell already selected will be unselected. | +| setChildrenFetched [](/x/introduction/licensing/#pro-plan) | (id: GridRowId, childrenFetched: boolean) => void | Set the fetched children state of a row. | | setColumnHeaderFilterFocus | (field: string, event?: MuiBaseEvent) => void | Sets the focus to the column header filter at the given `field`. | | setColumnHeaderFocus | (field: string, event?: MuiBaseEvent) => void | Sets the focus to the column header at the given `field`. | | setColumnIndex [](/x/introduction/licensing/#pro-plan) | (field: string, targetIndexPosition: number) => void | Moves a column from its original position to the position given by `targetIndexPosition`. | @@ -110,15 +112,18 @@ import { GridApi } from '@mui/x-data-grid'; | setExpandedDetailPanels [](/x/introduction/licensing/#pro-plan) | (ids: GridRowId[]) => void | Changes which rows to expand the detail panel. | | setFilterLogicOperator | (operator: GridLogicOperator) => void | Changes the GridLogicOperator used to connect the filters. | | setFilterModel | (model: GridFilterModel, reason?: GridControlledStateReasonLookup['filter']) => void | Sets the filter model to the one given by `model`. | +| setLoading | (loading: boolean) => void | Sets the internal loading state. | | setPage | (page: number) => void | Sets the displayed page to the value given by `page`. | | setPageSize | (pageSize: number) => void | Sets the number of displayed rows to the value given by `pageSize`. | | setPaginationModel | (model: GridPaginationModel) => void | Sets the `paginationModel` to a new value. | | setPinnedColumns [](/x/introduction/licensing/#pro-plan) | (pinnedColumns: GridPinnedColumnFields) => void | Changes the pinned columns. | | setQuickFilterValues | (values: any[]) => void | Set the quick filter values to the one given by `values` | | setRowChildrenExpansion [](/x/introduction/licensing/#pro-plan) | (id: GridRowId, isExpanded: boolean) => void | Expand or collapse a row children. | +| setRowCount | (rowCount: number) => void | Sets the `rowCount` to a new value. | | setRowGroupingCriteriaIndex [](/x/introduction/licensing/#premium-plan) | (groupingCriteriaField: string, groupingIndex: number) => void | Sets the grouping index of a grouping criteria. | | setRowGroupingModel [](/x/introduction/licensing/#premium-plan) | (model: GridRowGroupingModel) => void | Sets the columns to use as grouping criteria. | | setRowIndex [](/x/introduction/licensing/#pro-plan) | (rowId: GridRowId, targetIndex: number) => void | Moves a row from its original position to the position given by `targetIndex`. | +| setRowLoading [](/x/introduction/licensing/#pro-plan) | (id: GridRowId, loading: boolean) => void | Set the loading state of a row. | | setRows | (rows: GridRowModel[]) => void | Sets a new set of rows. | | setRowSelectionModel | (rowIds: GridRowId[]) => void | Updates the selected rows to be those passed to the `rowIds` argument.
Any row already selected will be unselected. | | setSortModel | (model: GridSortModel) => void | Updates the sort model and triggers the sorting of rows. | diff --git a/docs/pages/x/api/data-grid/grid-pagination-model-api.json b/docs/pages/x/api/data-grid/grid-pagination-model-api.json new file mode 100644 index 000000000000..cb60edc245db --- /dev/null +++ b/docs/pages/x/api/data-grid/grid-pagination-model-api.json @@ -0,0 +1,21 @@ +{ + "name": "GridPaginationModelApi", + "description": "The pagination model API interface that is available in the grid `apiRef`.", + "properties": [ + { + "name": "setPage", + "description": "Sets the displayed page to the value given by page.", + "type": "(page: number) => void" + }, + { + "name": "setPageSize", + "description": "Sets the number of displayed rows to the value given by pageSize.", + "type": "(pageSize: number) => void" + }, + { + "name": "setPaginationModel", + "description": "Sets the paginationModel to a new value.", + "type": "(model: GridPaginationModel) => void" + } + ] +} diff --git a/docs/pages/x/api/data-grid/selectors.json b/docs/pages/x/api/data-grid/selectors.json index f3fd67789100..c381b10af9ae 100644 --- a/docs/pages/x/api/data-grid/selectors.json +++ b/docs/pages/x/api/data-grid/selectors.json @@ -318,6 +318,13 @@ "description": "Get the pagination model", "supportsApiRef": true }, + { + "name": "gridPaginationRowCountSelector", + "returnType": "number", + "category": "Pagination", + "description": "Get the row count", + "supportsApiRef": true + }, { "name": "gridPaginationRowRangeSelector", "returnType": "{ firstRowIndex: number; lastRowIndex: number } | null", diff --git a/docs/scripts/api/buildInterfacesDocumentation.ts b/docs/scripts/api/buildInterfacesDocumentation.ts index d21706c22965..adff74104e0c 100644 --- a/docs/scripts/api/buildInterfacesDocumentation.ts +++ b/docs/scripts/api/buildInterfacesDocumentation.ts @@ -48,7 +48,8 @@ const GRID_API_INTERFACES_WITH_DEDICATED_PAGES = [ 'GridEditingApi', 'GridExcelExportApi', 'GridFilterApi', - 'GridPaginationApi', + // TODO: Relook + 'GridPaginationModelApi', 'GridPrintExportApi', 'GridRowGroupingApi', 'GridRowMultiSelectionApi', 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 c7931fb45627..521fe49f8b73 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 @@ -482,6 +482,10 @@ "details": "Additional details for this callback." } }, + "onRowCountChange": { + "description": "Callback fired when the row count has changed.", + "typeDescriptions": { "count": "Updated row count." } + }, "onRowDoubleClick": { "description": "Callback fired when a double click event comes from a row container element.", "typeDescriptions": { 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 7e08ead8fa2d..ae15af72b804 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 @@ -439,6 +439,10 @@ "details": "Additional details for this callback." } }, + "onRowCountChange": { + "description": "Callback fired when the row count has changed.", + "typeDescriptions": { "count": "Updated row count." } + }, "onRowDoubleClick": { "description": "Callback fired when a double click event comes from a row container element.", "typeDescriptions": { diff --git a/docs/translations/api-docs/data-grid/data-grid/data-grid.json b/docs/translations/api-docs/data-grid/data-grid/data-grid.json index a805b4dea7ea..7abcdced7d42 100644 --- a/docs/translations/api-docs/data-grid/data-grid/data-grid.json +++ b/docs/translations/api-docs/data-grid/data-grid/data-grid.json @@ -337,6 +337,10 @@ "details": "Additional details for this callback." } }, + "onRowCountChange": { + "description": "Callback fired when the row count has changed.", + "typeDescriptions": { "count": "Updated row count." } + }, "onRowDoubleClick": { "description": "Callback fired when a double click event comes from a row container element.", "typeDescriptions": { diff --git a/packages/x-data-grid-generator/src/hooks/createDummyDataSource.ts b/packages/x-data-grid-generator/src/hooks/createDummyDataSource.ts new file mode 100644 index 000000000000..383fab3a5e00 --- /dev/null +++ b/packages/x-data-grid-generator/src/hooks/createDummyDataSource.ts @@ -0,0 +1,124 @@ +import { + getGridDefaultColumnTypes, + GridRowModel, + GridGetRowsParams, + GridGetRowsResponse, + GridColDef, + GridInitialState, + GridDataSource, +} from '@mui/x-data-grid-pro'; +import { + UseDemoDataOptions, + getColumnsFromOptions, + getInitialState, + extrapolateSeed, +} from './useDemoData'; +import { getRealGridData, GridDemoData } from '../services/real-data-service'; +import { addTreeDataOptionsToDemoData } from '../services/tree-data-generator'; +import { + loadServerRows, + processTreeDataRows, + DEFAULT_DATASET_OPTIONS, + DEFAULT_SERVER_OPTIONS, +} from './serverUtils'; +import type { ServerOptions } from './serverUtils'; + +type DataSourceRelatedProps = { + columns: GridColDef[]; + initialState: GridInitialState; + getGroupKey?: (row: GridRowModel) => string; + hasChildren?: (row: GridRowModel) => boolean; + getChildrenCount?: (row: GridRowModel) => number; +}; + +type CreateDummyDataSourceResponse = [dataSource: GridDataSource, props: DataSourceRelatedProps]; + +let data: GridDemoData; +let isDataFetched = false; +let previousRowLength: number; + +export const createDummyDataSource = ( + dataSetOptions?: Partial, + serverOptions?: ServerOptions, +): CreateDummyDataSourceResponse => { + const dataSetOptionsWithDefault = { ...DEFAULT_DATASET_OPTIONS, ...dataSetOptions }; + const serverOptionsWithDefault = { ...DEFAULT_SERVER_OPTIONS, ...serverOptions }; + + const columns = getColumnsFromOptions(dataSetOptionsWithDefault); + const initialState = getInitialState(dataSetOptionsWithDefault, columns); + + const defaultColDef = getGridDefaultColumnTypes(); + const columnsWithDefaultColDef: GridColDef[] = columns.map((column) => ({ + ...defaultColDef[column.type || 'string'], + ...column, + })); + + const isTreeData = dataSetOptionsWithDefault.treeData?.groupingField != null; + + let getGroupKey; + let hasChildren; + let getChildrenCount; + if (isTreeData) { + getGroupKey = (row: GridRowModel): string => + row[dataSetOptionsWithDefault.treeData!.groupingField!]; + hasChildren = (row: GridRowModel): boolean => row.hasChildren; + getChildrenCount = (row: GridRowModel): number => row.descendantCount; + } + const getRows = async (params: GridGetRowsParams): Promise => { + if (!isDataFetched || previousRowLength !== dataSetOptionsWithDefault.rowLength) { + // Fetch all the data on the first request + const rowLength = dataSetOptionsWithDefault.rowLength; + if (rowLength > 1000) { + data = await getRealGridData(1000, columns); + data = await extrapolateSeed(rowLength, data); + } else { + data = await getRealGridData(rowLength, columns); + } + if (isTreeData) { + data = addTreeDataOptionsToDemoData(data, dataSetOptionsWithDefault.treeData); + } + isDataFetched = true; + previousRowLength = rowLength; + } + + let getRowsResponse: GridGetRowsResponse; + + if (isTreeData /* || TODO: `isRowGrouping` */) { + const { rows, rootRowCount } = await processTreeDataRows( + data.rows, + params, + serverOptionsWithDefault, + columnsWithDefaultColDef, + ); + + getRowsResponse = { + rows: rows.slice().map((row) => ({ ...row, path: undefined })), + rowCount: rootRowCount, + }; + } else { + // plain data + const { returnedRows, nextCursor, totalRowCount } = await loadServerRows( + data.rows, + { ...params, ...params.paginationModel }, + serverOptionsWithDefault, + columnsWithDefaultColDef, + ); + getRowsResponse = { rows: returnedRows, rowCount: totalRowCount, pageInfo: { nextCursor } }; + } + + return new Promise((resolve) => { + resolve(getRowsResponse); + }); + }; + + return [ + { getRows }, + { + columns: columnsWithDefaultColDef, + initialState, + getGroupKey, + hasChildren, + getChildrenCount, + }, + ]; +}; diff --git a/packages/x-data-grid-generator/src/hooks/index.ts b/packages/x-data-grid-generator/src/hooks/index.ts index 06e612fa89c8..0facf9eaab39 100644 --- a/packages/x-data-grid-generator/src/hooks/index.ts +++ b/packages/x-data-grid-generator/src/hooks/index.ts @@ -2,3 +2,5 @@ export * from './useDemoData'; export * from './useBasicDemoData'; export * from './useMovieData'; export * from './useQuery'; +export * from './createDummyDataSource'; +export { loadServerRows } from './serverUtils'; diff --git a/packages/x-data-grid-generator/src/hooks/serverUtils.ts b/packages/x-data-grid-generator/src/hooks/serverUtils.ts new file mode 100644 index 000000000000..f5f3a4c0602c --- /dev/null +++ b/packages/x-data-grid-generator/src/hooks/serverUtils.ts @@ -0,0 +1,493 @@ +import { + GridRowModel, + GridFilterModel, + GridSortModel, + GridLogicOperator, + GridFilterOperator, + GridColDef, + GridRowId, + GridPaginationModel, + GridValidRowModel, +} from '@mui/x-data-grid-pro'; +import { GridStateColDef } from '@mui/x-data-grid-pro/internals'; +import { UseDemoDataOptions } from './useDemoData'; +import { randomInt } from '../services/random-generator'; + +export interface FakeServerResponse { + returnedRows: GridRowModel[]; + nextCursor?: string; + totalRowCount: number; +} + +export interface PageInfo { + totalRowCount?: number; + nextCursor?: string; + pageSize?: number; +} + +export interface DefaultServerOptions { + minDelay: number; + maxDelay: number; + useCursorPagination?: boolean; + /* + * The success rate of the server response. It is a number between 0 and 1. + * 0 means that the server will always return an error. + * 1 means that the server will always return a success. + * `@default 1` + */ + successRate?: number; +} + +export type ServerOptions = Partial; + +export interface QueryOptions { + cursor?: GridRowId; + page?: number; + pageSize?: number; + // TODO: implement the behavior liked to following models + filterModel?: GridFilterModel; + sortModel?: GridSortModel; + firstRowToRender?: number; + lastRowToRender?: number; +} + +export interface ServerSideQueryOptions { + cursor?: GridRowId; + paginationModel?: GridPaginationModel; + groupKeys?: string[]; + // TODO: implement the behavior liked to following models + filterModel?: GridFilterModel; + sortModel?: GridSortModel; + firstRowToRender?: number; + lastRowToRender?: number; +} + +export const DEFAULT_DATASET_OPTIONS: UseDemoDataOptions = { + dataSet: 'Commodity', + rowLength: 100, + maxColumns: 6, +}; + +declare const DISABLE_CHANCE_RANDOM: any; +export const disableDelay = typeof DISABLE_CHANCE_RANDOM !== 'undefined' && DISABLE_CHANCE_RANDOM; + +export const DEFAULT_SERVER_OPTIONS: DefaultServerOptions = { + minDelay: disableDelay ? 0 : 100, + maxDelay: disableDelay ? 0 : 300, + useCursorPagination: true, +}; + +const apiRef = {} as any; + +const simplifiedValueGetter = (field: string, colDef: GridColDef) => (row: GridRowModel) => { + return colDef.valueGetter?.(row[row.id] as never, row, colDef, apiRef) || row[field]; +}; + +const getRowComparator = ( + sortModel: GridSortModel | undefined, + columnsWithDefaultColDef: GridColDef[], +) => { + if (!sortModel) { + const comparator = () => 0; + return comparator; + } + const sortOperators = sortModel.map((sortItem) => { + const columnField = sortItem.field; + const colDef = columnsWithDefaultColDef.find(({ field }) => field === columnField) as any; + return { + ...sortItem, + valueGetter: simplifiedValueGetter(columnField, colDef), + sortComparator: colDef.sortComparator, + }; + }); + + const comparator = (row1: GridRowModel, row2: GridRowModel) => + sortOperators.reduce((acc, { valueGetter, sort, sortComparator }) => { + if (acc !== 0) { + return acc; + } + const v1 = valueGetter(row1); + const v2 = valueGetter(row2); + return sort === 'desc' ? -1 * sortComparator(v1, v2) : sortComparator(v1, v2); + }, 0); + + return comparator; +}; + +const buildQuickFilterApplier = (filterModel: GridFilterModel, columns: GridColDef[]) => { + const quickFilterValues = filterModel.quickFilterValues?.filter(Boolean) ?? []; + if (quickFilterValues.length === 0) { + return null; + } + + const appliersPerField = [] as { + column: GridColDef; + appliers: { + fn: null | ((...args: any[]) => boolean); + }[]; + }[]; + + const stubApiRef = { + current: { + getRowFormattedValue: (row: GridValidRowModel, c: GridColDef) => { + const field = c.field; + return row[field]; + }, + }, + }; + + columns.forEach((column) => { + const getApplyQuickFilterFn = column?.getApplyQuickFilterFn; + + if (getApplyQuickFilterFn) { + appliersPerField.push({ + column, + appliers: quickFilterValues.map((quickFilterValue) => { + return { + fn: getApplyQuickFilterFn( + quickFilterValue, + column as GridStateColDef, + stubApiRef as any, + ), + }; + }), + }); + } + }); + + return function isRowMatchingQuickFilter( + row: GridValidRowModel, + shouldApplyFilter?: (field: string) => boolean, + ) { + const result = {} as Record; + /* eslint-disable no-restricted-syntax, no-labels */ + outer: for (let v = 0; v < quickFilterValues.length; v += 1) { + const filterValue = quickFilterValues[v]; + + for (let i = 0; i < appliersPerField.length; i += 1) { + const { column, appliers } = appliersPerField[i]; + const { field } = column; + + if (shouldApplyFilter && !shouldApplyFilter(field)) { + continue; + } + + const applier = appliers[v]; + const value = row[field]; + + if (applier.fn === null) { + continue; + } + const isMatching = applier.fn(value, row, column, stubApiRef); + + if (isMatching) { + result[filterValue] = true; + continue outer; + } + } + + result[filterValue] = false; + } + /* eslint-enable no-restricted-syntax, no-labels */ + + return result; + }; +}; + +const getQuicklyFilteredRows = ( + rows: GridRowModel[], + filterModel: GridFilterModel | undefined, + columnsWithDefaultColDef: GridColDef[], +) => { + if (filterModel === undefined || filterModel.quickFilterValues?.length === 0) { + return rows; + } + + const isRowMatchingQuickFilter = buildQuickFilterApplier(filterModel, columnsWithDefaultColDef); + + if (isRowMatchingQuickFilter) { + return rows.filter((row) => { + const result = isRowMatchingQuickFilter(row); + return filterModel.quickFilterLogicOperator === GridLogicOperator.And + ? Object.values(result).every(Boolean) + : Object.values(result).some(Boolean); + }); + } + return rows; +}; + +const getFilteredRows = ( + rows: GridRowModel[], + filterModel: GridFilterModel | undefined, + columnsWithDefaultColDef: GridColDef[], +) => { + if (filterModel === undefined || filterModel.items.length === 0) { + return rows; + } + + const valueGetters = filterModel.items.map(({ field }) => + simplifiedValueGetter( + field, + columnsWithDefaultColDef.find((column) => column.field === field) as any, + ), + ); + + const filterFunctions = filterModel.items.map((filterItem) => { + const { field, operator } = filterItem; + const colDef: GridColDef = columnsWithDefaultColDef.find( + (column) => column.field === field, + ) as any; + + if (!colDef.filterOperators) { + throw new Error(`MUI: No filter operator found for column '${field}'.`); + } + const filterOperator: any = colDef.filterOperators.find( + ({ value }: GridFilterOperator) => operator === value, + ); + + const parsedValue = filterItem.value; + + // TODO: Fix value parser impl. + // if (colDef.valueParser) { + // const parser = colDef.valueParser; + // parsedValue = Array.isArray(filterItem.value) + // ? filterItem.value?.map((x) => parser(x)) + // : parser(filterItem.value); + // } + + return filterOperator.getApplyFilterFn({ filterItem, value: parsedValue }, colDef); + }); + + if (filterModel.logicOperator === GridLogicOperator.Or) { + return rows.filter((row: GridRowModel) => + filterModel.items.some((_, index) => { + const value = valueGetters[index](row); + return filterFunctions[index] === null ? true : filterFunctions[index](value); + }), + ); + } + return rows.filter((row: GridRowModel) => + filterModel.items.every((_, index) => { + const value = valueGetters[index](row); + return filterFunctions[index] === null ? true : filterFunctions[index](value); + }), + ); +}; + +/** + * Simulates server data loading + */ +export const loadServerRows = ( + rows: GridRowModel[], + queryOptions: QueryOptions, + serverOptions: ServerOptions, + columnsWithDefaultColDef: GridColDef[], +): Promise => { + const { minDelay = 100, maxDelay = 300, useCursorPagination } = serverOptions; + + if (maxDelay < minDelay) { + throw new Error('serverOptions.minDelay is larger than serverOptions.maxDelay '); + } + const delay = randomInt(minDelay, maxDelay); + + const { cursor, page = 0, pageSize } = queryOptions; + + let nextCursor; + let firstRowIndex; + let lastRowIndex; + + let filteredRows = getFilteredRows(rows, queryOptions.filterModel, columnsWithDefaultColDef); + + const rowComparator = getRowComparator(queryOptions.sortModel, columnsWithDefaultColDef); + filteredRows = [...filteredRows].sort(rowComparator); + + const totalRowCount = filteredRows.length; + if (!pageSize) { + firstRowIndex = 0; + lastRowIndex = filteredRows.length; + } 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; + + nextCursor = lastRowIndex >= filteredRows.length ? undefined : filteredRows[lastRowIndex].id; + } else { + firstRowIndex = page * pageSize; + lastRowIndex = (page + 1) * pageSize; + } + const response: FakeServerResponse = { + returnedRows: filteredRows.slice(firstRowIndex, lastRowIndex), + nextCursor, + totalRowCount, + }; + + return new Promise((resolve) => { + setTimeout(() => { + resolve(response); + }, delay); // simulate network latency + }); +}; + +interface ProcessTreeDataRowsResponse { + rows: GridRowModel[]; + rootRowCount: number; +} + +const findTreeDataRowChildren = ( + allRows: GridRowModel[], + parentPath: string[], + pathKey: string = 'path', + depth: number = 1, // the depth of the children to find relative to parentDepth, `-1` to find all +) => { + const parentDepth = parentPath.length; + const children = []; + for (let i = 0; i < allRows.length; i += 1) { + const row = allRows[i]; + const rowPath = row[pathKey]; + if (!rowPath) { + continue; + } + if ( + ((depth < 0 && rowPath.length > parentDepth) || rowPath.length === parentDepth + depth) && + parentPath.every((value, index) => value === rowPath[index]) + ) { + children.push(row); + } + } + return children; +}; + +type GetTreeDataFilteredRows = ( + rows: GridValidRowModel[], + filterModel: GridFilterModel | undefined, + columnsWithDefaultColDef: GridColDef[], +) => GridValidRowModel; + +const getTreeDataFilteredRows: GetTreeDataFilteredRows = ( + rows, + filterModel, + columnsWithDefaultColDef, +): GridValidRowModel[] => { + let filteredRows = [...rows]; + if (filterModel && filterModel.quickFilterValues?.length! > 0) { + filteredRows = getQuicklyFilteredRows(rows, filterModel, columnsWithDefaultColDef); + } + if (filterModel?.items.length === 0) { + filteredRows = getFilteredRows(filteredRows, filterModel, columnsWithDefaultColDef); + } + + if (filteredRows.length === rows.length || filteredRows.length === 0) { + return filteredRows; + } + + const pathsToIndexesMap = new Map(); + rows.forEach((row: GridValidRowModel, index: number) => { + pathsToIndexesMap.set(row.path.join(','), index); + }); + + const includedPaths = new Set(); + + const missingChildren: GridValidRowModel[] = []; + + // include missing children of filtered rows + filteredRows.forEach((row) => { + const path = row.path; + if (path) { + const children = findTreeDataRowChildren(rows, path, 'path', -1); + children.forEach((child) => { + const subPath = child.path.join(','); + if (!includedPaths.has(subPath)) { + missingChildren.push(child); + } + }); + } + }); + + filteredRows = missingChildren.concat(filteredRows); + + const missingParents: GridValidRowModel[] = []; + + // include missing parents of filtered rows + filteredRows.forEach((row) => { + const path = row.path; + if (path) { + includedPaths.add(path.join(',')); + for (let i = 0; i < path.length - 1; i += 1) { + const subPath = path.slice(0, i + 1).join(','); + if (!includedPaths.has(subPath)) { + const index = pathsToIndexesMap.get(subPath); + if (index !== undefined) { + missingParents.push(rows[index]); + includedPaths.add(subPath); + } + } + } + } + }); + + return missingParents.concat(filteredRows); +}; + +/** + * Simulates server data loading + */ +export const processTreeDataRows = ( + rows: GridRowModel[], + queryOptions: ServerSideQueryOptions, + serverOptions: ServerOptions, + columnsWithDefaultColDef: GridColDef[], +): Promise => { + const { minDelay = 100, maxDelay = 300 } = serverOptions; + const pathKey = 'path'; + // TODO: Support filtering and cursor based pagination + if (maxDelay < minDelay) { + throw new Error('serverOptions.minDelay is larger than serverOptions.maxDelay '); + } + + if (queryOptions.groupKeys == null) { + throw new Error('serverOptions.groupKeys must be defined to compute tree data '); + } + + const delay = randomInt(minDelay, maxDelay); + + // apply plain filtering + const filteredRows = getTreeDataFilteredRows( + rows, + queryOptions.filterModel, + columnsWithDefaultColDef, + ) as GridValidRowModel[]; + + // get root row count + const rootRowCount = findTreeDataRowChildren(filteredRows, []).length; + + // find direct children referring to the `parentPath` + const childRows = findTreeDataRowChildren(filteredRows, queryOptions.groupKeys); + let childRowsWithDescendantCounts = childRows.map((row) => { + const descendants = findTreeDataRowChildren(filteredRows, row[pathKey], pathKey, -1); + const descendantCount = descendants.length; + return { ...row, descendantCount, hasChildren: descendantCount > 0 } as GridRowModel; + }); + + if (queryOptions.sortModel) { + // apply sorting + const rowComparator = getRowComparator(queryOptions.sortModel, columnsWithDefaultColDef); + childRowsWithDescendantCounts = [...childRowsWithDescendantCounts].sort(rowComparator); + } + + if (queryOptions.paginationModel && queryOptions.groupKeys.length === 0) { + // Only paginate root rows, grid should refetch root rows when `paginationModel` updates + const { pageSize, page } = queryOptions.paginationModel; + if (pageSize < childRowsWithDescendantCounts.length) { + childRowsWithDescendantCounts = childRowsWithDescendantCounts.slice( + page * pageSize, + (page + 1) * pageSize, + ); + } + } + + return new Promise((resolve) => { + setTimeout(() => { + resolve({ rows: childRowsWithDescendantCounts, rootRowCount }); + }, delay); // simulate network latency + }); +}; diff --git a/packages/x-data-grid-generator/src/hooks/useDemoData.ts b/packages/x-data-grid-generator/src/hooks/useDemoData.ts index 677ff89be441..bb36089a8d1f 100644 --- a/packages/x-data-grid-generator/src/hooks/useDemoData.ts +++ b/packages/x-data-grid-generator/src/hooks/useDemoData.ts @@ -37,7 +37,10 @@ export interface UseDemoDataOptions { // Generate fake data from a seed. // It's about x20 faster than getRealData. -async function extrapolateSeed(rowLength: number, data: GridDemoData): Promise { +export async function extrapolateSeed( + rowLength: number, + data: GridDemoData, +): Promise { return new Promise((resolve) => { const seed = data.rows; const rows = data.rows.slice(); diff --git a/packages/x-data-grid-generator/src/hooks/useQuery.ts b/packages/x-data-grid-generator/src/hooks/useQuery.ts index c888d14faa0e..d9bb1d254f52 100644 --- a/packages/x-data-grid-generator/src/hooks/useQuery.ts +++ b/packages/x-data-grid-generator/src/hooks/useQuery.ts @@ -1,14 +1,5 @@ import * as React from 'react'; -import { - getGridDefaultColumnTypes, - GridRowModel, - GridFilterModel, - GridSortModel, - GridRowId, - GridLogicOperator, - GridFilterOperator, - GridColDef, -} from '@mui/x-data-grid-pro'; +import { getGridDefaultColumnTypes, GridRowModel } from '@mui/x-data-grid-pro'; import { isDeepEqual } from '@mui/x-data-grid/internals'; import { useDemoData, @@ -16,194 +7,8 @@ import { getColumnsFromOptions, getInitialState, } from './useDemoData'; -import { randomInt } from '../services/random-generator'; - -const apiRef = {} as any; - -const simplifiedValueGetter = (field: string, colDef: GridColDef) => (row: GridRowModel) => { - return colDef.valueGetter?.(row[row.id] as never, row, colDef, apiRef) || row[field]; -}; - -const getRowComparator = ( - sortModel: GridSortModel | undefined, - columnsWithDefaultColDef: GridColDef[], -) => { - if (!sortModel) { - const comparator = () => 0; - return comparator; - } - const sortOperators = sortModel.map((sortItem) => { - const columnField = sortItem.field; - const colDef = columnsWithDefaultColDef.find(({ field }) => field === columnField) as any; - return { - ...sortItem, - valueGetter: simplifiedValueGetter(columnField, colDef), - sortComparator: colDef.sortComparator, - }; - }); - - const comparator = (row1: GridRowModel, row2: GridRowModel) => - sortOperators.reduce((acc, { valueGetter, sort, sortComparator }) => { - if (acc !== 0) { - return acc; - } - const v1 = valueGetter(row1); - const v2 = valueGetter(row2); - return sort === 'desc' ? -1 * sortComparator(v1, v2) : sortComparator(v1, v2); - }, 0); - - return comparator; -}; - -const getFilteredRows = ( - rows: GridRowModel[], - filterModel: GridFilterModel | undefined, - columnsWithDefaultColDef: GridColDef[], -) => { - if (filterModel === undefined || filterModel.items.length === 0) { - return rows; - } - - const valueGetters = filterModel.items.map(({ field }) => - simplifiedValueGetter( - field, - columnsWithDefaultColDef.find((column) => column.field === field) as any, - ), - ); - const filterFunctions = filterModel.items.map((filterItem) => { - const { field, operator } = filterItem; - const colDef = columnsWithDefaultColDef.find((column) => column.field === field) as any; - - const filterOperator: any = colDef.filterOperators.find( - ({ value }: GridFilterOperator) => operator === value, - ); - - let parsedValue = filterItem.value; - if (colDef.valueParser) { - const parser = colDef.valueParser; - parsedValue = Array.isArray(filterItem.value) - ? filterItem.value?.map((x) => parser(x)) - : parser(filterItem.value); - } - - return filterOperator?.getApplyFilterFn({ filterItem, value: parsedValue }, colDef); - }); - - if (filterModel.logicOperator === GridLogicOperator.Or) { - return rows.filter((row: GridRowModel) => - filterModel.items.some((_, index) => { - const value = valueGetters[index](row); - return filterFunctions[index] === null ? true : filterFunctions[index]({ value }); - }), - ); - } - return rows.filter((row: GridRowModel) => - filterModel.items.every((_, index) => { - const value = valueGetters[index](row); - return filterFunctions[index] === null ? true : filterFunctions[index]({ value }); - }), - ); -}; - -/** - * Simulates server data loading - */ -export const loadServerRows = ( - rows: GridRowModel[], - queryOptions: QueryOptions, - serverOptions: ServerOptions, - columnsWithDefaultColDef: GridColDef[], -): Promise => { - const { minDelay = 100, maxDelay = 300, useCursorPagination } = serverOptions; - - if (maxDelay < minDelay) { - throw new Error('serverOptions.minDelay is larger than serverOptions.maxDelay '); - } - const delay = randomInt(minDelay, maxDelay); - - const { cursor, page = 0, pageSize } = queryOptions; - - let nextCursor; - let firstRowIndex; - let lastRowIndex; - - let filteredRows = getFilteredRows(rows, queryOptions.filterModel, columnsWithDefaultColDef); - - const rowComparator = getRowComparator(queryOptions.sortModel, columnsWithDefaultColDef); - filteredRows = [...filteredRows].sort(rowComparator); - - const totalRowCount = filteredRows.length; - if (!pageSize) { - firstRowIndex = 0; - lastRowIndex = filteredRows.length; - } 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; - - nextCursor = lastRowIndex >= filteredRows.length ? undefined : filteredRows[lastRowIndex].id; - } else { - firstRowIndex = page * pageSize; - lastRowIndex = (page + 1) * pageSize; - } - const response: FakeServerResponse = { - returnedRows: filteredRows.slice(firstRowIndex, lastRowIndex), - nextCursor, - totalRowCount, - }; - - return new Promise((resolve) => { - setTimeout(() => { - resolve(response); - }, delay); // simulate network latency - }); -}; - -interface FakeServerResponse { - returnedRows: GridRowModel[]; - nextCursor?: string; - totalRowCount: number; -} - -interface PageInfo { - totalRowCount?: number; - nextCursor?: string; - pageSize?: number; -} - -interface DefaultServerOptions { - minDelay: number; - maxDelay: number; - useCursorPagination?: boolean; -} - -type ServerOptions = Partial; - -export interface QueryOptions { - cursor?: GridRowId; - page?: number; - pageSize?: number; - // TODO: implement the behavior liked to following models - filterModel?: GridFilterModel; - sortModel?: GridSortModel; - firstRowToRender?: number; - lastRowToRender?: number; -} - -const DEFAULT_DATASET_OPTIONS: UseDemoDataOptions = { - dataSet: 'Commodity', - rowLength: 100, - maxColumns: 6, -}; - -declare const DISABLE_CHANCE_RANDOM: any; -const disableDelay = typeof DISABLE_CHANCE_RANDOM !== 'undefined' && DISABLE_CHANCE_RANDOM; - -const DEFAULT_SERVER_OPTIONS: DefaultServerOptions = { - minDelay: disableDelay ? 0 : 100, - maxDelay: disableDelay ? 0 : 300, - useCursorPagination: true, -}; +import { DEFAULT_DATASET_OPTIONS, DEFAULT_SERVER_OPTIONS, loadServerRows } from './serverUtils'; +import type { ServerOptions, QueryOptions, PageInfo } from './serverUtils'; export const createFakeServer = ( dataSetOptions?: Partial, diff --git a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx index 17e518486a87..edd901d424f9 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx @@ -60,21 +60,6 @@ const DataGridPremiumRaw = React.forwardRef(function DataGridPremium ); }); -interface DataGridPremiumComponent { - ( - props: DataGridPremiumProps & React.RefAttributes, - ): React.JSX.Element; - propTypes?: any; -} - -/** - * Demos: - * - [DataGridPremium](https://mui.com/x/react-data-grid/demo/) - * - * API: - * - [DataGridPremium API](https://mui.com/x/api/data-grid/data-grid-premium/) - */ -export const DataGridPremium = React.memo(DataGridPremiumRaw) as DataGridPremiumComponent; DataGridPremiumRaw.propTypes = { // ----------------------------- Warning -------------------------------- @@ -371,6 +356,7 @@ DataGridPremiumRaw.propTypes = { * @returns {string} The CSS class to apply to the cell. */ getCellClassName: PropTypes.func, + getChildrenCount: PropTypes.func, /** * Function that returns the element to render in row detail. * @param {GridRowParams} params With all properties from [[GridRowParams]]. @@ -392,6 +378,7 @@ DataGridPremiumRaw.propTypes = { * @returns {number | null} The estimated row height value. If `null` or `undefined` then the default row height, based on the density, is applied. */ getEstimatedRowHeight: PropTypes.func, + getGroupKey: PropTypes.func, /** * Function that applies CSS classes dynamically on rows. * @param {GridRowClassNameParams} params With all properties from [[GridRowClassNameParams]]. @@ -427,6 +414,7 @@ DataGridPremiumRaw.propTypes = { * The grouping column used by the tree data. */ groupingColDef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + hasChildren: PropTypes.func, /** * If `true`, enables the data grid filtering on header feature. * @default false @@ -754,6 +742,11 @@ DataGridPremiumRaw.propTypes = { * @param {GridCallbackDetails} details Additional details for this callback. */ onRowClick: PropTypes.func, + /** + * Callback fired when the row count has changed. + * @param {number} count Updated row count. + */ + onRowCountChange: PropTypes.func, /** * Callback fired when a double click event comes from a row container element. * @param {GridRowParams} params With all properties from [[RowParams]]. @@ -913,8 +906,9 @@ DataGridPremiumRaw.propTypes = { rowReordering: PropTypes.bool, /** * Set of rows of type [[GridRowsProp]]. + * @default [] */ - rows: PropTypes.arrayOf(PropTypes.object).isRequired, + rows: PropTypes.array, /** * If `false`, the row selection mode is disabled. * @default true @@ -1019,4 +1013,29 @@ DataGridPremiumRaw.propTypes = { * @default false */ treeData: PropTypes.bool, + unstable_dataSource: PropTypes.shape({ + getRows: PropTypes.func.isRequired, + updateRow: PropTypes.func, + }), + unstable_dataSourceCache: PropTypes.shape({ + get: PropTypes.func.isRequired, + invalidate: PropTypes.func.isRequired, + set: PropTypes.func.isRequired, + }), } as any; + +interface DataGridPremiumComponent { + ( + props: DataGridPremiumProps & React.RefAttributes, + ): React.JSX.Element; + propTypes?: any; +} + +/** + * Demos: + * - [DataGridPremium](https://mui.com/x/react-data-grid/demo/) + * + * API: + * - [DataGridPremium API](https://mui.com/x/api/data-grid/data-grid-premium/) + */ +export const DataGridPremium = React.memo(DataGridPremiumRaw) as DataGridPremiumComponent; diff --git a/packages/x-data-grid-premium/src/DataGridPremium/index.ts b/packages/x-data-grid-premium/src/DataGridPremium/index.ts index 283b4bc437bb..2de98cc82798 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/index.ts +++ b/packages/x-data-grid-premium/src/DataGridPremium/index.ts @@ -1,3 +1,3 @@ export * from './DataGrid'; export * from './DataGridPremium'; -export { DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES } from './useDataGridPremiumProps'; +export { GET_DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES } from './useDataGridPremiumProps'; diff --git a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx index c1d5bf49973a..6b4de3b03bf5 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx @@ -65,6 +65,7 @@ import { useGridHeaderFiltering, virtualizationStateInitializer, useGridVirtualization, + useGridServerSideTreeDataPreProcessors, } from '@mui/x-data-grid-pro/internals'; import { GridApiPremium, GridPrivateApiPremium } from '../models/gridApiPremium'; import { DataGridPremiumProcessedProps } from '../models/dataGridPremiumProps'; @@ -99,6 +100,7 @@ export const useDataGridPremiumComponent = ( useGridRowReorderPreProcessors(apiRef, props); useGridRowGroupingPreProcessors(apiRef, props); useGridTreeDataPreProcessors(apiRef, props); + useGridServerSideTreeDataPreProcessors(apiRef, props); useGridLazyLoaderPreProcessors(apiRef, props); useGridRowPinningPreProcessors(apiRef); useGridAggregationPreProcessors(apiRef, props); diff --git a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts index c77bc51f8c99..6fa61af148c8 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts +++ b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts @@ -1,6 +1,10 @@ import * as React from 'react'; import { useThemeProps } from '@mui/material/styles'; -import { DATA_GRID_PRO_PROPS_DEFAULT_VALUES, GRID_DEFAULT_LOCALE_TEXT } from '@mui/x-data-grid-pro'; +import { + GET_DATA_GRID_PRO_PROPS_DEFAULT_VALUES, + GRID_DEFAULT_LOCALE_TEXT, + DataGridProProps, +} from '@mui/x-data-grid-pro'; import { computeSlots, useProps } from '@mui/x-data-grid-pro/internals'; import { DataGridPremiumProps, @@ -11,11 +15,15 @@ import { GridPremiumSlotsComponent } from '../models'; import { GRID_AGGREGATION_FUNCTIONS } from '../hooks/features/aggregation'; import { DATA_GRID_PREMIUM_DEFAULT_SLOTS_COMPONENTS } from '../constants/dataGridPremiumDefaultSlotsComponents'; +interface GetDataGridPremiumPropsDefaultValues extends DataGridPremiumProps {} + /** * The default values of `DataGridPremiumPropsWithDefaultValue` to inject in the props of DataGridPremium. */ -export const DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES: DataGridPremiumPropsWithDefaultValue = { - ...DATA_GRID_PRO_PROPS_DEFAULT_VALUES, +export const GET_DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES: ( + themedProps: GetDataGridPremiumPropsDefaultValues, +) => DataGridPremiumPropsWithDefaultValue = (themedProps) => ({ + ...GET_DATA_GRID_PRO_PROPS_DEFAULT_VALUES(themedProps as DataGridProProps), cellSelection: false, disableAggregation: false, disableRowGrouping: false, @@ -30,7 +38,7 @@ export const DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES: DataGridPremiumPropsWithDef const text = pastedText.replace(/\r?\n$/, ''); return text.split(/\r\n|\n|\r/).map((row) => row.split('\t')); }, -}; +}); const defaultSlots = DATA_GRID_PREMIUM_DEFAULT_SLOTS_COMPONENTS; @@ -59,7 +67,7 @@ export const useDataGridPremiumProps = (inProps: DataGridPremiumProps) => { return React.useMemo( () => ({ - ...DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES, + ...GET_DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES(themedProps), ...themedProps, localeText, slots, diff --git a/packages/x-data-grid-premium/src/models/gridApiPremium.ts b/packages/x-data-grid-premium/src/models/gridApiPremium.ts index e79561d733b9..05ba45172210 100644 --- a/packages/x-data-grid-premium/src/models/gridApiPremium.ts +++ b/packages/x-data-grid-premium/src/models/gridApiPremium.ts @@ -9,6 +9,7 @@ import { GridRowMultiSelectionApi, GridColumnReorderApi, GridRowProApi, + GridDataSourceApi, } from '@mui/x-data-grid-pro'; import { GridInitialStatePremium, GridStatePremium } from './gridStatePremium'; import type { GridRowGroupingApi, GridExcelExportApi, GridAggregationApi } from '../hooks'; @@ -29,6 +30,7 @@ export interface GridApiPremium GridExcelExportApi, GridAggregationApi, GridRowPinningApi, + GridDataSourceApi, GridCellSelectionApi, // APIs that are private in Community plan, but public in Pro and Premium plans GridRowMultiSelectionApi, diff --git a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx index cbb17ba529b8..3663976878a5 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx @@ -312,6 +312,7 @@ DataGridProRaw.propTypes = { * @returns {string} The CSS class to apply to the cell. */ getCellClassName: PropTypes.func, + getChildrenCount: PropTypes.func, /** * Function that returns the element to render in row detail. * @param {GridRowParams} params With all properties from [[GridRowParams]]. @@ -333,6 +334,7 @@ DataGridProRaw.propTypes = { * @returns {number | null} The estimated row height value. If `null` or `undefined` then the default row height, based on the density, is applied. */ getEstimatedRowHeight: PropTypes.func, + getGroupKey: PropTypes.func, /** * Function that applies CSS classes dynamically on rows. * @param {GridRowClassNameParams} params With all properties from [[GridRowClassNameParams]]. @@ -368,6 +370,7 @@ DataGridProRaw.propTypes = { * The grouping column used by the tree data. */ groupingColDef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + hasChildren: PropTypes.func, /** * If `true`, enables the data grid filtering on header feature. * @default false @@ -670,6 +673,11 @@ DataGridProRaw.propTypes = { * @param {GridCallbackDetails} details Additional details for this callback. */ onRowClick: PropTypes.func, + /** + * Callback fired when the row count has changed. + * @param {number} count Updated row count. + */ + onRowCountChange: PropTypes.func, /** * Callback fired when a double click event comes from a row container element. * @param {GridRowParams} params With all properties from [[RowParams]]. @@ -813,8 +821,9 @@ DataGridProRaw.propTypes = { rowReordering: PropTypes.bool, /** * Set of rows of type [[GridRowsProp]]. + * @default [] */ - rows: PropTypes.arrayOf(PropTypes.object).isRequired, + rows: PropTypes.array, /** * If `false`, the row selection mode is disabled. * @default true @@ -912,4 +921,13 @@ DataGridProRaw.propTypes = { * @default false */ treeData: PropTypes.bool, + unstable_dataSource: PropTypes.shape({ + getRows: PropTypes.func.isRequired, + updateRow: PropTypes.func, + }), + unstable_dataSourceCache: PropTypes.shape({ + get: PropTypes.func.isRequired, + invalidate: PropTypes.func.isRequired, + set: PropTypes.func.isRequired, + }), } as any; diff --git a/packages/x-data-grid-pro/src/DataGridPro/index.ts b/packages/x-data-grid-pro/src/DataGridPro/index.ts index 9a2f9455f863..fadddecfe7fc 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/index.ts +++ b/packages/x-data-grid-pro/src/DataGridPro/index.ts @@ -1,3 +1,3 @@ export * from './DataGrid'; export * from './DataGridPro'; -export { DATA_GRID_PRO_PROPS_DEFAULT_VALUES } from './useDataGridProProps'; +export { GET_DATA_GRID_PRO_PROPS_DEFAULT_VALUES } from './useDataGridProProps'; diff --git a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx index b89f14923492..fe11bb8bbdc8 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx @@ -60,6 +60,7 @@ import { } from '../hooks/features/columnResize/useGridColumnResize'; import { useGridTreeData } from '../hooks/features/treeData/useGridTreeData'; import { useGridTreeDataPreProcessors } from '../hooks/features/treeData/useGridTreeDataPreProcessors'; +import { useGridServerSideTreeDataPreProcessors } from '../hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors'; import { useGridColumnPinning, columnPinningStateInitializer, @@ -79,6 +80,7 @@ import { rowPinningStateInitializer, } from '../hooks/features/rowPinning/useGridRowPinning'; import { useGridRowPinningPreProcessors } from '../hooks/features/rowPinning/useGridRowPinningPreProcessors'; +import { useGridDataSource } from '../hooks/features/serverSideData/useGridDataSource'; export const useDataGridProComponent = ( inputApiRef: React.MutableRefObject | undefined, @@ -92,6 +94,7 @@ export const useDataGridProComponent = ( useGridRowSelectionPreProcessors(apiRef, props); useGridRowReorderPreProcessors(apiRef, props); useGridTreeDataPreProcessors(apiRef, props); + useGridServerSideTreeDataPreProcessors(apiRef, props); useGridLazyLoaderPreProcessors(apiRef, props); useGridRowPinningPreProcessors(apiRef); useGridDetailPanelPreProcessors(apiRef, props); @@ -159,6 +162,7 @@ export const useDataGridProComponent = ( useGridEvents(apiRef, props); useGridStatePersistence(apiRef); useGridVirtualization(apiRef, props); + useGridDataSource(apiRef, props); return apiRef; }; diff --git a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts index 33791fca9b48..c10c6ff6f491 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts +++ b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts @@ -14,10 +14,14 @@ import { import { GridProSlotsComponent } from '../models'; import { DATA_GRID_PRO_DEFAULT_SLOTS_COMPONENTS } from '../constants/dataGridProDefaultSlotsComponents'; +interface GetDataGridProPropsDefaultValues extends DataGridProProps {} + /** * The default values of `DataGridProPropsWithDefaultValue` to inject in the props of DataGridPro. */ -export const DATA_GRID_PRO_PROPS_DEFAULT_VALUES: DataGridProPropsWithDefaultValue = { +export const GET_DATA_GRID_PRO_PROPS_DEFAULT_VALUES: ( + themedProps: GetDataGridProPropsDefaultValues, +) => DataGridProPropsWithDefaultValue = (themedProps) => ({ ...DATA_GRID_PROPS_DEFAULT_VALUES, scrollEndThreshold: 80, treeData: false, @@ -29,10 +33,16 @@ export const DATA_GRID_PRO_PROPS_DEFAULT_VALUES: DataGridProPropsWithDefaultValu disableChildrenFiltering: false, disableChildrenSorting: false, rowReordering: false, - rowsLoadingMode: 'client', + rowsLoadingMode: themedProps.unstable_dataSource?.getRows ? 'server' : 'client', getDetailPanelHeight: () => 500, headerFilters: false, -}; + filterMode: themedProps.unstable_dataSource?.getRows ? 'server' : 'client', + sortingMode: themedProps.unstable_dataSource?.getRows ? 'server' : 'client', + paginationMode: themedProps.unstable_dataSource?.getRows ? 'server' : 'client', + filterDebounceMs: themedProps.unstable_dataSource?.getRows + ? 1000 + : DATA_GRID_PROPS_DEFAULT_VALUES.filterDebounceMs, +}); const defaultSlots = DATA_GRID_PRO_DEFAULT_SLOTS_COMPONENTS; @@ -61,7 +71,7 @@ export const useDataGridProProps = (inProps: DataGr return React.useMemo>( () => ({ - ...DATA_GRID_PRO_PROPS_DEFAULT_VALUES, + ...GET_DATA_GRID_PRO_PROPS_DEFAULT_VALUES(themedProps), ...themedProps, localeText, slots, diff --git a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx b/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx new file mode 100644 index 000000000000..8c16daefbd47 --- /dev/null +++ b/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx @@ -0,0 +1,203 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { unstable_composeClasses as composeClasses } from '@mui/utils'; +import { styled } from '@mui/system'; +import Box from '@mui/material/Box'; +import { + getDataGridUtilityClass, + GridRenderCellParams, + GridServerSideGroupNode, +} from '@mui/x-data-grid'; +import CircularProgress from '@mui/material/CircularProgress'; +import { useGridRootProps } from '../hooks/utils/useGridRootProps'; +import { useGridPrivateApiContext } from '../hooks/utils/useGridPrivateApiContext'; +import { DataGridProProcessedProps } from '../models/dataGridProProps'; +import { GridPrivateApiPro } from '../models/gridApiPro'; + +type OwnerState = { classes: DataGridProProcessedProps['classes'] }; + +const useUtilityClasses = (ownerState: OwnerState) => { + const { classes } = ownerState; + + const slots = { + root: ['treeDataGroupingCell'], + toggle: ['treeDataGroupingCellToggle'], + }; + + return composeClasses(slots, getDataGridUtilityClass, classes); +}; + +interface GridTreeDataGroupingCellProps + extends GridRenderCellParams { + hideDescendantCount?: boolean; + /** + * The cell offset multiplier used for calculating cell offset (`rowNode.depth * offsetMultiplier` px). + * @default 2 + */ + offsetMultiplier?: number; +} + +interface GridTreeDataGroupingCellIconProps + extends Pick { + descendantCount: number; +} + +const LoadingContainer = styled('div')({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + height: '100%', +}); + +function GridTreeDataGroupingCellIcon(props: GridTreeDataGroupingCellIconProps) { + const { rowNode, id, field, descendantCount } = props; + const apiRef = useGridPrivateApiContext() as React.MutableRefObject; + const rootProps = useGridRootProps(); + + const isServerSideNode = rowNode.isServerSide; + const isDataLoading = rowNode.isLoading; + + const handleClick = (event: React.MouseEvent) => { + if (isServerSideNode && !rowNode.childrenExpanded) { + // always fetch/get from cache the children when the node is collapsed + apiRef.current.fetchRowChildren(id); + } else { + apiRef.current.setRowChildrenExpansion(id, !rowNode.childrenExpanded); + } + apiRef.current.setCellFocus(id, field); + event.stopPropagation(); // TODO remove event.stopPropagation + }; + + const Icon = rowNode.childrenExpanded + ? rootProps.slots.treeDataCollapseIcon + : rootProps.slots.treeDataExpandIcon; + + if (isDataLoading) { + return ( + + + + ); + } + return descendantCount > 0 || isServerSideNode ? ( + + + + ) : null; +} + +function GridServerSideTreeDataGroupingCell(props: GridTreeDataGroupingCellProps) { + const { id, field, formattedValue, rowNode, hideDescendantCount, offsetMultiplier = 2 } = props; + + const rootProps = useGridRootProps(); + const apiRef = useGridPrivateApiContext(); + const row = apiRef.current.getRow(rowNode.id); + const ownerState: OwnerState = { classes: rootProps.classes }; + const classes = useUtilityClasses(ownerState); + const descendantCount = rootProps.getChildrenCount?.(row) ?? 0; + + return ( + +
+ +
+ + {formattedValue === undefined ? rowNode.groupingKey : formattedValue} + {!hideDescendantCount && descendantCount > 0 ? ` (${descendantCount})` : ''} + +
+ ); +} + +GridServerSideTreeDataGroupingCell.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "yarn proptypes" | + // ---------------------------------------------------------------------- + /** + * GridApi that let you manipulate the grid. + */ + api: PropTypes.object.isRequired, + /** + * The mode of the cell. + */ + cellMode: PropTypes.oneOf(['edit', 'view']).isRequired, + /** + * The column of the row that the current cell belongs to. + */ + colDef: PropTypes.object.isRequired, + /** + * The column field of the cell that triggered the event. + */ + field: PropTypes.string.isRequired, + /** + * A ref allowing to set imperative focus. + * It can be passed to the element that should receive focus. + * @ignore - do not document. + */ + focusElementRef: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.shape({ + current: PropTypes.shape({ + focus: PropTypes.func.isRequired, + }), + }), + ]), + /** + * The cell value formatted with the column valueFormatter. + */ + formattedValue: PropTypes.any, + /** + * If true, the cell is the active element. + */ + hasFocus: PropTypes.bool.isRequired, + hideDescendantCount: PropTypes.bool, + /** + * The grid row id. + */ + id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, + /** + * If true, the cell is editable. + */ + isEditable: PropTypes.bool, + /** + * The cell offset multiplier used for calculating cell offset (`rowNode.depth * offsetMultiplier` px). + * @default 2 + */ + offsetMultiplier: PropTypes.number, + /** + * The row model of the row that the current cell belongs to. + */ + row: PropTypes.any.isRequired, + /** + * The node of the row that the current cell belongs to. + */ + rowNode: PropTypes.object.isRequired, + /** + * the tabIndex value. + */ + tabIndex: PropTypes.oneOf([-1, 0]).isRequired, + /** + * The cell value. + * If the column has `valueGetter`, use `params.row` to directly access the fields. + */ + value: PropTypes.any, +} as any; + +export { GridServerSideTreeDataGroupingCell }; 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 2ad8b18e908e..e144e4cf6297 100644 --- a/packages/x-data-grid-pro/src/hooks/features/index.ts +++ b/packages/x-data-grid-pro/src/hooks/features/index.ts @@ -6,3 +6,4 @@ export * from './rowReorder'; export * from './treeData'; export * from './detailPanel'; export * from './rowPinning'; +export * from './serverSideData/dataSourceApi'; diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/dataSourceApi.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/dataSourceApi.ts new file mode 100644 index 000000000000..97deadf0830f --- /dev/null +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/dataSourceApi.ts @@ -0,0 +1,24 @@ +import { GridRowId } from '@mui/x-data-grid'; + +/** + * The dataSource API interface that is available in the grid [[apiRef]]. + */ +export interface GridDataSourceApi { + /** + * Initiates the fetch of the children of a row. + * @param {string} id The id of the rowNode belonging to the group to be fetched. + */ + fetchRowChildren: (id: GridRowId) => void; + /** + * Set the loading state of a row. + * @param {string} id The id of the rowNode. + * @param {boolean} loading The loading state to set. + */ + setRowLoading: (id: GridRowId, loading: boolean) => void; + /** + * Set the fetched children state of a row. + * @param {string} id The id of the rowNode. + * @param {boolean} childrenFetched The children to set. + */ + setChildrenFetched: (id: GridRowId, childrenFetched: boolean) => void; +} diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts new file mode 100644 index 000000000000..809751eccc47 --- /dev/null +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts @@ -0,0 +1,261 @@ +import * as React from 'react'; +import { + gridPaginationModelSelector, + gridFilterModelSelector, + gridSortModelSelector, + useGridApiEventHandler, + GridPaginationModel, + gridRowsLoadingSelector, + useGridApiMethod, + GridServerSideGroupNode, +} from '@mui/x-data-grid'; +import { GridPrivateApiPro } from '../../../models/gridApiPro'; +import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; +import { + GridGetRowsParams, + GridGetRowsResponse, + GridDataSource, + GridDataSourceCache, +} from '../../../models/gridDataSource'; +import { GridDataSourceApi } from './dataSourceApi'; + +const computeStartEnd = (paginationModel: GridPaginationModel) => { + const start = paginationModel.page * paginationModel.pageSize; + const end = start + paginationModel.pageSize - 1; + return { start, end }; +}; + +const noop = () => undefined; + +const defaultCache: GridDataSourceCache = { + // TODO: Implement an internal cache + set: noop, + get: noop, + invalidate: noop, +}; + +const getQueryKey = (params: GridGetRowsParams) => { + return [params.paginationModel, params.sortModel, params.filterModel, params.groupKeys]; +}; + +const fetchRowsWithError = async ( + getRows: GridDataSource['getRows'], + inputParams: GridGetRowsParams, +) => { + try { + const getRowsResponse = await getRows(inputParams); + return getRowsResponse; + } catch (error) { + throw new Error( + `MUI X: Error in fetching rows for the input params: ${JSON.stringify(inputParams)}`, + ); + } +}; + +const runIfServerMode = (modeProp: 'server' | 'client', fn: Function) => () => { + if (modeProp === 'server') { + fn(); + } +}; + +export const useGridDataSource = ( + privateApiRef: React.MutableRefObject, + props: Pick< + DataGridProProcessedProps, + | 'unstable_dataSource' + | 'sortingMode' + | 'filterMode' + | 'paginationMode' + | 'treeData' + | 'unstable_dataSourceCache' + >, +): void => { + const cache = React.useRef( + props.unstable_dataSourceCache || defaultCache, + ).current; + + const getInputParams = React.useCallback( + (additionalParams?: Partial): GridGetRowsParams => { + const paginationModel = gridPaginationModelSelector(privateApiRef); + + // const otherParams = privateApiRef.current.unstable_applyPipeProcessors('getRowsParams', {}); + return { + groupKeys: [], + paginationModel, + sortModel: gridSortModelSelector(privateApiRef), + filterModel: gridFilterModelSelector(privateApiRef), + ...computeStartEnd(paginationModel), + ...additionalParams, + }; + }, + [privateApiRef], + ); + + const fetchTopLevelRows = React.useCallback(async () => { + const getRows = props.unstable_dataSource?.getRows; + if (!getRows) { + return; + } + const inputParams = getInputParams(); + const cachedData = cache.get(getQueryKey(inputParams)) as GridGetRowsResponse | undefined; + if (cachedData) { + const rows = cachedData.rows; + privateApiRef.current.caches.groupKeys = []; + privateApiRef.current.setRows(rows); + if (cachedData.rowCount) { + privateApiRef.current.setRowCount(cachedData.rowCount); + } + } else { + const isLoading = gridRowsLoadingSelector(privateApiRef); + if (!isLoading) { + privateApiRef.current.setLoading(true); + } + + const getRowsResponse = await fetchRowsWithError(getRows, inputParams); + // TODO: Add respective events + // @ts-expect-error + privateApiRef.current.publishEvent('loadData', { + params: inputParams, + response: getRowsResponse, + }); + const queryKey = getQueryKey(inputParams); + cache.set(queryKey, getRowsResponse); + if (getRowsResponse.rowCount) { + privateApiRef.current.setRowCount(getRowsResponse.rowCount); + } + privateApiRef.current.caches.groupKeys = []; + privateApiRef.current.setRows(getRowsResponse.rows); + privateApiRef.current.setLoading(false); + // TODO: handle cursor based pagination + } + }, [cache, getInputParams, privateApiRef, props.unstable_dataSource?.getRows]); + + const fetchRowChildren = React.useCallback( + async (id) => { + if (!props.treeData) { + return; + } + const getRows = props.unstable_dataSource?.getRows; + if (!getRows) { + return; + } + + const rowNode = privateApiRef.current.getRowNode(id) as GridServerSideGroupNode; + if (!rowNode) { + return; + } + const inputParams = getInputParams({ groupKeys: rowNode.path }); + + const cachedData = cache.get(getQueryKey(inputParams)) as GridGetRowsResponse | undefined; + + if (cachedData) { + const rows = cachedData.rows; + privateApiRef.current.caches.groupKeys = rowNode.path; + privateApiRef.current.updateRows(rows); + if (cachedData.rowCount) { + privateApiRef.current.setRowCount(cachedData.rowCount); + } + privateApiRef.current.setRowChildrenExpansion(id, true); + privateApiRef.current.setChildrenFetched(id, true); + } else { + const isLoading = rowNode.isLoading; + if (!isLoading) { + privateApiRef.current.setRowLoading(id, true); + } + const getRowsResponse = await fetchRowsWithError(getRows, inputParams); + const queryKey = getQueryKey(inputParams); + cache.set(queryKey, getRowsResponse); + if (getRowsResponse.rowCount) { + privateApiRef.current.setRowCount(getRowsResponse.rowCount); + } + privateApiRef.current.caches.groupKeys = rowNode.path; + privateApiRef.current.updateRows(getRowsResponse.rows); + privateApiRef.current.setRowChildrenExpansion(id, true); + privateApiRef.current.setChildrenFetched(id, true); + privateApiRef.current.setRowLoading(id, false); + } + }, + [cache, getInputParams, privateApiRef, props.treeData, props.unstable_dataSource?.getRows], + ); + + const setRowLoading = React.useCallback( + (id, isLoading) => { + const currentNode = privateApiRef.current.getRowNode(id) as GridServerSideGroupNode; + if (!currentNode) { + throw new Error(`MUI: No row with id #${id} found`); + } + + const newNode: GridServerSideGroupNode = { ...currentNode, isLoading }; + privateApiRef.current.setState((state) => { + return { + ...state, + rows: { + ...state.rows, + tree: { ...state.rows.tree, [id]: newNode }, + }, + }; + }); + }, + [privateApiRef], + ); + + const setChildrenFetched = React.useCallback( + (id, childrenFetched) => { + const currentNode = privateApiRef.current.getRowNode(id) as GridServerSideGroupNode; + if (!currentNode) { + throw new Error(`MUI: No row with id #${id} found`); + } + + const newNode: GridServerSideGroupNode = { ...currentNode, childrenFetched }; + privateApiRef.current.setState((state) => { + return { + ...state, + rows: { + ...state.rows, + tree: { ...state.rows.tree, [id]: newNode }, + }, + }; + }); + }, + [privateApiRef], + ); + + const dataSourceApi: GridDataSourceApi = { + fetchRowChildren, + setRowLoading, + setChildrenFetched, + }; + + useGridApiMethod(privateApiRef, dataSourceApi, 'public'); + + /* + * EVENTS + */ + useGridApiEventHandler( + privateApiRef, + 'sortModelChange', + runIfServerMode(props.sortingMode, fetchTopLevelRows), + ); + useGridApiEventHandler( + privateApiRef, + 'filterModelChange', + runIfServerMode(props.filterMode, fetchTopLevelRows), + ); + useGridApiEventHandler( + privateApiRef, + 'paginationModelChange', + runIfServerMode(props.paginationMode, fetchTopLevelRows), + ); + + /* + * EFFECTS + */ + const isFirstRender = React.useRef(true); + React.useEffect(() => { + if (isFirstRender.current) { + isFirstRender.current = false; + } else { + fetchTopLevelRows(); + } + }, [props.unstable_dataSource, privateApiRef, fetchTopLevelRows]); +}; diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx new file mode 100644 index 000000000000..21649fc6c018 --- /dev/null +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx @@ -0,0 +1,261 @@ +import * as React from 'react'; +import { + gridRowTreeSelector, + useFirstRender, + GridColDef, + GridRenderCellParams, + GridServerSideGroupNode, + GridRowId, + GRID_CHECKBOX_SELECTION_FIELD, +} from '@mui/x-data-grid'; +import { + GridPipeProcessor, + GridStrategyProcessor, + useGridRegisterPipeProcessor, + useGridRegisterStrategyProcessor, +} from '@mui/x-data-grid/internals'; +import { + GRID_TREE_DATA_GROUPING_COL_DEF, + GRID_TREE_DATA_GROUPING_COL_DEF_FORCED_PROPERTIES, +} from '../treeData/gridTreeDataGroupColDef'; +import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; +import { skipFiltering } from './utils'; +import { GridPrivateApiPro } from '../../../models/gridApiPro'; +import { + GridGroupingColDefOverride, + GridGroupingColDefOverrideParams, +} from '../../../models/gridGroupingColDefOverride'; +import { GridServerSideTreeDataGroupingCell } from '../../../components/GridServerSideTreeDataGroupingCell'; +import { createRowTree } from '../../../utils/tree/createRowTree'; +import { + GridTreePathDuplicateHandler, + RowTreeBuilderGroupingCriterion, +} from '../../../utils/tree/models'; +import { sortRowTree } from '../../../utils/tree/sortRowTree'; +import { updateRowTree } from '../../../utils/tree/updateRowTree'; +import { getVisibleRowsLookup } from '../../../utils/tree/utils'; + +const SERVER_SIDE_TREE_DATA_STRATEGY = 'serverSideTreeData'; + +export const useGridServerSideTreeDataPreProcessors = ( + privateApiRef: React.MutableRefObject, + props: Pick< + DataGridProProcessedProps, + | 'treeData' + | 'groupingColDef' + | 'getGroupKey' + | 'disableChildrenSorting' + | 'disableChildrenFiltering' + | 'defaultGroupingExpansionDepth' + | 'isGroupExpandedByDefault' + | 'unstable_dataSource' + | 'hasChildren' + >, +) => { + const setStrategyAvailability = React.useCallback(() => { + privateApiRef.current.setStrategyAvailability( + 'rowTree', + SERVER_SIDE_TREE_DATA_STRATEGY, + props.treeData && props.unstable_dataSource ? () => true : () => false, + ); + }, [privateApiRef, props.treeData, props.unstable_dataSource]); + + const getGroupingColDef = React.useCallback(() => { + const groupingColDefProp = props.groupingColDef; + + let colDefOverride: GridGroupingColDefOverride | null | undefined; + if (typeof groupingColDefProp === 'function') { + const params: GridGroupingColDefOverrideParams = { + groupingName: SERVER_SIDE_TREE_DATA_STRATEGY, + fields: [], + }; + + colDefOverride = groupingColDefProp(params); + } else { + colDefOverride = groupingColDefProp; + } + + const { hideDescendantCount, ...colDefOverrideProperties } = colDefOverride ?? {}; + + const commonProperties: Omit = { + ...GRID_TREE_DATA_GROUPING_COL_DEF, + renderCell: (params) => ( + )} + hideDescendantCount={hideDescendantCount} + /> + ), + headerName: privateApiRef.current.getLocaleText('treeDataGroupingHeaderName'), + }; + + return { + ...commonProperties, + ...colDefOverrideProperties, + ...GRID_TREE_DATA_GROUPING_COL_DEF_FORCED_PROPERTIES, + }; + }, [privateApiRef, props.groupingColDef]); + + const updateGroupingColumn = React.useCallback>( + (columnsState) => { + const groupingColDefField = GRID_TREE_DATA_GROUPING_COL_DEF_FORCED_PROPERTIES.field; + + const shouldHaveGroupingColumn = props.treeData; + const prevGroupingColumn = columnsState.lookup[groupingColDefField]; + + if (shouldHaveGroupingColumn) { + const newGroupingColumn = getGroupingColDef(); + if (prevGroupingColumn) { + newGroupingColumn.width = prevGroupingColumn.width; + newGroupingColumn.flex = prevGroupingColumn.flex; + } + columnsState.lookup[groupingColDefField] = newGroupingColumn; + if (prevGroupingColumn == null) { + const index = columnsState.orderedFields[0] === GRID_CHECKBOX_SELECTION_FIELD ? 1 : 0; + columnsState.orderedFields = [ + ...columnsState.orderedFields.slice(0, index), + groupingColDefField, + ...columnsState.orderedFields.slice(index), + ]; + } + } else if (!shouldHaveGroupingColumn && prevGroupingColumn) { + delete columnsState.lookup[groupingColDefField]; + columnsState.orderedFields = columnsState.orderedFields.filter( + (field) => field !== groupingColDefField, + ); + } + + return columnsState; + }, + [props.treeData, getGroupingColDef], + ); + + const createRowTreeForTreeData = React.useCallback>( + (params) => { + if (!props.getGroupKey) { + throw new Error('MUI X: No `getGroupKey` prop provided.'); + } + + if (!props.hasChildren) { + throw new Error('MUI X: No `hasChildren` prop provided.'); + } + + const parentPath = privateApiRef.current.caches.groupKeys || []; + + const getRowTreeBuilderNode = (rowId: GridRowId) => ({ + id: rowId, + path: [...parentPath, props.getGroupKey!(params.dataRowIdToModelLookup[rowId])].map( + (key): RowTreeBuilderGroupingCriterion => ({ key, field: null }), + ), + hasServerChildren: props.hasChildren!(params.dataRowIdToModelLookup[rowId]), + }); + + const onDuplicatePath: GridTreePathDuplicateHandler = (firstId, secondId, path) => { + throw new Error( + [ + 'MUI X: The values returned by `getGroupKey` for all the siblings should be unique.', + `The rows with id #${firstId} and #${secondId} have the same.`, + `Path: ${JSON.stringify(path.map((step) => step.key))}.`, + ].join('\n'), + ); + }; + + if (params.updates.type === 'full') { + return createRowTree({ + previousTree: params.previousTree, + nodes: params.updates.rows.map(getRowTreeBuilderNode), + defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, + isGroupExpandedByDefault: props.isGroupExpandedByDefault, + groupingName: SERVER_SIDE_TREE_DATA_STRATEGY, + onDuplicatePath, + }); + } + + return updateRowTree({ + nodes: { + inserted: params.updates.actions.insert.map(getRowTreeBuilderNode), + modified: params.updates.actions.modify.map(getRowTreeBuilderNode), + removed: params.updates.actions.remove, + }, + previousTree: params.previousTree!, + previousTreeDepth: params.previousTreeDepths!, + defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, + isGroupExpandedByDefault: props.isGroupExpandedByDefault, + groupingName: SERVER_SIDE_TREE_DATA_STRATEGY, + }); + }, + [ + props.getGroupKey, + props.hasChildren, + props.defaultGroupingExpansionDepth, + props.isGroupExpandedByDefault, + privateApiRef, + ], + ); + + const filterRows = React.useCallback>(() => { + const rowTree = gridRowTreeSelector(privateApiRef); + + return skipFiltering({ + rowTree, + }); + }, [privateApiRef]); + + const sortRows = React.useCallback>( + (params) => { + const rowTree = gridRowTreeSelector(privateApiRef); + + return sortRowTree({ + rowTree, + sortRowList: params.sortRowList, + disableChildrenSorting: props.disableChildrenSorting, + shouldRenderGroupBelowLeaves: false, + }); + }, + [privateApiRef, props.disableChildrenSorting], + ); + + useGridRegisterPipeProcessor(privateApiRef, 'hydrateColumns', updateGroupingColumn); + useGridRegisterStrategyProcessor( + privateApiRef, + SERVER_SIDE_TREE_DATA_STRATEGY, + 'rowTreeCreation', + createRowTreeForTreeData, + ); + useGridRegisterStrategyProcessor( + privateApiRef, + SERVER_SIDE_TREE_DATA_STRATEGY, + 'filtering', + filterRows, + ); + useGridRegisterStrategyProcessor( + privateApiRef, + SERVER_SIDE_TREE_DATA_STRATEGY, + 'sorting', + sortRows, + ); + useGridRegisterStrategyProcessor( + privateApiRef, + SERVER_SIDE_TREE_DATA_STRATEGY, + 'visibleRowsLookupCreation', + getVisibleRowsLookup, + ); + + /** + * 1ST RENDER + */ + useFirstRender(() => { + setStrategyAvailability(); + }); + + /** + * EFFECTS + */ + const isFirstRender = React.useRef(true); + React.useEffect(() => { + if (!isFirstRender.current) { + setStrategyAvailability(); + } else { + isFirstRender.current = false; + } + }, [setStrategyAvailability]); +}; diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/utils.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/utils.ts new file mode 100644 index 000000000000..7423cd731673 --- /dev/null +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/utils.ts @@ -0,0 +1,18 @@ +import { GridRowId, GridRowTreeConfig } from '@mui/x-data-grid'; + +export const skipFiltering = (params: { rowTree: GridRowTreeConfig }) => { + const { rowTree } = params; + const filteredRowsLookup: Record = {}; + const filteredDescendantCountLookup: Record = {}; + + const nodes = Object.values(rowTree); + for (let i = 0; i < nodes.length; i += 1) { + const node: any = nodes[i]; + filteredRowsLookup[node.id] = true; + } + + return { + filteredRowsLookup, + filteredDescendantCountLookup, + }; +}; 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 be47c0bd6bb1..ec9227a45156 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 @@ -46,15 +46,16 @@ export const useGridTreeDataPreProcessors = ( | 'disableChildrenFiltering' | 'defaultGroupingExpansionDepth' | 'isGroupExpandedByDefault' + | 'unstable_dataSource' >, ) => { const setStrategyAvailability = React.useCallback(() => { privateApiRef.current.setStrategyAvailability( 'rowTree', TREE_DATA_STRATEGY, - props.treeData ? () => true : () => false, + props.treeData && !props.unstable_dataSource ? () => true : () => false, ); - }, [privateApiRef, props.treeData]); + }, [privateApiRef, props.treeData, props.unstable_dataSource]); const getGroupingColDef = React.useCallback(() => { const groupingColDefProp = props.groupingColDef; diff --git a/packages/x-data-grid-pro/src/internals/index.ts b/packages/x-data-grid-pro/src/internals/index.ts index 3f8352dd07ed..5d88bfc315bd 100644 --- a/packages/x-data-grid-pro/src/internals/index.ts +++ b/packages/x-data-grid-pro/src/internals/index.ts @@ -19,6 +19,7 @@ export { useGridColumnReorder, columnReorderStateInitializer, } from '../hooks/features/columnReorder/useGridColumnReorder'; +export { useGridServerSideTreeDataPreProcessors } from '../hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors'; export { useGridDetailPanel, detailPanelStateInitializer, diff --git a/packages/x-data-grid-pro/src/internals/propValidation.ts b/packages/x-data-grid-pro/src/internals/propValidation.ts index 8651c0fb8031..5876e9e38da8 100644 --- a/packages/x-data-grid-pro/src/internals/propValidation.ts +++ b/packages/x-data-grid-pro/src/internals/propValidation.ts @@ -11,6 +11,7 @@ export const propValidatorsDataGridPro: PropValidator (props) => (props.treeData && props.filterMode === 'server' && + !props.unstable_dataSource && 'MUI X: The `filterMode="server"` prop is not available when the `treeData` is enabled.') || undefined, (props) => @@ -18,4 +19,14 @@ export const propValidatorsDataGridPro: PropValidator props.checkboxSelectionVisibleOnly && 'MUI X: The `checkboxSelectionVisibleOnly` prop has no effect when the pagination is not enabled.') || undefined, + (props) => + (!props.unstable_dataSource && + !props.rows && + 'MUI X: One of `rows` prop or `unstable_dataSource` prop must be passed for the Grid to work as expected.') || + undefined, + (props) => + (props.unstable_dataSource && + props.rows && + 'MUI X: The `rows` prop has no effect when the `unstable_dataSource` prop is passed.') || + undefined, ]; diff --git a/packages/x-data-grid-pro/src/models/dataGridProProps.ts b/packages/x-data-grid-pro/src/models/dataGridProProps.ts index ee279741b9ce..ce4439af0b9e 100644 --- a/packages/x-data-grid-pro/src/models/dataGridProProps.ts +++ b/packages/x-data-grid-pro/src/models/dataGridProProps.ts @@ -25,6 +25,7 @@ import { import { GridInitialStatePro } from './gridStatePro'; import { GridProSlotsComponent } from './gridProSlotsComponent'; import type { GridProSlotProps } from './gridProSlotProps'; +import type { GridDataSource, GridDataSourceCache } from './gridDataSource'; import type { GridAutosizeOptions } from '../hooks'; export interface GridExperimentalProFeatures extends GridExperimentalFeatures { @@ -154,11 +155,33 @@ export interface DataGridProPropsWithDefaultValue extends DataGridPropsWithDefau headerFilters: boolean; } +interface DataGridProDataSourceProps { + unstable_dataSource?: GridDataSource; + unstable_dataSourceCache?: GridDataSourceCache; + getGroupKey?: (row: GridValidRowModel) => string; + hasChildren?: (row: GridValidRowModel) => boolean; + getChildrenCount?: (row: GridValidRowModel) => number; +} + +interface DataGridProRegularProps { + /** + * Determines the path of a row in the tree data. + * For instance, a row with the path ["A", "B"] is the child of the row with the path ["A"]. + * Note that all paths must contain at least one element. + * @template R + * @param {R} row The row from which we want the path. + * @returns {string[]} The path to the row. + */ + getTreeDataPath?: (row: R) => string[]; +} + export interface DataGridProPropsWithoutDefaultValue extends Omit< - DataGridPropsWithoutDefaultValue, - 'initialState' | 'componentsProps' | 'slotProps' - > { + DataGridPropsWithoutDefaultValue, + 'initialState' | 'componentsProps' | 'slotProps' + >, + DataGridProRegularProps, + DataGridProDataSourceProps { /** * The ref object that allows grid manipulation. Can be instantiated with `useGridApiRef()`. */ @@ -178,15 +201,6 @@ export interface DataGridProPropsWithoutDefaultValue; - /** - * Determines the path of a row in the tree data. - * For instance, a row with the path ["A", "B"] is the child of the row with the path ["A"]. - * Note that all paths must contain at least one element. - * @template R - * @param {R} row The row from which we want the path. - * @returns {string[]} The path to the row. - */ - getTreeDataPath?: (row: R) => string[]; /** * Callback fired while a column is being resized. * @param {GridColumnResizeParams} params With all properties from [[GridColumnResizeParams]]. diff --git a/packages/x-data-grid-pro/src/models/gridApiPro.ts b/packages/x-data-grid-pro/src/models/gridApiPro.ts index 1ad793c49c50..14526d15fc33 100644 --- a/packages/x-data-grid-pro/src/models/gridApiPro.ts +++ b/packages/x-data-grid-pro/src/models/gridApiPro.ts @@ -12,6 +12,7 @@ import type { GridDetailPanelApi, GridRowPinningApi, GridDetailPanelPrivateApi, + GridDataSourceApi, } from '../hooks'; import type { DataGridProProcessedProps } from './dataGridProProps'; @@ -25,6 +26,7 @@ export interface GridApiPro GridColumnResizeApi, GridDetailPanelApi, GridRowPinningApi, + GridDataSourceApi, // APIs that are private in Community plan, but public in Pro and Premium plans GridRowMultiSelectionApi, GridColumnReorderApi {} diff --git a/packages/x-data-grid-pro/src/models/dataSource.ts b/packages/x-data-grid-pro/src/models/gridDataSource.ts similarity index 84% rename from packages/x-data-grid-pro/src/models/dataSource.ts rename to packages/x-data-grid-pro/src/models/gridDataSource.ts index 3818c77a7201..cd2f7dc52253 100644 --- a/packages/x-data-grid-pro/src/models/dataSource.ts +++ b/packages/x-data-grid-pro/src/models/gridDataSource.ts @@ -6,7 +6,7 @@ import { GridPaginationModel, } from '@mui/x-data-grid'; -interface GetRowsParams { +export interface GridGetRowsParams { sortModel: GridSortModel; filterModel: GridFilterModel; /** @@ -26,14 +26,14 @@ interface GetRowsParams { * `getGroupKey` prop must be implemented to use this. * Useful for `treeData` and `rowGrouping` only. */ - groupKeys: string[]; + groupKeys?: string[]; /** * List of grouped columns (only applicable with `rowGrouping`). */ - groupFields: GridColDef['field'][]; + groupFields?: GridColDef['field'][]; } -interface GetRowsResponse { +export interface GridGetRowsResponse { rows: GridRowModel[]; /** * To reflect updates in total `rowCount` (optional). @@ -53,12 +53,13 @@ interface GetRowsResponse { * Thus `hasNextPage` will come into play to check more rows are available to fetch after the number becomes >= provided `rowCount` */ pageInfo?: { + nextCursor?: number | string; hasNextPage?: boolean; truncated?: number; }; } -export interface DataSource { +export interface GridDataSource { /** * Fetcher Functions: * - `getRows` is required @@ -67,6 +68,12 @@ export interface DataSource { * `getRows` will be used by the grid to fetch data for the current page or children for the current parent group. * It may return a `rowCount` to update the total count of rows in the grid along with the optional `pageInfo`. */ - getRows(params: GetRowsParams): Promise; + getRows(params: GridGetRowsParams): Promise; updateRow?(rows: GridRowModel): Promise; } + +export interface GridDataSourceCache { + set: (key: any[], value: unknown) => void; + get: (key: any[]) => unknown; + invalidate: (key?: any[]) => void; +} diff --git a/packages/x-data-grid-pro/src/models/index.ts b/packages/x-data-grid-pro/src/models/index.ts index 36deff5c944b..e530e16b5812 100644 --- a/packages/x-data-grid-pro/src/models/index.ts +++ b/packages/x-data-grid-pro/src/models/index.ts @@ -5,3 +5,4 @@ export * from './gridRowOrderChangeParams'; export * from './gridFetchRowsParams'; export * from './gridProSlotsComponent'; export * from './gridProIconSlotsComponent'; +export * from './gridDataSource'; diff --git a/packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx index 8c919672114a..283be6d47e95 100644 --- a/packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx @@ -6,7 +6,7 @@ import { GridLogicOperator, GridPreferencePanelsValue, GridRowModel, - DATA_GRID_PRO_PROPS_DEFAULT_VALUES, + GET_DATA_GRID_PRO_PROPS_DEFAULT_VALUES, useGridApiRef, DataGridPro, GetColumnForNewFilterArgs, @@ -22,7 +22,9 @@ import * as React from 'react'; import { spy } from 'sinon'; import { getColumnHeaderCell, getColumnValues, getSelectInput, grid } from 'test/utils/helperFn'; -const SUBMIT_FILTER_STROKE_TIME = DATA_GRID_PRO_PROPS_DEFAULT_VALUES.filterDebounceMs; +const SUBMIT_FILTER_STROKE_TIME = GET_DATA_GRID_PRO_PROPS_DEFAULT_VALUES( + {} as any, +).filterDebounceMs; const isJSDOM = /jsdom/.test(window.navigator.userAgent); diff --git a/packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx index ece96b663e62..9cd580be46ef 100644 --- a/packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx @@ -26,8 +26,12 @@ import { useBasicDemoData, getBasicGridData } from '@mui/x-data-grid-generator'; const isJSDOM = /jsdom/.test(window.navigator.userAgent); +interface BaselineProps extends DataGridProProps { + rows: GridRowModel[]; +} + describe(' - Rows', () => { - let baselineProps: DataGridProProps; + let baselineProps: BaselineProps; const { clock, render } = createRenderer({ clock: 'fake' }); diff --git a/packages/x-data-grid-pro/src/utils/tree/createRowTree.ts b/packages/x-data-grid-pro/src/utils/tree/createRowTree.ts index 09675aa21548..0ad4bd8d4e99 100644 --- a/packages/x-data-grid-pro/src/utils/tree/createRowTree.ts +++ b/packages/x-data-grid-pro/src/utils/tree/createRowTree.ts @@ -32,6 +32,7 @@ export const createRowTree = (params: CreateRowTreeParams): GridRowTreeCreationV previousTree: params.previousTree, id: node.id, path: node.path, + hasServerChildren: node.hasServerChildren, onDuplicatePath: params.onDuplicatePath, treeDepths, isGroupExpandedByDefault: params.isGroupExpandedByDefault, diff --git a/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts b/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts index a79fbdcc759e..c851a86da8a6 100644 --- a/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts +++ b/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts @@ -4,6 +4,7 @@ import { GridLeafNode, GridRowId, GridRowTreeConfig, + GridServerSideGroupNode, } from '@mui/x-data-grid'; import { GridTreeDepths, GridRowTreeUpdatedGroupsManager } from '@mui/x-data-grid/internals'; import { @@ -57,6 +58,7 @@ interface InsertDataRowInTreeParams { onDuplicatePath?: GridTreePathDuplicateHandler; isGroupExpandedByDefault?: DataGridProProps['isGroupExpandedByDefault']; defaultGroupingExpansionDepth: number; + hasServerChildren?: boolean; } /** @@ -75,6 +77,7 @@ export const insertDataRowInTree = ({ onDuplicatePath, isGroupExpandedByDefault, defaultGroupingExpansionDepth, + hasServerChildren, }: InsertDataRowInTreeParams) => { let parentNodeId = GRID_ROOT_GROUP_ID; @@ -92,17 +95,37 @@ export const insertDataRowInTree = ({ // If no node matches the full path, // We create a leaf node for the data row. if (existingNodeIdWithPartialPath == null) { - const leafNode: GridLeafNode = { - type: 'leaf', - id, - depth, - parent: parentNodeId, - groupingKey: key, - }; + let node: GridLeafNode | GridServerSideGroupNode; + if (hasServerChildren) { + node = { + type: 'group', + id, + parent: parentNodeId, + path: path.map((step) => step.key as string), + depth, + isAutoGenerated: false, + groupingKey: key, + groupingField: field, + children: [], + childrenFromPath: {}, + childrenExpanded: false, + isLoading: false, + isServerSide: true, + childrenFetched: false, + }; + } else { + node = { + type: 'leaf', + id, + depth, + parent: parentNodeId, + groupingKey: key, + }; + } updatedGroupsManager?.addAction(parentNodeId, 'insertChildren'); - insertNodeInTree(leafNode, tree, treeDepths, previousTree); + insertNodeInTree(node, tree, treeDepths, previousTree); } else { const existingNodeWithPartialPath = tree[existingNodeIdWithPartialPath]; diff --git a/packages/x-data-grid-pro/src/utils/tree/models.ts b/packages/x-data-grid-pro/src/utils/tree/models.ts index 5eef120e37ff..871d3fc86c97 100644 --- a/packages/x-data-grid-pro/src/utils/tree/models.ts +++ b/packages/x-data-grid-pro/src/utils/tree/models.ts @@ -8,6 +8,7 @@ export interface RowTreeBuilderGroupingCriterion { export interface RowTreeBuilderNode { id: GridRowId; path: RowTreeBuilderGroupingCriterion[]; + hasServerChildren?: boolean; } /** diff --git a/packages/x-data-grid-pro/src/utils/tree/updateRowTree.ts b/packages/x-data-grid-pro/src/utils/tree/updateRowTree.ts index ad87141ef647..b7f6f544768f 100644 --- a/packages/x-data-grid-pro/src/utils/tree/updateRowTree.ts +++ b/packages/x-data-grid-pro/src/utils/tree/updateRowTree.ts @@ -32,7 +32,7 @@ export const updateRowTree = (params: UpdateRowTreeParams): GridRowTreeCreationV const updatedGroupsManager = createUpdatedGroupsManager(); for (let i = 0; i < params.nodes.inserted.length; i += 1) { - const { id, path } = params.nodes.inserted[i]; + const { id, path, hasServerChildren } = params.nodes.inserted[i]; insertDataRowInTree({ previousTree: params.previousTree, @@ -41,6 +41,7 @@ export const updateRowTree = (params: UpdateRowTreeParams): GridRowTreeCreationV updatedGroupsManager, id, path, + hasServerChildren, onDuplicatePath: params.onDuplicatePath, isGroupExpandedByDefault: params.isGroupExpandedByDefault, defaultGroupingExpansionDepth: params.defaultGroupingExpansionDepth, @@ -59,7 +60,7 @@ export const updateRowTree = (params: UpdateRowTreeParams): GridRowTreeCreationV } for (let i = 0; i < params.nodes.modified.length; i += 1) { - const { id, path } = params.nodes.modified[i]; + const { id, path, hasServerChildren } = params.nodes.modified[i]; const pathInPreviousTree = getNodePathInTree({ tree, id }); const isInSameGroup = isDeepEqual(pathInPreviousTree, path); @@ -78,6 +79,7 @@ export const updateRowTree = (params: UpdateRowTreeParams): GridRowTreeCreationV updatedGroupsManager, id, path, + hasServerChildren, onDuplicatePath: params.onDuplicatePath, isGroupExpandedByDefault: params.isGroupExpandedByDefault, defaultGroupingExpansionDepth: params.defaultGroupingExpansionDepth, diff --git a/packages/x-data-grid/src/DataGrid/DataGrid.tsx b/packages/x-data-grid/src/DataGrid/DataGrid.tsx index a971d1e3fb38..6b49b495ba94 100644 --- a/packages/x-data-grid/src/DataGrid/DataGrid.tsx +++ b/packages/x-data-grid/src/DataGrid/DataGrid.tsx @@ -526,6 +526,11 @@ DataGridRaw.propTypes = { * @param {GridCallbackDetails} details Additional details for this callback. */ onRowClick: PropTypes.func, + /** + * Callback fired when the row count has changed. + * @param {number} count Updated row count. + */ + onRowCountChange: PropTypes.func, /** * Callback fired when a double click event comes from a row container element. * @param {GridRowParams} params With all properties from [[RowParams]]. @@ -635,8 +640,9 @@ DataGridRaw.propTypes = { rowPositionsDebounceMs: PropTypes.number, /** * Set of rows of type [[GridRowsProp]]. + * @default [] */ - rows: PropTypes.arrayOf(PropTypes.object).isRequired, + rows: PropTypes.array, /** * If `false`, the row selection mode is disabled. * @default true diff --git a/packages/x-data-grid/src/DataGrid/useDataGridProps.ts b/packages/x-data-grid/src/DataGrid/useDataGridProps.ts index 139c6151f83f..8a7fea9145c3 100644 --- a/packages/x-data-grid/src/DataGrid/useDataGridProps.ts +++ b/packages/x-data-grid/src/DataGrid/useDataGridProps.ts @@ -77,6 +77,7 @@ export const DATA_GRID_PROPS_DEFAULT_VALUES: DataGridPropsWithDefaultValues = { ignoreValueFormatterDuringExport: false, clipboardCopyCellDelimiter: '\t', rowPositionsDebounceMs: 166, + rows: [], }; const defaultSlots = DATA_GRID_DEFAULT_SLOTS_COMPONENTS; diff --git a/packages/x-data-grid/src/components/GridPagination.tsx b/packages/x-data-grid/src/components/GridPagination.tsx index 5b6e3f184ae8..974970deec0f 100644 --- a/packages/x-data-grid/src/components/GridPagination.tsx +++ b/packages/x-data-grid/src/components/GridPagination.tsx @@ -7,9 +7,10 @@ import { styled } from '@mui/material/styles'; import { useGridSelector } from '../hooks/utils/useGridSelector'; import { useGridApiContext } from '../hooks/utils/useGridApiContext'; import { useGridRootProps } from '../hooks/utils/useGridRootProps'; -import { gridFilteredTopLevelRowCountSelector } from '../hooks/features/filter'; - -import { gridPaginationModelSelector } from '../hooks/features/pagination/gridPaginationSelector'; +import { + gridPaginationModelSelector, + gridPaginationRowCountSelector, +} from '../hooks/features/pagination/gridPaginationSelector'; const GridPaginationRoot = styled(TablePagination)(({ theme }) => ({ [`& .${tablePaginationClasses.selectLabel}`]: { @@ -35,12 +36,7 @@ export const GridPagination = React.forwardRef rootProps.rowCount ?? visibleTopLevelRowCount ?? 0, - [rootProps.rowCount, visibleTopLevelRowCount], - ); + const rowCount = useGridSelector(apiRef, gridPaginationRowCountSelector) ?? 0; const lastPage = React.useMemo(() => { const calculatedValue = Math.ceil(rowCount / (paginationModel.pageSize || 1)) - 1; diff --git a/packages/x-data-grid/src/hooks/features/export/useGridPrintExport.tsx b/packages/x-data-grid/src/hooks/features/export/useGridPrintExport.tsx index 2c0ce7b9c433..5832f5f5f724 100644 --- a/packages/x-data-grid/src/hooks/features/export/useGridPrintExport.tsx +++ b/packages/x-data-grid/src/hooks/features/export/useGridPrintExport.tsx @@ -16,7 +16,7 @@ import { gridClasses } from '../../../constants/gridClasses'; import { useGridApiMethod } from '../../utils/useGridApiMethod'; import { gridRowsMetaSelector } from '../rows/gridRowsMetaSelector'; import { defaultGetRowsToExport, getColumnsToExport } from './utils'; -import { mergeStateWithPaginationModel } from '../pagination/useGridPagination'; +import { getDerivedPaginationModel } from '../pagination/useGridPaginationModel'; import { GridPipeProcessor, useGridRegisterPipeProcessor } from '../../core/pipeProcessing'; import { GridExportDisplayOptions, @@ -307,11 +307,18 @@ export const useGridPrintExport = ( page: 0, pageSize: visibleRowCount, }; - apiRef.current.updateControlState( - 'pagination', - // Using signature `DataGridPro` to allow more than 100 rows in the print export - mergeStateWithPaginationModel(visibleRowCount, 'DataGridPro', paginationModel), - ); + apiRef.current.setState((state) => ({ + ...state, + pagination: { + ...state.pagination, + paginationModel: getDerivedPaginationModel( + state.pagination, + 'DataGridPro', + paginationModel, + ), + }, + })); + // TODO: Verify if `#10045` still works as expected apiRef.current.forceUpdate(); } diff --git a/packages/x-data-grid/src/hooks/features/pagination/gridPaginationInterfaces.ts b/packages/x-data-grid/src/hooks/features/pagination/gridPaginationInterfaces.ts index 2506e67ba3e8..f0b334447bef 100644 --- a/packages/x-data-grid/src/hooks/features/pagination/gridPaginationInterfaces.ts +++ b/packages/x-data-grid/src/hooks/features/pagination/gridPaginationInterfaces.ts @@ -2,24 +2,28 @@ import { GridPaginationModel } from '../../../models/gridPaginationProps'; export interface GridPaginationState { paginationModel: GridPaginationModel; + rowCount: number; } export interface GridPaginationInitialState { paginationModel?: Partial; + rowCount?: number; } /** - * The pagination API interface that is available in the grid `apiRef`. + * The pagination model API interface that is available in the grid `apiRef`. */ -export interface GridPaginationApi { +export interface GridPaginationModelApi { /** * Sets the displayed page to the value given by `page`. * @param {number} page The new page number. + * @deprecated Use `setPaginationModel` instead. */ setPage: (page: number) => void; /** * Sets the number of displayed rows to the value given by `pageSize`. * @param {number} pageSize The new number of displayed rows. + * @deprecated Use `setPaginationModel` instead. */ setPageSize: (pageSize: number) => void; /** @@ -28,3 +32,14 @@ export interface GridPaginationApi { */ setPaginationModel: (model: GridPaginationModel) => void; } + +/** + * The pagination row count API interface that is available in the grid `apiRef`. + */ +export interface GridPaginationRowCountApi { + /** + * Sets the `rowCount` to a new value. + * @param {number} rowCount The new row count value. + */ + setRowCount: (rowCount: number) => void; +} diff --git a/packages/x-data-grid/src/hooks/features/pagination/gridPaginationSelector.ts b/packages/x-data-grid/src/hooks/features/pagination/gridPaginationSelector.ts index 5bd931769e0e..1b18fc92ad70 100644 --- a/packages/x-data-grid/src/hooks/features/pagination/gridPaginationSelector.ts +++ b/packages/x-data-grid/src/hooks/features/pagination/gridPaginationSelector.ts @@ -24,6 +24,15 @@ export const gridPaginationModelSelector = createSelector( (pagination) => pagination.paginationModel, ); +/** + * Get the row count + * @category Pagination + */ +export const gridPaginationRowCountSelector = createSelector( + gridPaginationSelector, + (pagination) => pagination.rowCount, +); + /** * Get the index of the page to render if the pagination is enabled * @category Pagination diff --git a/packages/x-data-grid/src/hooks/features/pagination/index.ts b/packages/x-data-grid/src/hooks/features/pagination/index.ts index f47e042c72ee..5c14da8edff3 100644 --- a/packages/x-data-grid/src/hooks/features/pagination/index.ts +++ b/packages/x-data-grid/src/hooks/features/pagination/index.ts @@ -1,6 +1,7 @@ export * from './gridPaginationSelector'; export type { - GridPaginationApi, + GridPaginationModelApi, + GridPaginationRowCountApi, GridPaginationState, GridPaginationInitialState, } from './gridPaginationInterfaces'; diff --git a/packages/x-data-grid/src/hooks/features/pagination/useGridPagination.ts b/packages/x-data-grid/src/hooks/features/pagination/useGridPagination.ts index 1f310902229e..9abbeab5301c 100644 --- a/packages/x-data-grid/src/hooks/features/pagination/useGridPagination.ts +++ b/packages/x-data-grid/src/hooks/features/pagination/useGridPagination.ts @@ -2,27 +2,13 @@ import * as React from 'react'; import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity'; import { DataGridProcessedProps } from '../../../models/props/DataGridProps'; import { GridStateInitializer } from '../../utils/useGridInitializeState'; -import { GridPaginationApi, GridPaginationState } from './gridPaginationInterfaces'; -import { GridEventListener } from '../../../models/events'; -import { GridPaginationModel } from '../../../models/gridPaginationProps'; -import { gridFilteredTopLevelRowCountSelector } from '../filter'; -import { gridDensityFactorSelector } from '../density'; -import { - useGridLogger, - useGridSelector, - useGridApiMethod, - useGridApiEventHandler, -} from '../../utils'; -import { GridPipeProcessor, useGridRegisterPipeProcessor } from '../../core/pipeProcessing'; -import { gridPaginationModelSelector } from './gridPaginationSelector'; + import { - getPageCount, - noRowCountInServerMode, - defaultPageSize, throwIfPageSizeExceedsTheLimit, getDefaultGridPaginationModel, - getValidPage, } from './gridPaginationUtils'; +import { useGridPaginationModel } from './useGridPaginationModel'; +import { useGridRowCount } from './useGridRowCount'; export const paginationStateInitializer: GridStateInitializer< Pick< @@ -37,257 +23,24 @@ export const paginationStateInitializer: GridStateInitializer< throwIfPageSizeExceedsTheLimit(paginationModel.pageSize, props.signature); + const rowCount = props.rowCount ?? props.initialState?.pagination?.rowCount; return { ...state, pagination: { paginationModel, + rowCount, }, }; }; -export const mergeStateWithPaginationModel = - ( - rowCount: number, - signature: DataGridProcessedProps['signature'], - paginationModelProp?: GridPaginationModel, - ) => - (paginationState: GridPaginationState) => { - let paginationModel = paginationState.paginationModel; - const pageSize = paginationModelProp?.pageSize ?? paginationModel.pageSize; - const pageCount = getPageCount(rowCount, pageSize); - - if ( - paginationModelProp && - (paginationModelProp?.page !== paginationModel.page || - paginationModelProp?.pageSize !== paginationModel.pageSize) - ) { - paginationModel = paginationModelProp; - } - - const validPage = getValidPage(paginationModel.page, pageCount); - if (validPage !== paginationModel.page) { - paginationModel = { ...paginationModel, page: validPage }; - } - - throwIfPageSizeExceedsTheLimit(paginationModel.pageSize, signature); - - return { paginationModel }; - }; - /** * @requires useGridFilter (state) * @requires useGridDimensions (event) - can be after */ export const useGridPagination = ( apiRef: React.MutableRefObject, - props: Pick< - DataGridProcessedProps, - | 'paginationModel' - | 'onPaginationModelChange' - | 'autoPageSize' - | 'rowCount' - | 'initialState' - | 'paginationMode' - | 'signature' - | 'rowHeight' - >, + props: DataGridProcessedProps, ) => { - const logger = useGridLogger(apiRef, 'useGridPagination'); - - const visibleTopLevelRowCount = useGridSelector(apiRef, gridFilteredTopLevelRowCountSelector); - const densityFactor = useGridSelector(apiRef, gridDensityFactorSelector); - const rowHeight = Math.floor(props.rowHeight * densityFactor); - - apiRef.current.registerControlState({ - stateId: 'pagination', - propModel: props.paginationModel, - propOnChange: props.onPaginationModelChange, - stateSelector: gridPaginationModelSelector, - changeEvent: 'paginationModelChange', - }); - - /** - * API METHODS - */ - const setPage = React.useCallback( - (page) => { - const currentModel = gridPaginationModelSelector(apiRef); - if (page === currentModel.page) { - return; - } - logger.debug(`Setting page to ${page}`); - apiRef.current.setPaginationModel({ page, pageSize: currentModel.pageSize }); - }, - [apiRef, logger], - ); - - const setPageSize = React.useCallback( - (pageSize) => { - const currentModel = gridPaginationModelSelector(apiRef); - if (pageSize === currentModel.pageSize) { - return; - } - - logger.debug(`Setting page size to ${pageSize}`); - apiRef.current.setPaginationModel({ pageSize, page: currentModel.page }); - }, - [apiRef, logger], - ); - - const setPaginationModel = React.useCallback( - (paginationModel) => { - const currentModel = gridPaginationModelSelector(apiRef); - if (paginationModel === currentModel) { - return; - } - logger.debug("Setting 'paginationModel' to", paginationModel); - - apiRef.current.updateControlState( - 'pagination', - mergeStateWithPaginationModel( - props.rowCount ?? visibleTopLevelRowCount, - props.signature, - paginationModel, - ), - 'setPaginationModel', - ); - apiRef.current.forceUpdate(); - }, - [apiRef, logger, props.rowCount, props.signature, visibleTopLevelRowCount], - ); - - const pageApi: GridPaginationApi = { - setPage, - setPageSize, - setPaginationModel, - }; - - useGridApiMethod(apiRef, pageApi, 'public'); - - /** - * PRE-PROCESSING - */ - const stateExportPreProcessing = React.useCallback>( - (prevState, context) => { - const paginationModel = gridPaginationModelSelector(apiRef); - - const shouldExportPaginationModel = - // Always export if the `exportOnlyDirtyModels` property is not activated - !context.exportOnlyDirtyModels || - // Always export if the `paginationModel` is controlled - props.paginationModel != null || - // Always export if the `paginationModel` has been initialized - props.initialState?.pagination?.paginationModel != null || - // Export if `page` or `pageSize` is not equal to the default value - (paginationModel.page !== 0 && - paginationModel.pageSize !== defaultPageSize(props.autoPageSize)); - - if (!shouldExportPaginationModel) { - return prevState; - } - - return { - ...prevState, - pagination: { - ...prevState.pagination, - paginationModel, - }, - }; - }, - [ - apiRef, - props.paginationModel, - props.initialState?.pagination?.paginationModel, - props.autoPageSize, - ], - ); - - const stateRestorePreProcessing = React.useCallback>( - (params, context) => { - const paginationModel = context.stateToRestore.pagination?.paginationModel - ? { - ...getDefaultGridPaginationModel(props.autoPageSize), - ...context.stateToRestore.pagination?.paginationModel, - } - : gridPaginationModelSelector(apiRef); - apiRef.current.updateControlState( - 'pagination', - mergeStateWithPaginationModel( - props.rowCount ?? visibleTopLevelRowCount, - props.signature, - paginationModel, - ), - 'stateRestorePreProcessing', - ); - return params; - }, - [apiRef, props.autoPageSize, props.rowCount, props.signature, visibleTopLevelRowCount], - ); - - useGridRegisterPipeProcessor(apiRef, 'exportState', stateExportPreProcessing); - useGridRegisterPipeProcessor(apiRef, 'restoreState', stateRestorePreProcessing); - - /** - * EVENTS - */ - const handlePaginationModelChange: GridEventListener<'paginationModelChange'> = () => { - const paginationModel = gridPaginationModelSelector(apiRef); - if (apiRef.current.virtualScrollerRef?.current) { - apiRef.current.scrollToIndexes({ - rowIndex: paginationModel.page * paginationModel.pageSize, - }); - } - - apiRef.current.forceUpdate(); - }; - - const handleUpdateAutoPageSize = React.useCallback(() => { - if (!props.autoPageSize) { - return; - } - - const dimensions = apiRef.current.getRootDimensions(); - - const maximumPageSizeWithoutScrollBar = Math.floor( - dimensions.viewportInnerSize.height / rowHeight, - ); - - apiRef.current.setPageSize(maximumPageSizeWithoutScrollBar); - }, [apiRef, props.autoPageSize, rowHeight]); - - useGridApiEventHandler(apiRef, 'viewportInnerSizeChange', handleUpdateAutoPageSize); - useGridApiEventHandler(apiRef, 'paginationModelChange', handlePaginationModelChange); - - /** - * EFFECTS - */ - React.useEffect(() => { - if (process.env.NODE_ENV !== 'production') { - if (props.paginationMode === 'server' && props.rowCount == null) { - noRowCountInServerMode(); - } - } - }, [props.rowCount, props.paginationMode]); - - React.useEffect(() => { - apiRef.current.updateControlState( - 'pagination', - mergeStateWithPaginationModel( - props.rowCount ?? visibleTopLevelRowCount, - props.signature, - props.paginationModel, - ), - ); - }, [ - apiRef, - props.paginationModel, - props.rowCount, - props.paginationMode, - visibleTopLevelRowCount, - props.signature, - ]); - - React.useEffect(() => { - handleUpdateAutoPageSize(); - }, [handleUpdateAutoPageSize]); + useGridPaginationModel(apiRef, props); + useGridRowCount(apiRef, props); }; diff --git a/packages/x-data-grid/src/hooks/features/pagination/useGridPaginationModel.ts b/packages/x-data-grid/src/hooks/features/pagination/useGridPaginationModel.ts new file mode 100644 index 000000000000..a98854bfa2c5 --- /dev/null +++ b/packages/x-data-grid/src/hooks/features/pagination/useGridPaginationModel.ts @@ -0,0 +1,257 @@ +import * as React from 'react'; +import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity'; +import { DataGridProcessedProps } from '../../../models/props/DataGridProps'; +import { GridPaginationModelApi, GridPaginationState } from './gridPaginationInterfaces'; +import { GridEventListener } from '../../../models/events'; +import { GridPaginationModel } from '../../../models/gridPaginationProps'; +import { gridDensityFactorSelector } from '../density'; +import { + useGridLogger, + useGridSelector, + useGridApiMethod, + useGridApiEventHandler, +} from '../../utils'; +import { GridPipeProcessor, useGridRegisterPipeProcessor } from '../../core/pipeProcessing'; +import { gridPaginationModelSelector } from './gridPaginationSelector'; +import { + getPageCount, + defaultPageSize, + throwIfPageSizeExceedsTheLimit, + getDefaultGridPaginationModel, + getValidPage, +} from './gridPaginationUtils'; + +export const getDerivedPaginationModel = ( + paginationState: GridPaginationState, + signature: DataGridProcessedProps['signature'], + paginationModelProp?: GridPaginationModel, +) => { + let paginationModel = paginationState.paginationModel; + const rowCount = paginationState.rowCount; + const pageSize = paginationModelProp?.pageSize ?? paginationModel.pageSize; + const pageCount = getPageCount(rowCount, pageSize); + + if ( + paginationModelProp && + (paginationModelProp?.page !== paginationModel.page || + paginationModelProp?.pageSize !== paginationModel.pageSize) + ) { + paginationModel = paginationModelProp; + } + + const validPage = getValidPage(paginationModel.page, pageCount); + if (validPage !== paginationModel.page) { + paginationModel = { ...paginationModel, page: validPage }; + } + + throwIfPageSizeExceedsTheLimit(paginationModel.pageSize, signature); + + return paginationModel; +}; + +/** + * @requires useGridFilter (state) + * @requires useGridDimensions (event) - can be after + */ +export const useGridPaginationModel = ( + apiRef: React.MutableRefObject, + props: Pick< + DataGridProcessedProps, + | 'paginationModel' + | 'onPaginationModelChange' + | 'autoPageSize' + | 'initialState' + | 'paginationMode' + | 'signature' + | 'rowHeight' + >, +) => { + const logger = useGridLogger(apiRef, 'useGridPaginationModel'); + + const densityFactor = useGridSelector(apiRef, gridDensityFactorSelector); + const rowHeight = Math.floor(props.rowHeight * densityFactor); + apiRef.current.registerControlState({ + stateId: 'paginationModel', + propModel: props.paginationModel, + propOnChange: props.onPaginationModelChange, + stateSelector: gridPaginationModelSelector, + changeEvent: 'paginationModelChange', + }); + + /** + * API METHODS + */ + const setPage = React.useCallback( + (page) => { + const currentModel = gridPaginationModelSelector(apiRef); + if (page === currentModel.page) { + return; + } + logger.debug(`Setting page to ${page}`); + apiRef.current.setPaginationModel({ page, pageSize: currentModel.pageSize }); + }, + [apiRef, logger], + ); + + const setPageSize = React.useCallback( + (pageSize) => { + const currentModel = gridPaginationModelSelector(apiRef); + if (pageSize === currentModel.pageSize) { + return; + } + + logger.debug(`Setting page size to ${pageSize}`); + apiRef.current.setPaginationModel({ pageSize, page: currentModel.page }); + }, + [apiRef, logger], + ); + + const setPaginationModel = React.useCallback( + (paginationModel) => { + const currentModel = gridPaginationModelSelector(apiRef); + if (paginationModel === currentModel) { + return; + } + logger.debug("Setting 'paginationModel' to", paginationModel); + + apiRef.current.setState((state) => ({ + ...state, + pagination: { + ...state.pagination, + paginationModel: getDerivedPaginationModel( + state.pagination, + props.signature, + paginationModel, + ), + }, + })); + apiRef.current.forceUpdate(); + }, + [apiRef, logger, props.signature], + ); + + const pageApi: GridPaginationModelApi = { + setPage, + setPageSize, + setPaginationModel, + }; + + useGridApiMethod(apiRef, pageApi, 'public'); + + /** + * PRE-PROCESSING + */ + const stateExportPreProcessing = React.useCallback>( + (prevState, context) => { + const paginationModel = gridPaginationModelSelector(apiRef); + + const shouldExportPaginationModel = + // Always export if the `exportOnlyDirtyModels` property is not activated + !context.exportOnlyDirtyModels || + // Always export if the `paginationModel` is controlled + props.paginationModel != null || + // Always export if the `paginationModel` has been initialized + props.initialState?.pagination?.paginationModel != null || + // Export if `page` or `pageSize` is not equal to the default value + (paginationModel.page !== 0 && + paginationModel.pageSize !== defaultPageSize(props.autoPageSize)); + + if (!shouldExportPaginationModel) { + return prevState; + } + + return { + ...prevState, + pagination: { + ...prevState.pagination, + paginationModel, + }, + }; + }, + [ + apiRef, + props.paginationModel, + props.initialState?.pagination?.paginationModel, + props.autoPageSize, + ], + ); + + const stateRestorePreProcessing = React.useCallback>( + (params, context) => { + const paginationModel = context.stateToRestore.pagination?.paginationModel + ? { + ...getDefaultGridPaginationModel(props.autoPageSize), + ...context.stateToRestore.pagination?.paginationModel, + } + : gridPaginationModelSelector(apiRef); + apiRef.current.setState((state) => ({ + ...state, + pagination: { + ...state.pagination, + paginationModel: getDerivedPaginationModel( + state.pagination, + props.signature, + paginationModel, + ), + }, + })); + return params; + }, + [apiRef, props.autoPageSize, props.signature], + ); + + useGridRegisterPipeProcessor(apiRef, 'exportState', stateExportPreProcessing); + useGridRegisterPipeProcessor(apiRef, 'restoreState', stateRestorePreProcessing); + + /** + * EVENTS + */ + const handlePaginationModelChange: GridEventListener<'paginationModelChange'> = () => { + const paginationModel = gridPaginationModelSelector(apiRef); + if (apiRef.current.virtualScrollerRef?.current) { + apiRef.current.scrollToIndexes({ + rowIndex: paginationModel.page * paginationModel.pageSize, + }); + } + + apiRef.current.forceUpdate(); + }; + + const handleUpdateAutoPageSize = React.useCallback(() => { + if (!props.autoPageSize) { + return; + } + + const dimensions = apiRef.current.getRootDimensions(); + + const maximumPageSizeWithoutScrollBar = Math.floor( + dimensions.viewportInnerSize.height / rowHeight, + ); + + apiRef.current.setPageSize(maximumPageSizeWithoutScrollBar); + }, [apiRef, props.autoPageSize, rowHeight]); + + useGridApiEventHandler(apiRef, 'viewportInnerSizeChange', handleUpdateAutoPageSize); + useGridApiEventHandler(apiRef, 'paginationModelChange', handlePaginationModelChange); + + /** + * EFFECTS + */ + React.useEffect(() => { + apiRef.current.setState((state) => ({ + ...state, + pagination: { + ...state.pagination, + paginationModel: getDerivedPaginationModel( + state.pagination, + props.signature, + props.paginationModel, + ), + }, + })); + }, [apiRef, props.paginationModel, props.paginationMode, props.signature]); + + React.useEffect(() => { + handleUpdateAutoPageSize(); + }, [handleUpdateAutoPageSize]); +}; diff --git a/packages/x-data-grid/src/hooks/features/pagination/useGridRowCount.ts b/packages/x-data-grid/src/hooks/features/pagination/useGridRowCount.ts new file mode 100644 index 000000000000..5f129e96732d --- /dev/null +++ b/packages/x-data-grid/src/hooks/features/pagination/useGridRowCount.ts @@ -0,0 +1,131 @@ +import * as React from 'react'; +import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity'; +import { DataGridProcessedProps } from '../../../models/props/DataGridProps'; +import { GridPaginationRowCountApi } from './gridPaginationInterfaces'; +import { gridFilteredTopLevelRowCountSelector } from '../filter'; +import { useGridLogger, useGridSelector, useGridApiMethod } from '../../utils'; +import { GridPipeProcessor, useGridRegisterPipeProcessor } from '../../core/pipeProcessing'; +import { gridPaginationRowCountSelector } from './gridPaginationSelector'; +import { noRowCountInServerMode } from './gridPaginationUtils'; + +/** + * @requires useGridFilter (state) + * @requires useGridDimensions (event) - can be after + */ +export const useGridRowCount = ( + apiRef: React.MutableRefObject, + props: Pick< + DataGridProcessedProps, + 'rowCount' | 'initialState' | 'paginationMode' | 'onRowCountChange' + >, +) => { + const logger = useGridLogger(apiRef, 'useGridRowCount'); + + const visibleTopLevelRowCount = useGridSelector(apiRef, gridFilteredTopLevelRowCountSelector); + const rowCount = useGridSelector(apiRef, gridPaginationRowCountSelector); + // @ts-expect-error To access Pro only prop + const isDataSourceAvailable = props.unstable_dataSource != null; + + apiRef.current.registerControlState({ + stateId: 'paginationRowCount', + propModel: props.rowCount, + propOnChange: props.onRowCountChange, + stateSelector: gridPaginationRowCountSelector, + changeEvent: 'rowCountChange', + }); + + /** + * API METHODS + */ + const setRowCount = React.useCallback( + (newRowCount) => { + if (rowCount === newRowCount) { + return; + } + logger.debug("Setting 'rowCount' to", newRowCount); + + apiRef.current.setState((state) => ({ + ...state, + pagination: { + ...state.pagination, + rowCount: newRowCount, + }, + })); + apiRef.current.forceUpdate(); + }, + [apiRef, logger, rowCount], + ); + + const paginationRowCountApi: GridPaginationRowCountApi = { + setRowCount, + }; + + useGridApiMethod(apiRef, paginationRowCountApi, 'public'); + + /** + * PRE-PROCESSING + */ + const stateExportPreProcessing = React.useCallback>( + (prevState, context) => { + const exportedRowCount = gridPaginationRowCountSelector(apiRef); + + const shouldExportRowCount = + // Always export if the `exportOnlyDirtyModels` property is not activated + !context.exportOnlyDirtyModels || + // Always export if the `rowCount` is controlled + props.rowCount != null || + // Always export if the `rowCount` has been initialized + props.initialState?.pagination?.rowCount != null; + + if (!shouldExportRowCount) { + return prevState; + } + + return { + ...prevState, + pagination: { + ...prevState.pagination, + rowCount: exportedRowCount, + }, + }; + }, + [apiRef, props.rowCount, props.initialState?.pagination?.rowCount], + ); + + const stateRestorePreProcessing = React.useCallback>( + (params, context) => { + const restoredRowCount = context.stateToRestore.pagination?.rowCount + ? context.stateToRestore.pagination.rowCount + : gridPaginationRowCountSelector(apiRef); + apiRef.current.setState((state) => ({ + ...state, + pagination: { + ...state.pagination, + rowCount: restoredRowCount, + }, + })); + return params; + }, + [apiRef], + ); + + useGridRegisterPipeProcessor(apiRef, 'exportState', stateExportPreProcessing); + useGridRegisterPipeProcessor(apiRef, 'restoreState', stateRestorePreProcessing); + + /** + * EFFECTS + */ + React.useEffect(() => { + if (process.env.NODE_ENV !== 'production') { + if (props.paginationMode === 'server' && props.rowCount == null && !isDataSourceAvailable) { + noRowCountInServerMode(); + } + } + }, [props.rowCount, props.paginationMode, isDataSourceAvailable]); + + React.useEffect(() => { + if (props.paginationMode === 'client') { + apiRef.current.setRowCount(visibleTopLevelRowCount); + } + }, [apiRef, visibleTopLevelRowCount, props.paginationMode]); +}; diff --git a/packages/x-data-grid/src/hooks/features/rows/gridRowsInterfaces.ts b/packages/x-data-grid/src/hooks/features/rows/gridRowsInterfaces.ts index 34562ff2231f..f46cf6efa70f 100644 --- a/packages/x-data-grid/src/hooks/features/rows/gridRowsInterfaces.ts +++ b/packages/x-data-grid/src/hooks/features/rows/gridRowsInterfaces.ts @@ -50,7 +50,7 @@ export interface GridRowsState { treeDepths: GridTreeDepths; dataRowIds: GridRowId[]; /** - * Matches the value of the `loading` prop. + * Depicts if the rows are loading. */ loading?: boolean; /** diff --git a/packages/x-data-grid/src/hooks/features/rows/useGridParamsApi.ts b/packages/x-data-grid/src/hooks/features/rows/useGridParamsApi.ts index ae7b518d64e8..7f04cb9b9d25 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridParamsApi.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridParamsApi.ts @@ -111,6 +111,9 @@ export function useGridParamsApi(apiRef: React.MutableRefObject( (row, colDef) => { + if (!row) { + return null; + } const field = colDef.field; if (!colDef || !colDef.valueGetter) { 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 1bdc10b76eab..50d041b8d233 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts @@ -44,8 +44,10 @@ import { useGridRegisterPipeApplier } from '../../core/pipeProcessing'; export const rowsStateInitializer: GridStateInitializer< Pick > = (state, props, apiRef) => { + // @ts-expect-error To read prop which belongs to the `DataGridPro` component + const isDataSourceAvailable = props.unstable_dataSource != null; apiRef.current.caches.rows = createRowsInternalCache({ - rows: props.rows, + rows: isDataSourceAvailable ? [] : props.rows, getRowId: props.getRowId, loading: props.loading, rowCount: props.rowCount, @@ -56,7 +58,7 @@ export const rowsStateInitializer: GridStateInitializer< rows: getRowsStateFromCache({ apiRef, rowCountProp: props.rowCount, - loadingProp: props.loading, + loadingProp: isDataSourceAvailable ? true : props.loading, previousTree: null, previousTreeDepths: null, }), @@ -238,6 +240,22 @@ export const useGridRows = ( [props.signature, props.getRowId, throttledRowsChange, apiRef], ); + const setLoading = React.useCallback( + (loading) => { + if (loading === props.loading) { + return; + } + logger.debug(`Setting loading to ${loading}`); + apiRef.current.setState((state) => ({ + ...state, + rows: { ...state.rows, loading }, + })); + apiRef.current.caches.rows.loadingPropBeforePartialUpdates = loading; + apiRef.current.forceUpdate(); + }, + [props.loading, apiRef, logger], + ); + const getRowModels = React.useCallback(() => { const dataRows = gridDataRowIdsSelector(apiRef); const idRowsLookup = gridRowsLookupSelector(apiRef); @@ -462,6 +480,7 @@ export const useGridRows = ( const rowApi: GridRowApi = { getRow, + setLoading, getRowId, getRowModels, getRowsCount, @@ -501,7 +520,7 @@ export const useGridRows = ( // We must use the new `props.rows` on the new grouping // This occurs because this event is triggered before the `useEffect` on the rows when both the grouping pre-processing and the rows changes on the same render cache = createRowsInternalCache({ - rows: props.rows, + rows: props.rows || [], getRowId: props.getRowId, loading: props.loading, rowCount: props.rowCount, @@ -624,10 +643,10 @@ export const useGridRows = ( return; } - logger.debug(`Updating all rows, new length ${props.rows.length}`); + logger.debug(`Updating all rows, new length ${props.rows?.length}`); throttledRowsChange({ cache: createRowsInternalCache({ - rows: props.rows, + rows: props.rows || [], getRowId: props.getRowId, loading: props.loading, rowCount: props.rowCount, diff --git a/packages/x-data-grid/src/models/api/gridApiCommon.ts b/packages/x-data-grid/src/models/api/gridApiCommon.ts index fc87a36a4d8b..8bf51aef459e 100644 --- a/packages/x-data-grid/src/models/api/gridApiCommon.ts +++ b/packages/x-data-grid/src/models/api/gridApiCommon.ts @@ -28,7 +28,10 @@ import type { GridDimensionsApi, GridDimensionsPrivateApi, } from '../../hooks/features/dimensions/gridDimensionsApi'; -import type { GridPaginationApi } from '../../hooks/features/pagination'; +import type { + GridPaginationModelApi, + GridPaginationRowCountApi, +} from '../../hooks/features/pagination'; import type { GridStatePersistenceApi } from '../../hooks/features/statePersistence'; import { GridColumnGroupingApi } from './gridColumnGroupingApi'; import type { GridInitialStateCommunity, GridStateCommunity } from '../gridStateCommunity'; @@ -49,7 +52,8 @@ export interface GridApiCommon< GridColumnApi, GridRowSelectionApi, GridSortApi, - GridPaginationApi, + GridPaginationModelApi, + GridPaginationRowCountApi, GridCsvExportApi, GridFocusApi, GridFilterApi, diff --git a/packages/x-data-grid/src/models/api/gridRowApi.ts b/packages/x-data-grid/src/models/api/gridRowApi.ts index 35382e3067dc..f35f06c80ed5 100644 --- a/packages/x-data-grid/src/models/api/gridRowApi.ts +++ b/packages/x-data-grid/src/models/api/gridRowApi.ts @@ -48,6 +48,11 @@ export interface GridRowApi { * @returns {GridRowId[]} A list of ids. */ getAllRowIds: () => GridRowId[]; + /** + * Sets the internal loading state. + * @param {boolean} loading The new rows. + */ + setLoading: (loading: boolean) => void; /** * Sets a new set of rows. * @param {GridRowModel[]} rows The new rows. diff --git a/packages/x-data-grid/src/models/events/gridEventLookup.ts b/packages/x-data-grid/src/models/events/gridEventLookup.ts index 31ed5a43554a..451988f596a1 100644 --- a/packages/x-data-grid/src/models/events/gridEventLookup.ts +++ b/packages/x-data-grid/src/models/events/gridEventLookup.ts @@ -358,6 +358,10 @@ export interface GridControlledStateEventLookup { * Fired when the column visibility model changes. */ columnVisibilityModelChange: { params: GridColumnVisibilityModel }; + /** + * Fired when the row count change. + */ + rowCountChange: { params: number }; } export interface GridControlledStateReasonLookup { diff --git a/packages/x-data-grid/src/models/gridApiCaches.ts b/packages/x-data-grid/src/models/gridApiCaches.ts index aa2e8932b91d..78bf52db8caa 100644 --- a/packages/x-data-grid/src/models/gridApiCaches.ts +++ b/packages/x-data-grid/src/models/gridApiCaches.ts @@ -2,4 +2,5 @@ import { GridRowsInternalCache } from '../hooks/features/rows/gridRowsInterfaces export interface GridApiCaches { rows: GridRowsInternalCache; + groupKeys: string[]; } diff --git a/packages/x-data-grid/src/models/gridRows.ts b/packages/x-data-grid/src/models/gridRows.ts index 1519fd1bddba..c89b55647e3c 100644 --- a/packages/x-data-grid/src/models/gridRows.ts +++ b/packages/x-data-grid/src/models/gridRows.ts @@ -114,6 +114,25 @@ export interface GridDataGroupNode extends GridBasicGroupNode { isAutoGenerated: false; } +export interface GridServerSideGroupNode extends GridDataGroupNode { + /** + * The children for this node are currently being fetched + */ + isLoading: boolean; + /** + * If true, this node is a server side group node. + */ + isServerSide: boolean; + /** + * If true, this node has been expanded by the user and the children have been fetched. + */ + childrenFetched: boolean; + /** + * The cached path to be passed on as `groupKey` to the server. + */ + path: string[]; +} + export type GridGroupNode = GridDataGroupNode | GridAutoGeneratedGroupNode; export type GridChildrenFromPathLookup = { diff --git a/packages/x-data-grid/src/models/props/DataGridProps.ts b/packages/x-data-grid/src/models/props/DataGridProps.ts index cab7a732b01e..0db5c31fc5eb 100644 --- a/packages/x-data-grid/src/models/props/DataGridProps.ts +++ b/packages/x-data-grid/src/models/props/DataGridProps.ts @@ -105,7 +105,12 @@ export interface DataGridPropsWithComplexDefaultValueBeforeProcessing { * The controlled model do not have a default value at the prop processing level, so they must be defined in `DataGridOtherProps` * TODO: add multiSortKey */ -export interface DataGridPropsWithDefaultValues { +export interface DataGridPropsWithDefaultValues { + /** + * Set of rows of type [[GridRowsProp]]. + * @default [] + */ + rows: GridRowsProp; /** * If `true`, the Data Grid height is dynamic and follow the number of rows in the Data Grid. * @default false @@ -591,6 +596,11 @@ export interface DataGridPropsWithoutDefaultValue void; + /** + * Callback fired when the row count has changed. + * @param {number} count Updated row count. + */ + onRowCountChange?: (count: number) => void; /** * Callback fired when the preferences panel is closed. * @param {GridPreferencePanelParams} params With all properties from [[GridPreferencePanelParams]]. @@ -713,10 +723,6 @@ export interface DataGridPropsWithoutDefaultValue; /** * The initial state of the DataGrid. * The data in it will be set in the state on initialization but will not be controlled. diff --git a/scripts/x-data-grid-generator.exports.json b/scripts/x-data-grid-generator.exports.json index e69f7b247f04..ddfce3e0f5f5 100644 --- a/scripts/x-data-grid-generator.exports.json +++ b/scripts/x-data-grid-generator.exports.json @@ -1,9 +1,11 @@ [ { "name": "ColumnsOptions", "kind": "Interface" }, + { "name": "createDummyDataSource", "kind": "Variable" }, { "name": "createFakeServer", "kind": "Variable" }, { "name": "currencyPairs", "kind": "Variable" }, { "name": "DemoDataReturnType", "kind": "TypeAlias" }, { "name": "DemoLink", "kind": "Variable" }, + { "name": "extrapolateSeed", "kind": "Function" }, { "name": "generateFilledQuantity", "kind": "Variable" }, { "name": "generateIsFilled", "kind": "Variable" }, { "name": "getBasicGridData", "kind": "Variable" }, @@ -18,7 +20,6 @@ { "name": "GridDemoData", "kind": "Interface" }, { "name": "loadServerRows", "kind": "Variable" }, { "name": "Movie", "kind": "TypeAlias" }, - { "name": "QueryOptions", "kind": "Interface" }, { "name": "random", "kind": "Variable" }, { "name": "randomAddress", "kind": "Variable" }, { "name": "randomArrayItem", "kind": "Variable" }, diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 1648d59a7528..f43158998e82 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -22,7 +22,6 @@ { "name": "ColumnsStylesInterface", "kind": "Interface" }, { "name": "createUseGridApiEventHandler", "kind": "Function" }, { "name": "CursorCoordinates", "kind": "Interface" }, - { "name": "DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES", "kind": "Variable" }, { "name": "DataGrid", "kind": "Function" }, { "name": "DataGridPremium", "kind": "Variable" }, { "name": "DataGridPremiumProps", "kind": "Interface" }, @@ -39,6 +38,7 @@ { "name": "FocusElement", "kind": "Interface" }, { "name": "FooterPropsOverrides", "kind": "Interface" }, { "name": "FooterRowCountOverrides", "kind": "Interface" }, + { "name": "GET_DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES", "kind": "Variable" }, { "name": "getAggregationFooterRowIdFromGroupId", "kind": "Variable" }, { "name": "GetApplyFilterFn", "kind": "TypeAlias" }, { "name": "GetApplyQuickFilterFn", "kind": "TypeAlias" }, @@ -241,6 +241,9 @@ { "name": "GridDataGroupNode", "kind": "Interface" }, { "name": "GridDataPinnedRowNode", "kind": "Interface" }, { "name": "gridDataRowIdsSelector", "kind": "Variable" }, + { "name": "GridDataSource", "kind": "Interface" }, + { "name": "GridDataSourceApi", "kind": "Interface" }, + { "name": "GridDataSourceCache", "kind": "Interface" }, { "name": "gridDateComparator", "kind": "Variable" }, { "name": "gridDateFormatter", "kind": "Variable" }, { "name": "gridDateTimeFormatter", "kind": "Variable" }, @@ -355,6 +358,8 @@ { "name": "GridFunctionsIcon", "kind": "Variable" }, { "name": "GridGenericColumnMenu", "kind": "Variable" }, { "name": "GridGenericColumnMenuProps", "kind": "Interface" }, + { "name": "GridGetRowsParams", "kind": "Interface" }, + { "name": "GridGetRowsResponse", "kind": "Interface" }, { "name": "GridGetRowsToExportParams", "kind": "Interface" }, { "name": "GridGroupingColDefOverride", "kind": "Interface" }, { "name": "GridGroupingColDefOverrideParams", "kind": "Interface" }, @@ -405,10 +410,12 @@ { "name": "gridPaginatedVisibleSortedGridRowEntriesSelector", "kind": "Variable" }, { "name": "gridPaginatedVisibleSortedGridRowIdsSelector", "kind": "Variable" }, { "name": "GridPagination", "kind": "Variable" }, - { "name": "GridPaginationApi", "kind": "Interface" }, { "name": "GridPaginationInitialState", "kind": "Interface" }, { "name": "GridPaginationModel", "kind": "Interface" }, + { "name": "GridPaginationModelApi", "kind": "Interface" }, { "name": "gridPaginationModelSelector", "kind": "Variable" }, + { "name": "GridPaginationRowCountApi", "kind": "Interface" }, + { "name": "gridPaginationRowCountSelector", "kind": "Variable" }, { "name": "gridPaginationRowRangeSelector", "kind": "Variable" }, { "name": "gridPaginationSelector", "kind": "Variable" }, { "name": "GridPaginationState", "kind": "Interface" }, @@ -528,6 +535,7 @@ { "name": "GridSearchIcon", "kind": "Variable" }, { "name": "GridSelectedRowCount", "kind": "Variable" }, { "name": "GridSeparatorIcon", "kind": "Variable" }, + { "name": "GridServerSideGroupNode", "kind": "Interface" }, { "name": "GridSignature", "kind": "Enum" }, { "name": "GridSingleSelectColDef", "kind": "Interface" }, { "name": "GridSkeletonCell", "kind": "Function" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index 8dd84eb54b0b..b39d71efd258 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -21,7 +21,6 @@ { "name": "ColumnsPanelPropsOverrides", "kind": "Interface" }, { "name": "createUseGridApiEventHandler", "kind": "Function" }, { "name": "CursorCoordinates", "kind": "Interface" }, - { "name": "DATA_GRID_PRO_PROPS_DEFAULT_VALUES", "kind": "Variable" }, { "name": "DataGrid", "kind": "Function" }, { "name": "DataGridPremium", "kind": "Function" }, { "name": "DataGridPro", "kind": "Variable" }, @@ -38,6 +37,7 @@ { "name": "FocusElement", "kind": "Interface" }, { "name": "FooterPropsOverrides", "kind": "Interface" }, { "name": "FooterRowCountOverrides", "kind": "Interface" }, + { "name": "GET_DATA_GRID_PRO_PROPS_DEFAULT_VALUES", "kind": "Variable" }, { "name": "GetApplyFilterFn", "kind": "TypeAlias" }, { "name": "GetApplyQuickFilterFn", "kind": "TypeAlias" }, { "name": "GetColumnForNewFilterArgs", "kind": "Interface" }, @@ -215,6 +215,9 @@ { "name": "GridDataGroupNode", "kind": "Interface" }, { "name": "GridDataPinnedRowNode", "kind": "Interface" }, { "name": "gridDataRowIdsSelector", "kind": "Variable" }, + { "name": "GridDataSource", "kind": "Interface" }, + { "name": "GridDataSourceApi", "kind": "Interface" }, + { "name": "GridDataSourceCache", "kind": "Interface" }, { "name": "gridDateComparator", "kind": "Variable" }, { "name": "gridDateFormatter", "kind": "Variable" }, { "name": "gridDateTimeFormatter", "kind": "Variable" }, @@ -323,6 +326,8 @@ { "name": "GridFooterPlaceholder", "kind": "Function" }, { "name": "GridGenericColumnMenu", "kind": "Variable" }, { "name": "GridGenericColumnMenuProps", "kind": "Interface" }, + { "name": "GridGetRowsParams", "kind": "Interface" }, + { "name": "GridGetRowsResponse", "kind": "Interface" }, { "name": "GridGetRowsToExportParams", "kind": "Interface" }, { "name": "GridGroupingColDefOverride", "kind": "Interface" }, { "name": "GridGroupingColDefOverrideParams", "kind": "Interface" }, @@ -369,10 +374,12 @@ { "name": "gridPaginatedVisibleSortedGridRowEntriesSelector", "kind": "Variable" }, { "name": "gridPaginatedVisibleSortedGridRowIdsSelector", "kind": "Variable" }, { "name": "GridPagination", "kind": "Variable" }, - { "name": "GridPaginationApi", "kind": "Interface" }, { "name": "GridPaginationInitialState", "kind": "Interface" }, { "name": "GridPaginationModel", "kind": "Interface" }, + { "name": "GridPaginationModelApi", "kind": "Interface" }, { "name": "gridPaginationModelSelector", "kind": "Variable" }, + { "name": "GridPaginationRowCountApi", "kind": "Interface" }, + { "name": "gridPaginationRowCountSelector", "kind": "Variable" }, { "name": "gridPaginationRowRangeSelector", "kind": "Variable" }, { "name": "gridPaginationSelector", "kind": "Variable" }, { "name": "GridPaginationState", "kind": "Interface" }, @@ -482,6 +489,7 @@ { "name": "GridSearchIcon", "kind": "Variable" }, { "name": "GridSelectedRowCount", "kind": "Variable" }, { "name": "GridSeparatorIcon", "kind": "Variable" }, + { "name": "GridServerSideGroupNode", "kind": "Interface" }, { "name": "GridSignature", "kind": "Enum" }, { "name": "GridSingleSelectColDef", "kind": "Interface" }, { "name": "GridSkeletonCell", "kind": "Function" }, diff --git a/scripts/x-data-grid.exports.json b/scripts/x-data-grid.exports.json index 828cec3e844d..62f636740652 100644 --- a/scripts/x-data-grid.exports.json +++ b/scripts/x-data-grid.exports.json @@ -335,10 +335,12 @@ { "name": "gridPaginatedVisibleSortedGridRowEntriesSelector", "kind": "Variable" }, { "name": "gridPaginatedVisibleSortedGridRowIdsSelector", "kind": "Variable" }, { "name": "GridPagination", "kind": "Variable" }, - { "name": "GridPaginationApi", "kind": "Interface" }, { "name": "GridPaginationInitialState", "kind": "Interface" }, { "name": "GridPaginationModel", "kind": "Interface" }, + { "name": "GridPaginationModelApi", "kind": "Interface" }, { "name": "gridPaginationModelSelector", "kind": "Variable" }, + { "name": "GridPaginationRowCountApi", "kind": "Interface" }, + { "name": "gridPaginationRowCountSelector", "kind": "Variable" }, { "name": "gridPaginationRowRangeSelector", "kind": "Variable" }, { "name": "gridPaginationSelector", "kind": "Variable" }, { "name": "GridPaginationState", "kind": "Interface" }, @@ -437,6 +439,7 @@ { "name": "GridSearchIcon", "kind": "Variable" }, { "name": "GridSelectedRowCount", "kind": "Variable" }, { "name": "GridSeparatorIcon", "kind": "Variable" }, + { "name": "GridServerSideGroupNode", "kind": "Interface" }, { "name": "GridSignature", "kind": "Enum" }, { "name": "GridSingleSelectColDef", "kind": "Interface" }, { "name": "GridSkeletonCell", "kind": "Function" }, diff --git a/yarn.lock b/yarn.lock index 5c983ec7d5ac..665f93d7faf1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2833,6 +2833,11 @@ dependencies: defer-to-connect "^2.0.0" +"@tanstack/query-core@^5.24.8": + version "5.24.8" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.24.8.tgz#b407546a726ecd4d60b8682dec2ca84ac9eab81e" + integrity sha512-yH7KnfXMf10p1U5GffTQzFi2Miiw6WJZImGYGdV7eqa5ZbKO8qVx9lOA9SfhIaJXomrMp1Yz5w/CBhVM3yWeTA== + "@testing-library/dom@^9.0.0": version "9.3.4" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-9.3.4.tgz#50696ec28376926fec0a1bf87d9dbac5e27f60ce" From b83276c3155e4a2268080598f977945a4130a3b7 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 4 Mar 2024 17:24:15 +0500 Subject: [PATCH 02/90] Minor improvement --- .../data/data-grid/server-side-data/ServerSideDataGrid.tsx | 2 +- .../src/hooks/features/serverSideData/useGridDataSource.ts | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx index c8e23b74ec2f..fe290b4a834c 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx @@ -18,7 +18,7 @@ function ServerSideDataGrid() { return (
{ - if (isFirstRender.current) { - isFirstRender.current = false; - } else { - fetchTopLevelRows(); - } + fetchTopLevelRows(); }, [props.unstable_dataSource, privateApiRef, fetchTopLevelRows]); }; From 20b56f2a16b2af9f5a10e634228ee6a4fa273a56 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Sun, 10 Mar 2024 11:55:17 +0500 Subject: [PATCH 03/90] Add paginationType --- packages/x-data-grid/src/DataGrid/useDataGridProps.ts | 1 + packages/x-data-grid/src/models/gridPaginationProps.ts | 2 ++ packages/x-data-grid/src/models/props/DataGridProps.ts | 10 +++++++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/x-data-grid/src/DataGrid/useDataGridProps.ts b/packages/x-data-grid/src/DataGrid/useDataGridProps.ts index 8a7fea9145c3..29846d75969b 100644 --- a/packages/x-data-grid/src/DataGrid/useDataGridProps.ts +++ b/packages/x-data-grid/src/DataGrid/useDataGridProps.ts @@ -62,6 +62,7 @@ export const DATA_GRID_PROPS_DEFAULT_VALUES: DataGridPropsWithDefaultValues = { logLevel: process.env.NODE_ENV === 'production' ? ('error' as const) : ('warn' as const), pagination: false, paginationMode: 'client', + paginationType: 'index', rowHeight: 52, pageSizeOptions: [25, 50, 100], rowSpacingType: 'margin', diff --git a/packages/x-data-grid/src/models/gridPaginationProps.ts b/packages/x-data-grid/src/models/gridPaginationProps.ts index 543323ac86b1..9eaef049c4c8 100644 --- a/packages/x-data-grid/src/models/gridPaginationProps.ts +++ b/packages/x-data-grid/src/models/gridPaginationProps.ts @@ -11,3 +11,5 @@ export interface GridPaginationModel { */ page: number; } + +export type GridPaginationType = 'index' | 'cursor'; diff --git a/packages/x-data-grid/src/models/props/DataGridProps.ts b/packages/x-data-grid/src/models/props/DataGridProps.ts index 0db5c31fc5eb..2319587fc0bd 100644 --- a/packages/x-data-grid/src/models/props/DataGridProps.ts +++ b/packages/x-data-grid/src/models/props/DataGridProps.ts @@ -30,7 +30,7 @@ import { GridSlotsComponentsProps } from '../gridSlotsComponentsProps'; import { GridColumnVisibilityModel } from '../../hooks/features/columns/gridColumnsInterfaces'; import { GridCellModesModel, GridRowModesModel } from '../api/gridEditingApi'; import { GridColumnGroupingModel } from '../gridColumnGrouping'; -import { GridPaginationModel } from '../gridPaginationProps'; +import { GridPaginationModel, GridPaginationType } from '../gridPaginationProps'; export interface GridExperimentalFeatures { /** @@ -295,6 +295,14 @@ export interface DataGridPropsWithDefaultValues Date: Tue, 2 Apr 2024 18:05:24 +0500 Subject: [PATCH 04/90] useDemoDataSource, useGridServerSideCache + minor improvements --- .../server-side-data/ServerSideDataGrid.js | 33 ++-- .../server-side-data/ServerSideDataGrid.tsx | 33 ++-- .../server-side-data/ServerSideTreeData.js | 82 ++++---- .../server-side-data/ServerSideTreeData.tsx | 82 ++++---- .../ServerSideTreeData.tsx.preview | 29 +-- .../x/api/data-grid/data-grid-premium.json | 5 +- docs/pages/x/api/data-grid/data-grid-pro.json | 5 +- docs/pages/x/api/data-grid/data-grid.json | 4 - docs/pages/x/api/data-grid/grid-api.md | 4 + .../data-grid-premium/data-grid-premium.json | 6 +- .../data-grid-pro/data-grid-pro.json | 6 +- .../data-grid/data-grid/data-grid.json | 3 - .../src/hooks/createDummyDataSource.ts | 124 ------------ .../x-data-grid-generator/src/hooks/index.ts | 2 +- .../src/hooks/serverUtils.ts | 4 + .../src/hooks/useDemoDataSource.ts | 184 ++++++++++++++++++ .../src/DataGridPremium/DataGridPremium.tsx | 15 +- .../useDataGridPremiumComponent.tsx | 4 + .../src/models/gridApiPremium.ts | 2 + .../src/DataGridPro/DataGridPro.tsx | 15 +- .../DataGridPro/useDataGridProComponent.tsx | 2 + .../src/DataGridPro/useDataGridProProps.ts | 3 +- .../src/hooks/features/index.ts | 2 +- .../features/serverSideData/dataSourceApi.ts | 24 --- .../serverSideData/serverSideInterfaces.ts | 51 +++++ .../serverSideData/useGridDataSource.ts | 58 ++---- .../serverSideData/useGridServerSideCache.ts | 83 ++++++++ ...useGridServerSideTreeDataPreProcessors.tsx | 2 +- .../x-data-grid-pro/src/internals/index.ts | 2 + .../src/models/dataGridProProps.ts | 5 + .../x-data-grid-pro/src/models/gridApiPro.ts | 2 + .../src/models/gridDataSource.ts | 2 +- .../x-data-grid/src/DataGrid/DataGrid.tsx | 8 - .../src/models/gridPaginationProps.ts | 2 - scripts/x-data-grid-generator.exports.json | 2 +- scripts/x-data-grid-premium.exports.json | 2 +- scripts/x-data-grid-pro.exports.json | 2 +- scripts/x-data-grid.exports.json | 1 - 38 files changed, 541 insertions(+), 354 deletions(-) delete mode 100644 packages/x-data-grid-generator/src/hooks/createDummyDataSource.ts create mode 100644 packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts delete mode 100644 packages/x-data-grid-pro/src/hooks/features/serverSideData/dataSourceApi.ts create mode 100644 packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts create mode 100644 packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridServerSideCache.ts diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGrid.js b/docs/data/data-grid/server-side-data/ServerSideDataGrid.js index fe290b4a834c..57c2ac091fbc 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGrid.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGrid.js @@ -1,20 +1,29 @@ import * as React from 'react'; import { DataGridPro } from '@mui/x-data-grid-pro'; -import { createDummyDataSource } from '@mui/x-data-grid-generator'; +import { useDemoDataSource } from '@mui/x-data-grid-generator'; -const [dataSource, { initialState, columns }] = createDummyDataSource( - {}, - { useCursorPagination: false }, -); +function ServerSideDataGrid() { + const { getRows, columns, initialState } = useDemoDataSource( + {}, + { useCursorPagination: false }, + ); -const initialStateWithPagination = { - ...initialState, - pagination: { - paginationModel: { pageSize: 10, page: 0 }, - }, -}; + const initialStateWithPagination = React.useMemo( + () => ({ + ...initialState, + pagination: { + paginationModel: { pageSize: 10, page: 0 }, + }, + }), + [initialState], + ); + + const dataSource = React.useMemo(() => { + return { + getRows, + }; + }, [getRows]); -function ServerSideDataGrid() { return (
({ + ...initialState, + pagination: { + paginationModel: { pageSize: 10, page: 0 }, + }, + }), + [initialState], + ); + + const dataSource = React.useMemo(() => { + return { + getRows, + }; + }, [getRows]); -function ServerSideDataGrid() { return (
{ cacheInstance.setQueryData(key, value); - console.log('setting cache', key, value); }, get: (key) => { - console.log('getting cache', key, cacheInstance.getQueryData(key)); return cacheInstance.getQueryData(key); }, - invalidate: (queryKey) => { - if (queryKey) { - cacheInstance.invalidateQueries({ queryKey }); - } - cacheInstance.invalidateQueries(); + clear: () => { + cacheInstance.clear(); }, }; @@ -48,21 +29,48 @@ const pageSizeOptions = [5, 10, 50]; export default function ServerSideTreeData() { const apiRef = useGridApiRef(); + const { getRows, ...props } = useDemoDataSource({ + dataSet: 'Employee', + rowLength: 1000, + treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, + }); + + const dataSource = React.useMemo(() => { + return { + getRows, + }; + }, [getRows]); + + const initialState = React.useMemo( + () => ({ + ...props.initialState, + pagination: { + paginationModel: { + pageSize: 5, + }, + }, + }), + [props.initialState], + ); + return ( -
- +
+ +
+ +
); } diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx index ad49721f91c2..490ffd31f53a 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx @@ -6,24 +6,10 @@ import { GridToolbar, GridDataSourceCache, } from '@mui/x-data-grid-pro'; -import { createDummyDataSource } from '@mui/x-data-grid-generator'; +import Button from '@mui/material/Button'; +import { useDemoDataSource } from '@mui/x-data-grid-generator'; import { QueryClient } from '@tanstack/query-core'; -const [dataSource, props] = createDummyDataSource({ - dataSet: 'Employee', - rowLength: 1000, - treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, -}); - -const initialState: GridInitialState = { - ...props.initialState, - pagination: { - paginationModel: { - pageSize: 5, - }, - }, -}; - const cacheInstance = new QueryClient({ defaultOptions: { queries: { @@ -35,17 +21,12 @@ const cacheInstance = new QueryClient({ const cache: GridDataSourceCache = { set: (key, value) => { cacheInstance.setQueryData(key, value); - console.log('setting cache', key, value); }, get: (key) => { - console.log('getting cache', key, cacheInstance.getQueryData(key)); return cacheInstance.getQueryData(key); }, - invalidate: (queryKey) => { - if (queryKey) { - cacheInstance.invalidateQueries({ queryKey }); - } - cacheInstance.invalidateQueries(); + clear: () => { + cacheInstance.clear(); }, }; @@ -54,21 +35,48 @@ const pageSizeOptions = [5, 10, 50]; export default function ServerSideTreeData() { const apiRef = useGridApiRef(); + const { getRows, ...props } = useDemoDataSource({ + dataSet: 'Employee', + rowLength: 1000, + treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, + }); + + const dataSource = React.useMemo(() => { + return { + getRows, + }; + }, [getRows]); + + const initialState: GridInitialState = React.useMemo( + () => ({ + ...props.initialState, + pagination: { + paginationModel: { + pageSize: 5, + }, + }, + }), + [props.initialState], + ); + return ( -
- +
+ +
+ +
); } diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview index 7e5643237f56..bb6dbf04bb1a 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview @@ -1,13 +1,16 @@ - \ No newline at end of file + +
+ +
\ No newline at end of file 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 1769dda598e9..2909e7ec5b85 100644 --- a/docs/pages/x/api/data-grid/data-grid-premium.json +++ b/docs/pages/x/api/data-grid/data-grid-premium.json @@ -68,6 +68,7 @@ }, "disableRowGrouping": { "type": { "name": "bool" }, "default": "false" }, "disableRowSelectionOnClick": { "type": { "name": "bool" }, "default": "false" }, + "disableServerSideCache": { "type": { "name": "bool" }, "default": "false" }, "disableVirtualization": { "type": { "name": "bool" }, "default": "false" }, "editMode": { "type": { "name": "enum", "description": "'cell'
| 'row'" }, @@ -533,10 +534,6 @@ "paginationModel": { "type": { "name": "shape", "description": "{ page: number, pageSize: number }" } }, - "paginationType": { - "type": { "name": "enum", "description": "'cursor'
| 'index'" }, - "default": "\"index\"" - }, "pinnedColumns": { "type": { "name": "object" } }, "pinnedRows": { "type": { 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 0be343432bd3..5a9cbed2d7f7 100644 --- a/docs/pages/x/api/data-grid/data-grid-pro.json +++ b/docs/pages/x/api/data-grid/data-grid-pro.json @@ -54,6 +54,7 @@ "default": "false (`!props.checkboxSelection` for MIT Data Grid)" }, "disableRowSelectionOnClick": { "type": { "name": "bool" }, "default": "false" }, + "disableServerSideCache": { "type": { "name": "bool" }, "default": "false" }, "disableVirtualization": { "type": { "name": "bool" }, "default": "false" }, "editMode": { "type": { "name": "enum", "description": "'cell'
| 'row'" }, @@ -476,10 +477,6 @@ "paginationModel": { "type": { "name": "shape", "description": "{ page: number, pageSize: number }" } }, - "paginationType": { - "type": { "name": "enum", "description": "'cursor'
| 'index'" }, - "default": "\"index\"" - }, "pinnedColumns": { "type": { "name": "object" } }, "pinnedRows": { "type": { diff --git a/docs/pages/x/api/data-grid/data-grid.json b/docs/pages/x/api/data-grid/data-grid.json index 30d8cd71ede4..ac8d1f022003 100644 --- a/docs/pages/x/api/data-grid/data-grid.json +++ b/docs/pages/x/api/data-grid/data-grid.json @@ -400,10 +400,6 @@ "paginationModel": { "type": { "name": "shape", "description": "{ page: number, pageSize: number }" } }, - "paginationType": { - "type": { "name": "enum", "description": "'cursor'
| 'index'" }, - "default": "\"index\"" - }, "processRowUpdate": { "type": { "name": "func" }, "signature": { diff --git a/docs/pages/x/api/data-grid/grid-api.md b/docs/pages/x/api/data-grid/grid-api.md index c1b13eb3e489..a966a3aeb275 100644 --- a/docs/pages/x/api/data-grid/grid-api.md +++ b/docs/pages/x/api/data-grid/grid-api.md @@ -28,16 +28,19 @@ import { GridApi } from '@mui/x-data-grid'; | addRowGroupingCriteria [](/x/introduction/licensing/#premium-plan) | (groupingCriteriaField: string, groupingIndex?: number) => void | Adds the field to the row grouping model. | | applySorting | () => void | Applies the current sort model to the rows. | | autosizeColumns | (options?: GridAutosizeOptions) => Promise<void> | Auto-size the columns of the grid based on the cells' content and the space available. | +| clearCache [](/x/introduction/licensing/#pro-plan) | () => void | Clears the cache. | | deleteFilterItem | (item: GridFilterItem) => void | Deletes a [GridFilterItem](/x/api/data-grid/grid-filter-item/). | | exportDataAsCsv | (options?: GridCsvExportOptions) => void | Downloads and exports a CSV of the grid's data. | | exportDataAsExcel [](/x/introduction/licensing/#premium-plan) | (options?: GridExcelExportOptions) => Promise<void> | Downloads and exports an Excel file of the grid's data. | | exportDataAsPrint | (options?: GridPrintExportOptions) => void | Print the grid's data. | | exportState | (params?: GridExportStateParams) => InitialState | Generates a serializable object containing the exportable parts of the DataGrid state.
These values can then be passed to the `initialState` prop or injected using the `restoreState` method. | | fetchRowChildren [](/x/introduction/licensing/#pro-plan) | (id: GridRowId) => void | Initiates the fetch of the children of a row. | +| fetchTopLevelRows [](/x/introduction/licensing/#pro-plan) | () => void | Fetches the top level rows. | | forceUpdate | () => void | Forces the grid to rerender. It's often used after a state update. | | getAllColumns | () => GridStateColDef[] | Returns an array of [GridColDef](/x/api/data-grid/grid-col-def/) containing all the column definitions. | | getAllGroupDetails | () => GridColumnGroupLookup | Returns the column group lookup. | | getAllRowIds | () => GridRowId[] | Gets the list of row ids. | +| getCacheData [](/x/introduction/licensing/#pro-plan) | (params: GridGetRowsParams) => unknown | Tries to search for some data in cache | | getCellElement | (id: GridRowId, field: string) => HTMLDivElement \| null | Gets the underlying DOM element for a cell at the given `id` and `field`. | | getCellMode | (id: GridRowId, field: string) => GridCellMode | Gets the mode of a cell. | | getCellParams | <R extends GridValidRowModel = any, V = unknown, F = V, N extends GridTreeNode = GridTreeNode>(id: GridRowId, field: string) => GridCellParams<R, V, F, N> | Gets the [GridCellParams](/x/api/data-grid/grid-cell-params/) object that is passed as argument in events. | @@ -98,6 +101,7 @@ import { GridApi } from '@mui/x-data-grid'; | selectRowRange [](/x/introduction/licensing/#pro-plan) | (range: { startId: GridRowId; endId: GridRowId }, isSelected?: boolean, resetSelection?: boolean) => void | Change the selection state of all the selectable rows in a range. | | selectRows [](/x/introduction/licensing/#pro-plan) | (ids: GridRowId[], isSelected?: boolean, resetSelection?: boolean) => void | Change the selection state of multiple rows. | | setAggregationModel [](/x/introduction/licensing/#premium-plan) | (model: GridAggregationModel) => void | Sets the aggregation model to the one given by `model`. | +| setCacheData [](/x/introduction/licensing/#pro-plan) | (params: GridGetRowsParams, data: GridGetRowsResponse) => void | Tries to search for some data in cache | | setCellFocus | (id: GridRowId, field: string) => void | Sets the focus to the cell at the given `id` and `field`. | | setCellSelectionModel [](/x/introduction/licensing/#premium-plan) | (newModel: GridCellSelectionModel) => void | Updates the selected cells to be those passed to the `newModel` argument.
Any cell already selected will be unselected. | | setChildrenFetched [](/x/introduction/licensing/#pro-plan) | (id: GridRowId, childrenFetched: boolean) => void | Set the fetched children state of a row. | 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 cc09d8cebb93..602e1e98d8ae 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 @@ -100,6 +100,9 @@ "disableRowSelectionOnClick": { "description": "If true, the selection on click on a row or cell is disabled." }, + "disableServerSideCache": { + "description": "If true, the server-side cache will be disabled." + }, "disableVirtualization": { "description": "If true, the virtualization is disabled." }, @@ -565,9 +568,6 @@ "paginationModel": { "description": "The pagination model of type GridPaginationModel which refers to current page and pageSize." }, - "paginationType": { - "description": "Server-side pagination could either be based on a page number or a cursor. Set it to 'index' if the pagination is based on a page number. Set it to 'cursor' if the pagination is based on a cursor. Only applicable when paginationMode is set to 'server'." - }, "pinnedColumns": { "description": "The column fields to display pinned to left or right." }, "pinnedRows": { "description": "Rows data to pin on top or bottom." }, "processRowUpdate": { 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 db51827ec4d7..adbdc4d8ecc7 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 @@ -88,6 +88,9 @@ "disableRowSelectionOnClick": { "description": "If true, the selection on click on a row or cell is disabled." }, + "disableServerSideCache": { + "description": "If true, the server-side cache will be disabled." + }, "disableVirtualization": { "description": "If true, the virtualization is disabled." }, @@ -511,9 +514,6 @@ "paginationModel": { "description": "The pagination model of type GridPaginationModel which refers to current page and pageSize." }, - "paginationType": { - "description": "Server-side pagination could either be based on a page number or a cursor. Set it to 'index' if the pagination is based on a page number. Set it to 'cursor' if the pagination is based on a cursor. Only applicable when paginationMode is set to 'server'." - }, "pinnedColumns": { "description": "The column fields to display pinned to left or right." }, "pinnedRows": { "description": "Rows data to pin on top or bottom." }, "processRowUpdate": { diff --git a/docs/translations/api-docs/data-grid/data-grid/data-grid.json b/docs/translations/api-docs/data-grid/data-grid/data-grid.json index 8a4fad34a01a..18b8f9aee4f1 100644 --- a/docs/translations/api-docs/data-grid/data-grid/data-grid.json +++ b/docs/translations/api-docs/data-grid/data-grid/data-grid.json @@ -416,9 +416,6 @@ "paginationModel": { "description": "The pagination model of type GridPaginationModel which refers to current page and pageSize." }, - "paginationType": { - "description": "Server-side pagination could either be based on a page number or a cursor. Set it to 'index' if the pagination is based on a page number. Set it to 'cursor' if the pagination is based on a cursor. Only applicable when paginationMode is set to 'server'." - }, "processRowUpdate": { "description": "Callback called before updating a row with new values in the row and cell editing.", "typeDescriptions": { diff --git a/packages/x-data-grid-generator/src/hooks/createDummyDataSource.ts b/packages/x-data-grid-generator/src/hooks/createDummyDataSource.ts deleted file mode 100644 index 383fab3a5e00..000000000000 --- a/packages/x-data-grid-generator/src/hooks/createDummyDataSource.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { - getGridDefaultColumnTypes, - GridRowModel, - GridGetRowsParams, - GridGetRowsResponse, - GridColDef, - GridInitialState, - GridDataSource, -} from '@mui/x-data-grid-pro'; -import { - UseDemoDataOptions, - getColumnsFromOptions, - getInitialState, - extrapolateSeed, -} from './useDemoData'; -import { getRealGridData, GridDemoData } from '../services/real-data-service'; -import { addTreeDataOptionsToDemoData } from '../services/tree-data-generator'; -import { - loadServerRows, - processTreeDataRows, - DEFAULT_DATASET_OPTIONS, - DEFAULT_SERVER_OPTIONS, -} from './serverUtils'; -import type { ServerOptions } from './serverUtils'; - -type DataSourceRelatedProps = { - columns: GridColDef[]; - initialState: GridInitialState; - getGroupKey?: (row: GridRowModel) => string; - hasChildren?: (row: GridRowModel) => boolean; - getChildrenCount?: (row: GridRowModel) => number; -}; - -type CreateDummyDataSourceResponse = [dataSource: GridDataSource, props: DataSourceRelatedProps]; - -let data: GridDemoData; -let isDataFetched = false; -let previousRowLength: number; - -export const createDummyDataSource = ( - dataSetOptions?: Partial, - serverOptions?: ServerOptions, -): CreateDummyDataSourceResponse => { - const dataSetOptionsWithDefault = { ...DEFAULT_DATASET_OPTIONS, ...dataSetOptions }; - const serverOptionsWithDefault = { ...DEFAULT_SERVER_OPTIONS, ...serverOptions }; - - const columns = getColumnsFromOptions(dataSetOptionsWithDefault); - const initialState = getInitialState(dataSetOptionsWithDefault, columns); - - const defaultColDef = getGridDefaultColumnTypes(); - const columnsWithDefaultColDef: GridColDef[] = columns.map((column) => ({ - ...defaultColDef[column.type || 'string'], - ...column, - })); - - const isTreeData = dataSetOptionsWithDefault.treeData?.groupingField != null; - - let getGroupKey; - let hasChildren; - let getChildrenCount; - if (isTreeData) { - getGroupKey = (row: GridRowModel): string => - row[dataSetOptionsWithDefault.treeData!.groupingField!]; - hasChildren = (row: GridRowModel): boolean => row.hasChildren; - getChildrenCount = (row: GridRowModel): number => row.descendantCount; - } - const getRows = async (params: GridGetRowsParams): Promise => { - if (!isDataFetched || previousRowLength !== dataSetOptionsWithDefault.rowLength) { - // Fetch all the data on the first request - const rowLength = dataSetOptionsWithDefault.rowLength; - if (rowLength > 1000) { - data = await getRealGridData(1000, columns); - data = await extrapolateSeed(rowLength, data); - } else { - data = await getRealGridData(rowLength, columns); - } - if (isTreeData) { - data = addTreeDataOptionsToDemoData(data, dataSetOptionsWithDefault.treeData); - } - isDataFetched = true; - previousRowLength = rowLength; - } - - let getRowsResponse: GridGetRowsResponse; - - if (isTreeData /* || TODO: `isRowGrouping` */) { - const { rows, rootRowCount } = await processTreeDataRows( - data.rows, - params, - serverOptionsWithDefault, - columnsWithDefaultColDef, - ); - - getRowsResponse = { - rows: rows.slice().map((row) => ({ ...row, path: undefined })), - rowCount: rootRowCount, - }; - } else { - // plain data - const { returnedRows, nextCursor, totalRowCount } = await loadServerRows( - data.rows, - { ...params, ...params.paginationModel }, - serverOptionsWithDefault, - columnsWithDefaultColDef, - ); - getRowsResponse = { rows: returnedRows, rowCount: totalRowCount, pageInfo: { nextCursor } }; - } - - return new Promise((resolve) => { - resolve(getRowsResponse); - }); - }; - - return [ - { getRows }, - { - columns: columnsWithDefaultColDef, - initialState, - getGroupKey, - hasChildren, - getChildrenCount, - }, - ]; -}; diff --git a/packages/x-data-grid-generator/src/hooks/index.ts b/packages/x-data-grid-generator/src/hooks/index.ts index 0facf9eaab39..33d9e5ebd232 100644 --- a/packages/x-data-grid-generator/src/hooks/index.ts +++ b/packages/x-data-grid-generator/src/hooks/index.ts @@ -2,5 +2,5 @@ export * from './useDemoData'; export * from './useBasicDemoData'; export * from './useMovieData'; export * from './useQuery'; -export * from './createDummyDataSource'; +export * from './useDemoDataSource'; export { loadServerRows } from './serverUtils'; diff --git a/packages/x-data-grid-generator/src/hooks/serverUtils.ts b/packages/x-data-grid-generator/src/hooks/serverUtils.ts index f5f3a4c0602c..c6b549fa57b7 100644 --- a/packages/x-data-grid-generator/src/hooks/serverUtils.ts +++ b/packages/x-data-grid-generator/src/hooks/serverUtils.ts @@ -386,6 +386,9 @@ const getTreeDataFilteredRows: GetTreeDataFilteredRows = ( }); const includedPaths = new Set(); + filteredRows.forEach((row) => { + includedPaths.add(row.path.join(',')); + }); const missingChildren: GridValidRowModel[] = []; @@ -462,6 +465,7 @@ export const processTreeDataRows = ( // find direct children referring to the `parentPath` const childRows = findTreeDataRowChildren(filteredRows, queryOptions.groupKeys); + let childRowsWithDescendantCounts = childRows.map((row) => { const descendants = findTreeDataRowChildren(filteredRows, row[pathKey], pathKey, -1); const descendantCount = descendants.length; diff --git a/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts b/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts new file mode 100644 index 000000000000..318ba338dc0e --- /dev/null +++ b/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts @@ -0,0 +1,184 @@ +import * as React from 'react'; +import { + getGridDefaultColumnTypes, + GridRowModel, + GridGetRowsParams, + GridGetRowsResponse, + GridColDef, + GridInitialState, + GridColumnVisibilityModel, + GridDataSource, +} from '@mui/x-data-grid-pro'; +import { UseDemoDataOptions, getColumnsFromOptions, extrapolateSeed } from './useDemoData'; +import { GridColDefGenerator } from '../services/gridColDefGenerator'; +import { getRealGridData, GridDemoData } from '../services/real-data-service'; +import { addTreeDataOptionsToDemoData } from '../services/tree-data-generator'; +import { + loadServerRows, + processTreeDataRows, + DEFAULT_DATASET_OPTIONS, + DEFAULT_SERVER_OPTIONS, +} from './serverUtils'; +import type { ServerOptions } from './serverUtils'; + +type CreateDummyDataSourceResponse = { + columns: GridColDef[]; + initialState: GridInitialState; + getGroupKey?: (row: GridRowModel) => string; + hasChildren?: (row: GridRowModel) => boolean; + getChildrenCount?: (row: GridRowModel) => number; + getRows: GridDataSource['getRows']; +}; + +const getInitialState = (columns: GridColDefGenerator[], groupingField?: string) => { + const columnVisibilityModel: GridColumnVisibilityModel = {}; + columns.forEach((col) => { + if (col.hide) { + columnVisibilityModel[col.field] = false; + } + }); + + if (groupingField) { + columnVisibilityModel![groupingField] = false; + } + + return { columns: { columnVisibilityModel } }; +}; + +const defaultColDef = getGridDefaultColumnTypes(); + +export const useDemoDataSource = ( + dataSetOptions?: Partial, + serverOptions?: ServerOptions, +): CreateDummyDataSourceResponse => { + const [data, setData] = React.useState(); + const previousRowLength = React.useRef(); + const options = { ...DEFAULT_DATASET_OPTIONS, ...dataSetOptions }; + + const columns = React.useMemo(() => { + return getColumnsFromOptions({ + dataSet: options.dataSet, + editable: options.editable, + maxColumns: options.maxColumns, + visibleFields: options.visibleFields, + }); + }, [options.dataSet, options.editable, options.maxColumns, options.visibleFields]); + + const initialState = React.useMemo( + () => getInitialState(columns, options.treeData?.groupingField), + [columns, options.treeData?.groupingField], + ); + + const columnsWithDefaultColDef: GridColDef[] = React.useMemo( + () => + columns.map((column) => ({ + ...defaultColDef[column.type || 'string'], + ...column, + })), + [columns], + ); + + const isTreeData = options.treeData?.groupingField != null; + + const getGroupKey = React.useMemo(() => { + if (isTreeData) { + return (row: GridRowModel): string => row[options.treeData!.groupingField!]; + } + return undefined; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [options.treeData?.groupingField, isTreeData]); + + const hasChildren = React.useMemo(() => { + if (isTreeData) { + return (row: GridRowModel): boolean => row.hasChildren; + } + return undefined; + }, [isTreeData]); + + const getChildrenCount = React.useMemo(() => { + if (isTreeData) { + return (row: GridRowModel): number => row.descendantCount; + } + return undefined; + }, [isTreeData]); + + React.useEffect(() => { + const fetchData = async () => { + // Fetch all the data on the first request + let rowData; + const rowLength = options.rowLength; + if (rowLength > 1000) { + rowData = await getRealGridData(1000, columns); + rowData = await extrapolateSeed(rowLength, rowData); + } else { + rowData = await getRealGridData(rowLength, columns); + } + if (isTreeData) { + rowData = addTreeDataOptionsToDemoData(rowData, { + maxDepth: options.treeData?.maxDepth, + groupingField: options.treeData?.groupingField, + averageChildren: options.treeData?.averageChildren, + }); + } + setData(rowData); + previousRowLength.current = rowLength; + }; + fetchData(); + }, [ + columns, + isTreeData, + options.rowLength, + options.treeData?.maxDepth, + options.treeData?.groupingField, + options.treeData?.averageChildren, + ]); + + const getRows = React.useCallback( + async (params: GridGetRowsParams): Promise => { + if (!data) { + return new Promise((resolve) => { + resolve({ rows: [], rowCount: 0 }); + }); + } + let getRowsResponse: GridGetRowsResponse; + const serverOptionsWithDefault = { ...DEFAULT_SERVER_OPTIONS, ...serverOptions }; + + if (isTreeData /* || TODO: `isRowGrouping` */) { + const { rows, rootRowCount } = await processTreeDataRows( + data.rows, + params, + serverOptionsWithDefault, + columnsWithDefaultColDef, + ); + + getRowsResponse = { + rows: rows.slice().map((row) => ({ ...row, path: undefined })), + rowCount: rootRowCount, + }; + } else { + // plain data + const { returnedRows, nextCursor, totalRowCount } = await loadServerRows( + data.rows, + { ...params, ...params.paginationModel }, + serverOptionsWithDefault, + columnsWithDefaultColDef, + ); + getRowsResponse = { rows: returnedRows, rowCount: totalRowCount, pageInfo: { nextCursor } }; + } + + return new Promise((resolve) => { + resolve(getRowsResponse); + }); + }, + [data, columnsWithDefaultColDef, isTreeData, serverOptions], + ); + + return { + columns: columnsWithDefaultColDef, + initialState, + getGroupKey, + hasChildren, + getChildrenCount, + getRows, + }; +}; diff --git a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx index 4a9c2ea6b203..e4ef32bcb77f 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx @@ -288,6 +288,11 @@ DataGridPremiumRaw.propTypes = { * @default false */ disableRowSelectionOnClick: PropTypes.bool, + /** + * If `true`, the server-side cache will be disabled. + * @default false + */ + disableServerSideCache: PropTypes.bool, /** * If `true`, the virtualization is disabled. * @default false @@ -852,14 +857,6 @@ DataGridPremiumRaw.propTypes = { page: PropTypes.number.isRequired, pageSize: PropTypes.number.isRequired, }), - /** - * Server-side pagination could either be based on a page number or a cursor. - * Set it to 'index' if the pagination is based on a page number. - * Set it to 'cursor' if the pagination is based on a cursor. - * Only applicable when `paginationMode` is set to 'server'. - * @default "index" - */ - paginationType: PropTypes.oneOf(['cursor', 'index']), /** * The column fields to display pinned to left or right. */ @@ -1029,8 +1026,8 @@ DataGridPremiumRaw.propTypes = { updateRow: PropTypes.func, }), unstable_dataSourceCache: PropTypes.shape({ + clear: PropTypes.func.isRequired, get: PropTypes.func.isRequired, - invalidate: PropTypes.func.isRequired, set: PropTypes.func.isRequired, }), } as any; diff --git a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx index 6b4de3b03bf5..7e6100bc8683 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx @@ -66,6 +66,8 @@ import { virtualizationStateInitializer, useGridVirtualization, useGridServerSideTreeDataPreProcessors, + useGridDataSource, + useGridServerSideCache, } from '@mui/x-data-grid-pro/internals'; import { GridApiPremium, GridPrivateApiPremium } from '../models/gridApiPremium'; import { DataGridPremiumProcessedProps } from '../models/dataGridPremiumProps'; @@ -176,6 +178,8 @@ export const useDataGridPremiumComponent = ( useGridDimensions(apiRef, props); useGridEvents(apiRef, props); useGridStatePersistence(apiRef); + useGridDataSource(apiRef, props); + useGridServerSideCache(apiRef, props); useGridVirtualization(apiRef, props); return apiRef; diff --git a/packages/x-data-grid-premium/src/models/gridApiPremium.ts b/packages/x-data-grid-premium/src/models/gridApiPremium.ts index beb733915ec1..3a15639a0e68 100644 --- a/packages/x-data-grid-premium/src/models/gridApiPremium.ts +++ b/packages/x-data-grid-premium/src/models/gridApiPremium.ts @@ -9,6 +9,7 @@ import { GridColumnReorderApi, GridRowProApi, GridDataSourceApi, + GridServerSideCacheApi, } from '@mui/x-data-grid-pro'; import { GridInitialStatePremium, GridStatePremium } from './gridStatePremium'; import type { GridRowGroupingApi, GridExcelExportApi, GridAggregationApi } from '../hooks'; @@ -29,6 +30,7 @@ export interface GridApiPremium GridAggregationApi, GridRowPinningApi, GridDataSourceApi, + GridServerSideCacheApi, GridCellSelectionApi, // APIs that are private in Community plan, but public in Pro and Premium plans GridRowMultiSelectionApi, diff --git a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx index 956900e442b6..5441209c4385 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx @@ -251,6 +251,11 @@ DataGridProRaw.propTypes = { * @default false */ disableRowSelectionOnClick: PropTypes.bool, + /** + * If `true`, the server-side cache will be disabled. + * @default false + */ + disableServerSideCache: PropTypes.bool, /** * If `true`, the virtualization is disabled. * @default false @@ -769,14 +774,6 @@ DataGridProRaw.propTypes = { page: PropTypes.number.isRequired, pageSize: PropTypes.number.isRequired, }), - /** - * Server-side pagination could either be based on a page number or a cursor. - * Set it to 'index' if the pagination is based on a page number. - * Set it to 'cursor' if the pagination is based on a cursor. - * Only applicable when `paginationMode` is set to 'server'. - * @default "index" - */ - paginationType: PropTypes.oneOf(['cursor', 'index']), /** * The column fields to display pinned to left or right. */ @@ -929,8 +926,8 @@ DataGridProRaw.propTypes = { updateRow: PropTypes.func, }), unstable_dataSourceCache: PropTypes.shape({ + clear: PropTypes.func.isRequired, get: PropTypes.func.isRequired, - invalidate: PropTypes.func.isRequired, set: PropTypes.func.isRequired, }), } as any; diff --git a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx index ddbca1fe24d6..134f979457ed 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx @@ -79,6 +79,7 @@ import { } from '../hooks/features/rowPinning/useGridRowPinning'; import { useGridRowPinningPreProcessors } from '../hooks/features/rowPinning/useGridRowPinningPreProcessors'; import { useGridDataSource } from '../hooks/features/serverSideData/useGridDataSource'; +import { useGridServerSideCache } from '../hooks/features/serverSideData/useGridServerSideCache'; export const useDataGridProComponent = ( inputApiRef: React.MutableRefObject | undefined, @@ -161,6 +162,7 @@ export const useDataGridProComponent = ( useGridStatePersistence(apiRef); useGridVirtualization(apiRef, props); useGridDataSource(apiRef, props); + useGridServerSideCache(apiRef, props); return apiRef; }; diff --git a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts index c10c6ff6f491..5f4fee03d7f6 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts +++ b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts @@ -23,6 +23,7 @@ export const GET_DATA_GRID_PRO_PROPS_DEFAULT_VALUES: ( themedProps: GetDataGridProPropsDefaultValues, ) => DataGridProPropsWithDefaultValue = (themedProps) => ({ ...DATA_GRID_PROPS_DEFAULT_VALUES, + disableServerSideCache: false, scrollEndThreshold: 80, treeData: false, defaultGroupingExpansionDepth: 0, @@ -33,7 +34,7 @@ export const GET_DATA_GRID_PRO_PROPS_DEFAULT_VALUES: ( disableChildrenFiltering: false, disableChildrenSorting: false, rowReordering: false, - rowsLoadingMode: themedProps.unstable_dataSource?.getRows ? 'server' : 'client', + rowsLoadingMode: 'client', getDetailPanelHeight: () => 500, headerFilters: false, filterMode: themedProps.unstable_dataSource?.getRows ? 'server' : 'client', 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 d0a167e8f3ba..ff5e58de2cb7 100644 --- a/packages/x-data-grid-pro/src/hooks/features/index.ts +++ b/packages/x-data-grid-pro/src/hooks/features/index.ts @@ -5,4 +5,4 @@ export * from './rowReorder'; export * from './treeData'; export * from './detailPanel'; export * from './rowPinning'; -export * from './serverSideData/dataSourceApi'; +export * from './serverSideData/serverSideInterfaces'; diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/dataSourceApi.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/dataSourceApi.ts deleted file mode 100644 index 97deadf0830f..000000000000 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/dataSourceApi.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { GridRowId } from '@mui/x-data-grid'; - -/** - * The dataSource API interface that is available in the grid [[apiRef]]. - */ -export interface GridDataSourceApi { - /** - * Initiates the fetch of the children of a row. - * @param {string} id The id of the rowNode belonging to the group to be fetched. - */ - fetchRowChildren: (id: GridRowId) => void; - /** - * Set the loading state of a row. - * @param {string} id The id of the rowNode. - * @param {boolean} loading The loading state to set. - */ - setRowLoading: (id: GridRowId, loading: boolean) => void; - /** - * Set the fetched children state of a row. - * @param {string} id The id of the rowNode. - * @param {boolean} childrenFetched The children to set. - */ - setChildrenFetched: (id: GridRowId, childrenFetched: boolean) => void; -} diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts new file mode 100644 index 000000000000..d99088305473 --- /dev/null +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts @@ -0,0 +1,51 @@ +import { GridRowId } from '@mui/x-data-grid'; +import { GridGetRowsParams, GridGetRowsResponse } from '../../../models/gridDataSource'; + +/** + * The dataSource API interface that is available in the grid [[apiRef]]. + */ +export interface GridDataSourceApi { + /** + * Initiates the fetch of the children of a row. + * @param {string} id The id of the rowNode belonging to the group to be fetched. + */ + fetchRowChildren: (id: GridRowId) => void; + /** + * Set the loading state of a row. + * @param {string} id The id of the rowNode. + * @param {boolean} loading The loading state to set. + */ + setRowLoading: (id: GridRowId, loading: boolean) => void; + /** + * Set the fetched children state of a row. + * @param {string} id The id of the rowNode. + * @param {boolean} childrenFetched The children to set. + */ + setChildrenFetched: (id: GridRowId, childrenFetched: boolean) => void; + /** + * Fetches the top level rows. + */ + fetchTopLevelRows: () => void; +} + +/** + * The server side cache API interface that is available in the grid [[apiRef]]. + */ +export interface GridServerSideCacheApi { + /** + * Tries to search for some data in cache + * @param {GridGetRowsParams} params The params of type [[GridGetRowsParams]]. + * @returns {GridGetRowsResponse | null} The data of type [[GridGetRowsResponse]] or `null` for cache miss. + */ + getCacheData: (params: GridGetRowsParams) => unknown; + /** + * Tries to search for some data in cache + * @param {GridGetRowsParams} params The params of type [[GridGetRowsParams]]. + * @param {GridGetRowsResponse} data The data of type [[GridGetRowsResponse]]. + */ + setCacheData: (params: GridGetRowsParams, data: GridGetRowsResponse) => void; + /** + * Clears the cache. + */ + clearCache: () => void; +} diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts index d6fb6ac1f93f..57349215a0af 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts @@ -15,9 +15,8 @@ import { GridGetRowsParams, GridGetRowsResponse, GridDataSource, - GridDataSourceCache, } from '../../../models/gridDataSource'; -import { GridDataSourceApi } from './dataSourceApi'; +import { GridDataSourceApi } from './serverSideInterfaces'; const computeStartEnd = (paginationModel: GridPaginationModel) => { const start = paginationModel.page * paginationModel.pageSize; @@ -25,19 +24,6 @@ const computeStartEnd = (paginationModel: GridPaginationModel) => { return { start, end }; }; -const noop = () => undefined; - -const defaultCache: GridDataSourceCache = { - // TODO: Implement an internal cache - set: noop, - get: noop, - invalidate: noop, -}; - -const getQueryKey = (params: GridGetRowsParams) => { - return [params.paginationModel, params.sortModel, params.filterModel, params.groupKeys]; -}; - const fetchRowsWithError = async ( getRows: GridDataSource['getRows'], inputParams: GridGetRowsParams, @@ -62,18 +48,9 @@ export const useGridDataSource = ( privateApiRef: React.MutableRefObject, props: Pick< DataGridProProcessedProps, - | 'unstable_dataSource' - | 'sortingMode' - | 'filterMode' - | 'paginationMode' - | 'treeData' - | 'unstable_dataSourceCache' + 'unstable_dataSource' | 'sortingMode' | 'filterMode' | 'paginationMode' | 'treeData' >, ): void => { - const cache = React.useRef( - props.unstable_dataSourceCache || defaultCache, - ).current; - const getInputParams = React.useCallback( (additionalParams?: Partial): GridGetRowsParams => { const paginationModel = gridPaginationModelSelector(privateApiRef); @@ -96,9 +73,12 @@ export const useGridDataSource = ( if (!getRows) { return; } + const inputParams = getInputParams(); - const cachedData = cache.get(getQueryKey(inputParams)) as GridGetRowsResponse | undefined; - if (cachedData) { + const cachedData = privateApiRef.current.getCacheData(inputParams) as + | GridGetRowsResponse + | undefined; + if (cachedData != null) { const rows = cachedData.rows; privateApiRef.current.caches.groupKeys = []; privateApiRef.current.setRows(rows); @@ -118,8 +98,7 @@ export const useGridDataSource = ( params: inputParams, response: getRowsResponse, }); - const queryKey = getQueryKey(inputParams); - cache.set(queryKey, getRowsResponse); + privateApiRef.current.setCacheData(inputParams, getRowsResponse); if (getRowsResponse.rowCount) { privateApiRef.current.setRowCount(getRowsResponse.rowCount); } @@ -128,7 +107,7 @@ export const useGridDataSource = ( privateApiRef.current.setLoading(false); // TODO: handle cursor based pagination } - }, [cache, getInputParams, privateApiRef, props.unstable_dataSource?.getRows]); + }, [getInputParams, privateApiRef, props.unstable_dataSource]); const fetchRowChildren = React.useCallback( async (id) => { @@ -146,9 +125,11 @@ export const useGridDataSource = ( } const inputParams = getInputParams({ groupKeys: rowNode.path }); - const cachedData = cache.get(getQueryKey(inputParams)) as GridGetRowsResponse | undefined; + const cachedData = privateApiRef.current.getCacheData(inputParams) as + | GridGetRowsResponse + | undefined; - if (cachedData) { + if (cachedData != null) { const rows = cachedData.rows; privateApiRef.current.caches.groupKeys = rowNode.path; privateApiRef.current.updateRows(rows); @@ -163,8 +144,7 @@ export const useGridDataSource = ( privateApiRef.current.setRowLoading(id, true); } const getRowsResponse = await fetchRowsWithError(getRows, inputParams); - const queryKey = getQueryKey(inputParams); - cache.set(queryKey, getRowsResponse); + privateApiRef.current.setCacheData(inputParams, getRowsResponse); if (getRowsResponse.rowCount) { privateApiRef.current.setRowCount(getRowsResponse.rowCount); } @@ -175,7 +155,7 @@ export const useGridDataSource = ( privateApiRef.current.setRowLoading(id, false); } }, - [cache, getInputParams, privateApiRef, props.treeData, props.unstable_dataSource?.getRows], + [getInputParams, privateApiRef, props.treeData, props.unstable_dataSource?.getRows], ); const setRowLoading = React.useCallback( @@ -224,6 +204,7 @@ export const useGridDataSource = ( fetchRowChildren, setRowLoading, setChildrenFetched, + fetchTopLevelRows, }; useGridApiMethod(privateApiRef, dataSourceApi, 'public'); @@ -251,6 +232,9 @@ export const useGridDataSource = ( * EFFECTS */ React.useEffect(() => { - fetchTopLevelRows(); - }, [props.unstable_dataSource, privateApiRef, fetchTopLevelRows]); + if (props.unstable_dataSource) { + privateApiRef.current.clearCache(); + privateApiRef.current.fetchTopLevelRows(); + } + }, [privateApiRef, props.unstable_dataSource]); }; diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridServerSideCache.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridServerSideCache.ts new file mode 100644 index 000000000000..6c1c2dfb47c9 --- /dev/null +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridServerSideCache.ts @@ -0,0 +1,83 @@ +import * as React from 'react'; +import { useGridApiMethod } from '@mui/x-data-grid'; +import { GridPrivateApiPro } from '../../../models/gridApiPro'; +import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; +import { + GridGetRowsParams, + GridGetRowsResponse, + GridDataSourceCache, +} from '../../../models/gridDataSource'; +import { GridServerSideCacheApi } from './serverSideInterfaces'; + +const noop = () => undefined; + +const defaultCache: GridDataSourceCache = { + // TODO: Implement an internal cache + set: noop, + get: noop, + clear: noop, +}; + +const getQueryKey = (params: GridGetRowsParams) => { + return [params.paginationModel, params.sortModel, params.filterModel, params.groupKeys]; +}; + +export const useGridServerSideCache = ( + privateApiRef: React.MutableRefObject, + props: Pick< + DataGridProProcessedProps, + 'unstable_dataSource' | 'disableServerSideCache' | 'unstable_dataSourceCache' + >, +): void => { + const cacheRef = React.useRef( + props.unstable_dataSourceCache || defaultCache, + ); + + const getCacheData = React.useCallback( + (params: GridGetRowsParams) => { + if (props.disableServerSideCache) { + return undefined; + } + const queryKey = getQueryKey(params); + return cacheRef.current.get(queryKey); + }, + [props.disableServerSideCache], + ); + + const setCacheData = React.useCallback( + (params: GridGetRowsParams, data: GridGetRowsResponse) => { + if (props.disableServerSideCache) { + return; + } + const queryKey = getQueryKey(params); + cacheRef.current.set(queryKey, data); + }, + [props.disableServerSideCache], + ); + + const clearCache = React.useCallback(() => { + if (props.disableServerSideCache) { + return; + } + cacheRef.current.clear(); + }, [props.disableServerSideCache]); + + const serverSideCacheApi: GridServerSideCacheApi = { + getCacheData, + setCacheData, + clearCache, + }; + + useGridApiMethod(privateApiRef, serverSideCacheApi, 'public'); + + const isFirstRender = React.useRef(true); + React.useEffect(() => { + if (isFirstRender.current) { + isFirstRender.current = false; + return; + } + if (props.unstable_dataSourceCache) { + cacheRef.current = props.unstable_dataSourceCache; + } + }, [props.unstable_dataSourceCache]); +}; diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx index 21649fc6c018..4aeb18b401a5 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx @@ -152,7 +152,7 @@ export const useGridServerSideTreeDataPreProcessors = ( const onDuplicatePath: GridTreePathDuplicateHandler = (firstId, secondId, path) => { throw new Error( [ - 'MUI X: The values returned by `getGroupKey` for all the siblings should be unique.', + 'MUI X: The values returned by `getGroupKey` for all the sibling rows should be unique.', `The rows with id #${firstId} and #${secondId} have the same.`, `Path: ${JSON.stringify(path.map((step) => step.key))}.`, ].join('\n'), diff --git a/packages/x-data-grid-pro/src/internals/index.ts b/packages/x-data-grid-pro/src/internals/index.ts index 192b1d9460f0..f9ef2b6a1556 100644 --- a/packages/x-data-grid-pro/src/internals/index.ts +++ b/packages/x-data-grid-pro/src/internals/index.ts @@ -37,6 +37,8 @@ export { } from '../hooks/features/rowPinning/useGridRowPinningPreProcessors'; export { useGridLazyLoader } from '../hooks/features/lazyLoader/useGridLazyLoader'; export { useGridLazyLoaderPreProcessors } from '../hooks/features/lazyLoader/useGridLazyLoaderPreProcessors'; +export { useGridDataSource } from '../hooks/features/serverSideData/useGridDataSource'; +export { useGridServerSideCache } from '../hooks/features/serverSideData/useGridServerSideCache'; export type { GridExperimentalProFeatures, diff --git a/packages/x-data-grid-pro/src/models/dataGridProProps.ts b/packages/x-data-grid-pro/src/models/dataGridProProps.ts index b97c283b5a9f..c47ebfe7f0f3 100644 --- a/packages/x-data-grid-pro/src/models/dataGridProProps.ts +++ b/packages/x-data-grid-pro/src/models/dataGridProProps.ts @@ -108,6 +108,11 @@ export interface DataGridProPropsWithDefaultValue void; get: (key: any[]) => unknown; - invalidate: (key?: any[]) => void; + clear: () => void; } diff --git a/packages/x-data-grid/src/DataGrid/DataGrid.tsx b/packages/x-data-grid/src/DataGrid/DataGrid.tsx index 998b9625497a..3ae35b011893 100644 --- a/packages/x-data-grid/src/DataGrid/DataGrid.tsx +++ b/packages/x-data-grid/src/DataGrid/DataGrid.tsx @@ -644,14 +644,6 @@ DataGridRaw.propTypes = { page: PropTypes.number.isRequired, pageSize: PropTypes.number.isRequired, }), - /** - * Server-side pagination could either be based on a page number or a cursor. - * Set it to 'index' if the pagination is based on a page number. - * Set it to 'cursor' if the pagination is based on a cursor. - * Only applicable when `paginationMode` is set to 'server'. - * @default "index" - */ - paginationType: PropTypes.oneOf(['cursor', 'index']), /** * Callback called before updating a row with new values in the row and cell editing. * @template R diff --git a/packages/x-data-grid/src/models/gridPaginationProps.ts b/packages/x-data-grid/src/models/gridPaginationProps.ts index 9eaef049c4c8..543323ac86b1 100644 --- a/packages/x-data-grid/src/models/gridPaginationProps.ts +++ b/packages/x-data-grid/src/models/gridPaginationProps.ts @@ -11,5 +11,3 @@ export interface GridPaginationModel { */ page: number; } - -export type GridPaginationType = 'index' | 'cursor'; diff --git a/scripts/x-data-grid-generator.exports.json b/scripts/x-data-grid-generator.exports.json index ddfce3e0f5f5..18dc8b0ac178 100644 --- a/scripts/x-data-grid-generator.exports.json +++ b/scripts/x-data-grid-generator.exports.json @@ -1,6 +1,5 @@ [ { "name": "ColumnsOptions", "kind": "Interface" }, - { "name": "createDummyDataSource", "kind": "Variable" }, { "name": "createFakeServer", "kind": "Variable" }, { "name": "currencyPairs", "kind": "Variable" }, { "name": "DemoDataReturnType", "kind": "TypeAlias" }, @@ -78,5 +77,6 @@ { "name": "useBasicDemoData", "kind": "Variable" }, { "name": "useDemoData", "kind": "Variable" }, { "name": "UseDemoDataOptions", "kind": "Interface" }, + { "name": "useDemoDataSource", "kind": "Variable" }, { "name": "useMovieData", "kind": "Variable" } ] diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 979ee26565c5..064379c1e5f3 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -421,7 +421,6 @@ { "name": "gridPaginationRowRangeSelector", "kind": "Variable" }, { "name": "gridPaginationSelector", "kind": "Variable" }, { "name": "GridPaginationState", "kind": "Interface" }, - { "name": "GridPaginationType", "kind": "TypeAlias" }, { "name": "GridPanel", "kind": "Variable" }, { "name": "gridPanelClasses", "kind": "Variable" }, { "name": "GridPanelClasses", "kind": "Interface" }, @@ -539,6 +538,7 @@ { "name": "GridSearchIcon", "kind": "Variable" }, { "name": "GridSelectedRowCount", "kind": "Variable" }, { "name": "GridSeparatorIcon", "kind": "Variable" }, + { "name": "GridServerSideCacheApi", "kind": "Interface" }, { "name": "GridServerSideGroupNode", "kind": "Interface" }, { "name": "GridSignature", "kind": "Enum" }, { "name": "GridSingleSelectColDef", "kind": "Interface" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index d88c07d26497..cfd324af19a6 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -385,7 +385,6 @@ { "name": "gridPaginationRowRangeSelector", "kind": "Variable" }, { "name": "gridPaginationSelector", "kind": "Variable" }, { "name": "GridPaginationState", "kind": "Interface" }, - { "name": "GridPaginationType", "kind": "TypeAlias" }, { "name": "GridPanel", "kind": "Variable" }, { "name": "gridPanelClasses", "kind": "Variable" }, { "name": "GridPanelClasses", "kind": "Interface" }, @@ -493,6 +492,7 @@ { "name": "GridSearchIcon", "kind": "Variable" }, { "name": "GridSelectedRowCount", "kind": "Variable" }, { "name": "GridSeparatorIcon", "kind": "Variable" }, + { "name": "GridServerSideCacheApi", "kind": "Interface" }, { "name": "GridServerSideGroupNode", "kind": "Interface" }, { "name": "GridSignature", "kind": "Enum" }, { "name": "GridSingleSelectColDef", "kind": "Interface" }, diff --git a/scripts/x-data-grid.exports.json b/scripts/x-data-grid.exports.json index 72bd0598ba4a..3be9261983f9 100644 --- a/scripts/x-data-grid.exports.json +++ b/scripts/x-data-grid.exports.json @@ -351,7 +351,6 @@ { "name": "gridPaginationRowRangeSelector", "kind": "Variable" }, { "name": "gridPaginationSelector", "kind": "Variable" }, { "name": "GridPaginationState", "kind": "Interface" }, - { "name": "GridPaginationType", "kind": "TypeAlias" }, { "name": "GridPanel", "kind": "Variable" }, { "name": "gridPanelClasses", "kind": "Variable" }, { "name": "GridPanelClasses", "kind": "Interface" }, From e1af3b95de6ef8636726c1ee6e2554ef586fd4fc Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Sun, 21 Apr 2024 22:04:22 +0500 Subject: [PATCH 05/90] Run docs:api script --- docs/pages/x/api/data-grid/grid-api.json | 42 +++++++++++++++++-- .../api-docs/data-grid/grid-api.json | 9 +++- .../src/hooks/features/pagination/index.ts | 1 + scripts/x-data-grid-premium.exports.json | 1 + scripts/x-data-grid-pro.exports.json | 1 + scripts/x-data-grid.exports.json | 1 + 6 files changed, 50 insertions(+), 5 deletions(-) diff --git a/docs/pages/x/api/data-grid/grid-api.json b/docs/pages/x/api/data-grid/grid-api.json index a4fc87914cd1..fd31b8ad562f 100644 --- a/docs/pages/x/api/data-grid/grid-api.json +++ b/docs/pages/x/api/data-grid/grid-api.json @@ -19,6 +19,11 @@ "type": { "description": "(options?: GridAutosizeOptions) => Promise<void>" }, "required": true }, + "clearCache": { + "type": { "description": "() => void" }, + "required": true, + "isProPlan": true + }, "deleteFilterItem": { "type": { "description": "(item: GridFilterItem) => void" }, "required": true @@ -40,6 +45,16 @@ "type": { "description": "(params?: GridExportStateParams) => InitialState" }, "required": true }, + "fetchRowChildren": { + "type": { "description": "(id: GridRowId) => void" }, + "required": true, + "isProPlan": true + }, + "fetchTopLevelRows": { + "type": { "description": "() => void" }, + "required": true, + "isProPlan": true + }, "forceUpdate": { "type": { "description": "() => void" }, "required": true }, "getAllColumns": { "type": { "description": "() => GridStateColDef[]" }, "required": true }, "getAllGroupDetails": { @@ -47,6 +62,11 @@ "required": true }, "getAllRowIds": { "type": { "description": "() => GridRowId[]" }, "required": true }, + "getCacheData": { + "type": { "description": "(params: GridGetRowsParams) => unknown" }, + "required": true, + "isProPlan": true + }, "getCellElement": { "type": { "description": "(id: GridRowId, field: string) => HTMLDivElement | null" }, "required": true @@ -285,6 +305,13 @@ "required": true, "isPremiumPlan": true }, + "setCacheData": { + "type": { + "description": "(params: GridGetRowsParams, data: GridGetRowsResponse) => void" + }, + "required": true, + "isProPlan": true + }, "setCellFocus": { "type": { "description": "(id: GridRowId, field: string) => void" }, "required": true @@ -294,6 +321,11 @@ "required": true, "isPremiumPlan": true }, + "setChildrenFetched": { + "type": { "description": "(id: GridRowId, childrenFetched: boolean) => void" }, + "required": true, + "isProPlan": true + }, "setColumnHeaderFilterFocus": { "type": { "description": "(field: string, event?: MuiBaseEvent) => void" }, "required": true @@ -344,12 +376,9 @@ }, "required": true }, + "setLoading": { "type": { "description": "(loading: boolean) => void" }, "required": true }, "setPage": { "type": { "description": "(page: number) => void" }, "required": true }, "setPageSize": { "type": { "description": "(pageSize: number) => void" }, "required": true }, - "setPaginationMeta": { - "type": { "description": "(paginationMeta: GridPaginationMeta) => void" }, - "required": true - }, "setPaginationModel": { "type": { "description": "(model: GridPaginationModel) => void" }, "required": true @@ -386,6 +415,11 @@ "required": true, "isProPlan": true }, + "setRowLoading": { + "type": { "description": "(id: GridRowId, loading: boolean) => void" }, + "required": true, + "isProPlan": true + }, "setRows": { "type": { "description": "(rows: GridRowModel[]) => void" }, "required": true }, "setRowSelectionModel": { "type": { "description": "(rowIds: GridRowId[]) => void" }, diff --git a/docs/translations/api-docs/data-grid/grid-api.json b/docs/translations/api-docs/data-grid/grid-api.json index 02e89d3757ea..888811f775e0 100644 --- a/docs/translations/api-docs/data-grid/grid-api.json +++ b/docs/translations/api-docs/data-grid/grid-api.json @@ -6,6 +6,7 @@ "autosizeColumns": { "description": "Auto-size the columns of the grid based on the cells' content and the space available." }, + "clearCache": { "description": "Clears the cache." }, "deleteFilterItem": { "description": "Deletes a GridFilterItem." }, @@ -17,6 +18,8 @@ "exportState": { "description": "Generates a serializable object containing the exportable parts of the DataGrid state.
These values can then be passed to the initialState prop or injected using the restoreState method." }, + "fetchRowChildren": { "description": "Initiates the fetch of the children of a row." }, + "fetchTopLevelRows": { "description": "Fetches the top level rows." }, "forceUpdate": { "description": "Forces the grid to rerender. It's often used after a state update." }, @@ -25,6 +28,7 @@ }, "getAllGroupDetails": { "description": "Returns the column group lookup." }, "getAllRowIds": { "description": "Gets the list of row ids." }, + "getCacheData": { "description": "Tries to search for some data in cache" }, "getCellElement": { "description": "Gets the underlying DOM element for a cell at the given id and field." }, @@ -145,12 +149,14 @@ "setAggregationModel": { "description": "Sets the aggregation model to the one given by model." }, + "setCacheData": { "description": "Tries to search for some data in cache" }, "setCellFocus": { "description": "Sets the focus to the cell at the given id and field." }, "setCellSelectionModel": { "description": "Updates the selected cells to be those passed to the newModel argument.
Any cell already selected will be unselected." }, + "setChildrenFetched": { "description": "Set the fetched children state of a row." }, "setColumnHeaderFilterFocus": { "description": "Sets the focus to the column header filter at the given field." }, @@ -178,13 +184,13 @@ "setFilterModel": { "description": "Sets the filter model to the one given by model." }, + "setLoading": { "description": "Sets the internal loading state." }, "setPage": { "description": "Sets the displayed page to the value given by page." }, "setPageSize": { "description": "Sets the number of displayed rows to the value given by pageSize." }, - "setPaginationMeta": { "description": "Sets the paginationMeta to a new value." }, "setPaginationModel": { "description": "Sets the paginationModel to a new value." }, @@ -201,6 +207,7 @@ "setRowIndex": { "description": "Moves a row from its original position to the position given by targetIndex." }, + "setRowLoading": { "description": "Set the loading state of a row." }, "setRows": { "description": "Sets a new set of rows." }, "setRowSelectionModel": { "description": "Updates the selected rows to be those passed to the rowIds argument.
Any row already selected will be unselected." diff --git a/packages/x-data-grid/src/hooks/features/pagination/index.ts b/packages/x-data-grid/src/hooks/features/pagination/index.ts index 5c14da8edff3..4437fa668eb6 100644 --- a/packages/x-data-grid/src/hooks/features/pagination/index.ts +++ b/packages/x-data-grid/src/hooks/features/pagination/index.ts @@ -2,6 +2,7 @@ export * from './gridPaginationSelector'; export type { GridPaginationModelApi, GridPaginationRowCountApi, + GridPaginationApi, GridPaginationState, GridPaginationInitialState, } from './gridPaginationInterfaces'; diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 26850e734419..47b1b37496be 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -412,6 +412,7 @@ { "name": "gridPaginatedVisibleSortedGridRowEntriesSelector", "kind": "Variable" }, { "name": "gridPaginatedVisibleSortedGridRowIdsSelector", "kind": "Variable" }, { "name": "GridPagination", "kind": "Variable" }, + { "name": "GridPaginationApi", "kind": "Interface" }, { "name": "GridPaginationInitialState", "kind": "Interface" }, { "name": "GridPaginationMeta", "kind": "Interface" }, { "name": "gridPaginationMetaSelector", "kind": "Variable" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index c91f288e5aa0..0ba5140ee0ac 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -376,6 +376,7 @@ { "name": "gridPaginatedVisibleSortedGridRowEntriesSelector", "kind": "Variable" }, { "name": "gridPaginatedVisibleSortedGridRowIdsSelector", "kind": "Variable" }, { "name": "GridPagination", "kind": "Variable" }, + { "name": "GridPaginationApi", "kind": "Interface" }, { "name": "GridPaginationInitialState", "kind": "Interface" }, { "name": "GridPaginationMeta", "kind": "Interface" }, { "name": "gridPaginationMetaSelector", "kind": "Variable" }, diff --git a/scripts/x-data-grid.exports.json b/scripts/x-data-grid.exports.json index efaf45edba74..5fcb02cd3677 100644 --- a/scripts/x-data-grid.exports.json +++ b/scripts/x-data-grid.exports.json @@ -342,6 +342,7 @@ { "name": "gridPaginatedVisibleSortedGridRowEntriesSelector", "kind": "Variable" }, { "name": "gridPaginatedVisibleSortedGridRowIdsSelector", "kind": "Variable" }, { "name": "GridPagination", "kind": "Variable" }, + { "name": "GridPaginationApi", "kind": "Interface" }, { "name": "GridPaginationInitialState", "kind": "Interface" }, { "name": "GridPaginationMeta", "kind": "Interface" }, { "name": "gridPaginationMetaSelector", "kind": "Variable" }, From cb0d5bbd013eb156e99f5e586e4dd230e7c4c5a8 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Tue, 23 Apr 2024 04:33:34 +0500 Subject: [PATCH 06/90] Move unstable_dataSource interface to MIT package --- .../src/hooks/features/serverSideData/useGridDataSource.ts | 6 +----- packages/x-data-grid-pro/src/models/dataGridProProps.ts | 5 ++--- packages/x-data-grid/src/hooks/features/rows/useGridRows.ts | 3 +-- packages/x-data-grid/src/internals/index.ts | 2 +- .../src/models/gridDataSource.ts | 4 ++-- packages/x-data-grid/src/models/props/DataGridProps.ts | 2 ++ 6 files changed, 9 insertions(+), 13 deletions(-) rename packages/{x-data-grid-pro => x-data-grid}/src/models/gridDataSource.ts (98%) diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts index 57349215a0af..fdf03920bf2c 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts @@ -9,13 +9,9 @@ import { useGridApiMethod, GridServerSideGroupNode, } from '@mui/x-data-grid'; +import { GridGetRowsParams, GridGetRowsResponse, GridDataSource } from '@mui/x-data-grid/internals'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; -import { - GridGetRowsParams, - GridGetRowsResponse, - GridDataSource, -} from '../../../models/gridDataSource'; import { GridDataSourceApi } from './serverSideInterfaces'; const computeStartEnd = (paginationModel: GridPaginationModel) => { diff --git a/packages/x-data-grid-pro/src/models/dataGridProProps.ts b/packages/x-data-grid-pro/src/models/dataGridProProps.ts index 34f271bef7a4..bf9a83ed1cc8 100644 --- a/packages/x-data-grid-pro/src/models/dataGridProProps.ts +++ b/packages/x-data-grid-pro/src/models/dataGridProProps.ts @@ -8,7 +8,7 @@ import { GridGroupNode, GridFeatureMode, } from '@mui/x-data-grid'; -import { +import type { GridExperimentalFeatures, DataGridPropsWithoutDefaultValue, DataGridPropsWithDefaultValues, @@ -17,6 +17,7 @@ import { GridPinnedColumnFields, DataGridProSharedPropsWithDefaultValue, DataGridProSharedPropsWithoutDefaultValue, + GridDataSourceCache, } from '@mui/x-data-grid/internals'; import type { GridPinnedRowsProp } from '../hooks/features/rowPinning'; import { GridApiPro } from './gridApiPro'; @@ -27,7 +28,6 @@ import { import { GridInitialStatePro } from './gridStatePro'; import { GridProSlotsComponent } from './gridProSlotsComponent'; import type { GridProSlotProps } from './gridProSlotProps'; -import type { GridDataSource, GridDataSourceCache } from './gridDataSource'; export interface GridExperimentalProFeatures extends GridExperimentalFeatures {} @@ -144,7 +144,6 @@ export interface DataGridProPropsWithDefaultValue string; hasChildren?: (row: GridValidRowModel) => boolean; 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 9712a0eefa73..b5787d22cb6c 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts @@ -42,9 +42,8 @@ import { import { useGridRegisterPipeApplier } from '../../core/pipeProcessing'; export const rowsStateInitializer: GridStateInitializer< - Pick + Pick > = (state, props, apiRef) => { - // @ts-expect-error To read prop which belongs to the `DataGridPro` component const isDataSourceAvailable = props.unstable_dataSource != null; apiRef.current.caches.rows = createRowsInternalCache({ rows: isDataSourceAvailable ? [] : props.rows, diff --git a/packages/x-data-grid/src/internals/index.ts b/packages/x-data-grid/src/internals/index.ts index 88b4d71bf80f..74cd94589c64 100644 --- a/packages/x-data-grid/src/internals/index.ts +++ b/packages/x-data-grid/src/internals/index.ts @@ -130,7 +130,7 @@ export { useGridInitializeState } from '../hooks/utils/useGridInitializeState'; export type { GridStateInitializer } from '../hooks/utils/useGridInitializeState'; export type * from '../models/props/DataGridProps'; - +export type * from '../models/gridDataSource'; export { getColumnsToExport, defaultGetRowsToExport } from '../hooks/features/export/utils'; export * from '../utils/createControllablePromise'; export { createSelector, createSelectorMemoized } from '../utils/createSelector'; diff --git a/packages/x-data-grid-pro/src/models/gridDataSource.ts b/packages/x-data-grid/src/models/gridDataSource.ts similarity index 98% rename from packages/x-data-grid-pro/src/models/gridDataSource.ts rename to packages/x-data-grid/src/models/gridDataSource.ts index 27923e4118ec..631ed3a4eff4 100644 --- a/packages/x-data-grid-pro/src/models/gridDataSource.ts +++ b/packages/x-data-grid/src/models/gridDataSource.ts @@ -1,10 +1,10 @@ -import { +import type { GridSortModel, GridFilterModel, GridColDef, GridRowModel, GridPaginationModel, -} from '@mui/x-data-grid'; +} from '.'; export interface GridGetRowsParams { sortModel: GridSortModel; diff --git a/packages/x-data-grid/src/models/props/DataGridProps.ts b/packages/x-data-grid/src/models/props/DataGridProps.ts index 3d80dcdb101c..cb3905ffcd30 100644 --- a/packages/x-data-grid/src/models/props/DataGridProps.ts +++ b/packages/x-data-grid/src/models/props/DataGridProps.ts @@ -32,6 +32,7 @@ import { GridCellModesModel, GridRowModesModel } from '../api/gridEditingApi'; import { GridColumnGroupingModel } from '../gridColumnGrouping'; import { GridPaginationMeta, GridPaginationModel } from '../gridPaginationProps'; import type { GridAutosizeOptions } from '../../hooks/features/columnResize'; +import type { GridDataSource } from '../gridDataSource'; export interface GridExperimentalFeatures { /** @@ -813,6 +814,7 @@ export interface DataGridProSharedPropsWithoutDefaultValue { * Override the height of the header filters. */ headerFilterHeight?: number; + unstable_dataSource?: GridDataSource; } export interface DataGridPremiumSharedPropsWithDefaultValue { From 0c3d45ea300ba2c8c3238c9b9ac1634fe37e3350 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Tue, 23 Apr 2024 04:35:40 +0500 Subject: [PATCH 07/90] Remove extra export --- packages/x-data-grid-pro/src/models/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/x-data-grid-pro/src/models/index.ts b/packages/x-data-grid-pro/src/models/index.ts index e530e16b5812..36deff5c944b 100644 --- a/packages/x-data-grid-pro/src/models/index.ts +++ b/packages/x-data-grid-pro/src/models/index.ts @@ -5,4 +5,3 @@ export * from './gridRowOrderChangeParams'; export * from './gridFetchRowsParams'; export * from './gridProSlotsComponent'; export * from './gridProIconSlotsComponent'; -export * from './gridDataSource'; From e11117a9696964aabc981c3ce7281f59c33d7b81 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 29 Apr 2024 13:25:01 +0500 Subject: [PATCH 08/90] Initial support for defaultGroupingExpansionDepth and isGroupExpandedByDefault --- .../server-side-data/ServerSideTreeData.js | 2 + .../server-side-data/ServerSideTreeData.tsx | 2 + .../ServerSideTreeData.tsx.preview | 16 -- docs/pages/x/api/data-grid/grid-api.json | 4 +- .../serverSideData/serverSideInterfaces.ts | 5 +- .../serverSideData/useGridDataSource.ts | 168 ++++++++++++++++-- .../serverSideData/useGridServerSideCache.ts | 6 +- ...useGridServerSideTreeDataPreProcessors.tsx | 1 + packages/x-data-grid-pro/src/models/index.ts | 6 + .../src/utils/tree/createRowTree.ts | 3 + .../src/utils/tree/insertDataRowInTree.ts | 11 ++ .../src/utils/tree/updateRowTree.ts | 7 + .../x-data-grid-pro/src/utils/tree/utils.ts | 16 +- .../hooks/features/rows/gridRowsInterfaces.ts | 9 +- .../hooks/features/rows/gridRowsSelector.ts | 5 + .../src/hooks/features/rows/gridRowsUtils.ts | 9 +- .../src/hooks/features/rows/useGridRows.ts | 6 +- packages/x-data-grid/src/internals/index.ts | 1 + .../src/models/api/gridApiCommon.ts | 8 +- .../x-data-grid/src/models/api/gridRowApi.ts | 3 +- 20 files changed, 235 insertions(+), 53 deletions(-) delete mode 100644 docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.js b/docs/data/data-grid/server-side-data/ServerSideTreeData.js index 25b5b08e7c10..dfccd2fda468 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.js @@ -48,6 +48,7 @@ export default function ServerSideTreeData() { paginationModel: { pageSize: 5, }, + rowCount: 0, }, }), [props.initialState], @@ -68,6 +69,7 @@ export default function ServerSideTreeData() { initialState={initialState} slots={{ toolbar: GridToolbar }} slotProps={{ toolbar: { showQuickFilter: true } }} + defaultGroupingExpansionDepth={-1} filterDebounceMs={1000} />
diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx index 490ffd31f53a..aeae75b747b4 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx @@ -54,6 +54,7 @@ export default function ServerSideTreeData() { paginationModel: { pageSize: 5, }, + rowCount: 0, }, }), [props.initialState], @@ -74,6 +75,7 @@ export default function ServerSideTreeData() { initialState={initialState} slots={{ toolbar: GridToolbar }} slotProps={{ toolbar: { showQuickFilter: true } }} + defaultGroupingExpansionDepth={-1} filterDebounceMs={1000} />
diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview deleted file mode 100644 index bb6dbf04bb1a..000000000000 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview +++ /dev/null @@ -1,16 +0,0 @@ - -
- -
\ No newline at end of file diff --git a/docs/pages/x/api/data-grid/grid-api.json b/docs/pages/x/api/data-grid/grid-api.json index fd31b8ad562f..bec76eb87bd0 100644 --- a/docs/pages/x/api/data-grid/grid-api.json +++ b/docs/pages/x/api/data-grid/grid-api.json @@ -46,7 +46,7 @@ "required": true }, "fetchRowChildren": { - "type": { "description": "(id: GridRowId) => void" }, + "type": { "description": "(id: GridRowId, throttle?: boolean) => void" }, "required": true, "isProPlan": true }, @@ -516,7 +516,7 @@ "required": true }, "updateRows": { - "type": { "description": "(updates: GridRowModelUpdate[]) => void" }, + "type": { "description": "(updates: GridRowModelUpdate[], throttle?: boolean) => void" }, "required": true }, "upsertFilterItem": { diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts index d99088305473..afc2d19af0db 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts @@ -1,5 +1,5 @@ import { GridRowId } from '@mui/x-data-grid'; -import { GridGetRowsParams, GridGetRowsResponse } from '../../../models/gridDataSource'; +import { GridGetRowsParams, GridGetRowsResponse } from '../../../models'; /** * The dataSource API interface that is available in the grid [[apiRef]]. @@ -8,8 +8,9 @@ export interface GridDataSourceApi { /** * Initiates the fetch of the children of a row. * @param {string} id The id of the rowNode belonging to the group to be fetched. + * @param {boolean} throttle If `true`, the request will be throttled. (default: `true`) */ - fetchRowChildren: (id: GridRowId) => void; + fetchRowChildren: (id: GridRowId, throttle?: boolean) => void; /** * Set the loading state of a row. * @param {string} id The id of the rowNode. diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts index fdf03920bf2c..25b559078a2b 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts @@ -8,8 +8,10 @@ import { gridRowsLoadingSelector, useGridApiMethod, GridServerSideGroupNode, + GridRowId, } from '@mui/x-data-grid'; -import { GridGetRowsParams, GridGetRowsResponse, GridDataSource } from '@mui/x-data-grid/internals'; +import { gridRowGroupsToFetchSelector } from '@mui/x-data-grid/internals'; +import { GridGetRowsParams, GridGetRowsResponse, GridDataSource } from '../../../models'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; import { GridDataSourceApi } from './serverSideInterfaces'; @@ -20,6 +22,9 @@ const computeStartEnd = (paginationModel: GridPaginationModel) => { return { start, end }; }; +const getErrorMessage = (inputParams: GridGetRowsParams) => + `MUI: Error in fetching rows for the input params: ${JSON.stringify(inputParams)}`; + const fetchRowsWithError = async ( getRows: GridDataSource['getRows'], inputParams: GridGetRowsParams, @@ -40,6 +45,98 @@ const runIfServerMode = (modeProp: 'server' | 'client', fn: Function) => () => { } }; +const MAX_CONCURRENT_REQUESTS = Infinity; + +enum RequestStatus { + INQUEUE, + PENDING, + SETTLED, + CANCELLED, + UNKNOWN, +} + +/** + * Fetches row children from the server limiting the number of concurrent requests + * Determines the status of a request based on the enum `RequestStatus` + */ +class NestedDataManager { + private pendingRequests: Set = new Set(); + + private fetchQueue: Set = new Set(); + + private fetchQueueStatus: Map = new Map(); + + private fetchQueueIndex = 0; + + private api: GridPrivateApiPro; + + private maxConcurrentRequests: number; + + constructor( + private privateApiRef: React.MutableRefObject, + private maxRequests = MAX_CONCURRENT_REQUESTS, + ) { + this.api = privateApiRef.current; + this.maxConcurrentRequests = maxRequests; + } + + public fetch = async (ids: GridRowId[]) => { + ids.forEach((id) => { + if (this.pendingRequests.size < this.maxConcurrentRequests) { + this.pendingRequests.add(id); + this.api.fetchRowChildren(id, false); + } else { + this.fetchQueue.add(id); + this.fetchQueueStatus.set(id, RequestStatus.INQUEUE); + } + }); + }; + + public setRequestSettled = (id: GridRowId) => { + this.fetchQueueStatus.set(id, RequestStatus.SETTLED); + this.pendingRequests.delete(id); + this.processQueue(); + }; + + public processQueue = async () => { + if (this.fetchQueue.size === 0) { + return; + } + const fetchQueue = Array.from(this.fetchQueue); + for ( + let i = this.fetchQueueIndex; + this.fetchQueueIndex < fetchQueue.length; + this.fetchQueueIndex += 1 + ) { + const nextId = fetchQueue[i]; + if (this.fetchQueueStatus.get(nextId) === RequestStatus.INQUEUE) { + this.api.fetchRowChildren(nextId); + this.fetchQueueStatus.set(nextId, RequestStatus.PENDING); + } + } + }; + + public clearPendingRequests = () => { + this.fetchQueue.clear(); + this.fetchQueueIndex = 0; + Array.from(this.pendingRequests).forEach((id) => { + this.fetchQueueStatus.set(id, RequestStatus.CANCELLED); + this.api.setRowLoading(id, false); + this.pendingRequests.delete(id); + }); + }; + + public clearPendingRequest = (id: GridRowId) => { + this.fetchQueueStatus.set(id, RequestStatus.CANCELLED); + this.pendingRequests.delete(id); + }; + + public getRequestStatus = (id: GridRowId) => + this.fetchQueueStatus.get(id) ?? RequestStatus.UNKNOWN; + + public getActiveRequestsCount = () => this.pendingRequests.size; +} + export const useGridDataSource = ( privateApiRef: React.MutableRefObject, props: Pick< @@ -47,11 +144,14 @@ export const useGridDataSource = ( 'unstable_dataSource' | 'sortingMode' | 'filterMode' | 'paginationMode' | 'treeData' >, ): void => { + const nestedDataManager = React.useRef( + new NestedDataManager(privateApiRef), + ).current; + const groupsToAutoFetch = gridRowGroupsToFetchSelector(privateApiRef); const getInputParams = React.useCallback( (additionalParams?: Partial): GridGetRowsParams => { const paginationModel = gridPaginationModelSelector(privateApiRef); - // const otherParams = privateApiRef.current.unstable_applyPipeProcessors('getRowsParams', {}); return { groupKeys: [], paginationModel, @@ -74,6 +174,10 @@ export const useGridDataSource = ( const cachedData = privateApiRef.current.getCacheData(inputParams) as | GridGetRowsResponse | undefined; + + if (nestedDataManager.getActiveRequestsCount() > 0) { + nestedDataManager.clearPendingRequests(); + } if (cachedData != null) { const rows = cachedData.rows; privateApiRef.current.caches.groupKeys = []; @@ -103,10 +207,10 @@ export const useGridDataSource = ( privateApiRef.current.setLoading(false); // TODO: handle cursor based pagination } - }, [getInputParams, privateApiRef, props.unstable_dataSource]); + }, [getInputParams, nestedDataManager, privateApiRef, props.unstable_dataSource?.getRows]); const fetchRowChildren = React.useCallback( - async (id) => { + async (id, throttle = true) => { if (!props.treeData) { return; } @@ -128,7 +232,7 @@ export const useGridDataSource = ( if (cachedData != null) { const rows = cachedData.rows; privateApiRef.current.caches.groupKeys = rowNode.path; - privateApiRef.current.updateRows(rows); + privateApiRef.current.updateRows(rows, throttle); if (cachedData.rowCount) { privateApiRef.current.setRowCount(cachedData.rowCount); } @@ -139,19 +243,40 @@ export const useGridDataSource = ( if (!isLoading) { privateApiRef.current.setRowLoading(id, true); } - const getRowsResponse = await fetchRowsWithError(getRows, inputParams); - privateApiRef.current.setCacheData(inputParams, getRowsResponse); - if (getRowsResponse.rowCount) { - privateApiRef.current.setRowCount(getRowsResponse.rowCount); + + try { + const getRowsResponse = await getRows(inputParams); + nestedDataManager.setRequestSettled(id); + if (!privateApiRef.current.getRowNode(id)) { + nestedDataManager.clearPendingRequest(id); + return; + } + if (nestedDataManager.getRequestStatus(id) === RequestStatus.CANCELLED) { + return; + } + privateApiRef.current.setCacheData(inputParams, getRowsResponse); + if (getRowsResponse.rowCount) { + privateApiRef.current.setRowCount(getRowsResponse.rowCount); + } + privateApiRef.current.caches.groupKeys = rowNode.path; + privateApiRef.current.updateRows(getRowsResponse.rows, throttle); + privateApiRef.current.setRowChildrenExpansion(id, true); + privateApiRef.current.setChildrenFetched(id, true); + privateApiRef.current.setRowLoading(id, false); + } catch (error) { + nestedDataManager.setRequestSettled(id); + privateApiRef.current.setRowLoading(id, false); + throw new Error(getErrorMessage(inputParams)); } - privateApiRef.current.caches.groupKeys = rowNode.path; - privateApiRef.current.updateRows(getRowsResponse.rows); - privateApiRef.current.setRowChildrenExpansion(id, true); - privateApiRef.current.setChildrenFetched(id, true); - privateApiRef.current.setRowLoading(id, false); } }, - [getInputParams, privateApiRef, props.treeData, props.unstable_dataSource?.getRows], + [ + getInputParams, + nestedDataManager, + privateApiRef, + props.treeData, + props.unstable_dataSource?.getRows, + ], ); const setRowLoading = React.useCallback( @@ -233,4 +358,17 @@ export const useGridDataSource = ( privateApiRef.current.fetchTopLevelRows(); } }, [privateApiRef, props.unstable_dataSource]); + + React.useEffect(() => { + if (groupsToAutoFetch && groupsToAutoFetch.length > 0) { + nestedDataManager.fetch(groupsToAutoFetch); + privateApiRef.current.setState((state) => ({ + ...state, + rows: { + ...state.rows, + groupsToFetch: [], + }, + })); + } + }, [privateApiRef, nestedDataManager, groupsToAutoFetch]); }; diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridServerSideCache.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridServerSideCache.ts index 6c1c2dfb47c9..8b10caa6eb93 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridServerSideCache.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridServerSideCache.ts @@ -2,11 +2,7 @@ import * as React from 'react'; import { useGridApiMethod } from '@mui/x-data-grid'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; -import { - GridGetRowsParams, - GridGetRowsResponse, - GridDataSourceCache, -} from '../../../models/gridDataSource'; +import { GridGetRowsParams, GridGetRowsResponse, GridDataSourceCache } from '../../../models'; import { GridServerSideCacheApi } from './serverSideInterfaces'; const noop = () => undefined; diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx index 4aeb18b401a5..ebcbe5e39096 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx @@ -177,6 +177,7 @@ export const useGridServerSideTreeDataPreProcessors = ( removed: params.updates.actions.remove, }, previousTree: params.previousTree!, + previousGroupsToFetch: params.previousGroupsToFetch, previousTreeDepth: params.previousTreeDepths!, defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, isGroupExpandedByDefault: props.isGroupExpandedByDefault, diff --git a/packages/x-data-grid-pro/src/models/index.ts b/packages/x-data-grid-pro/src/models/index.ts index 36deff5c944b..8110b6c70a91 100644 --- a/packages/x-data-grid-pro/src/models/index.ts +++ b/packages/x-data-grid-pro/src/models/index.ts @@ -1,3 +1,9 @@ +export type { + GridGetRowsParams, + GridGetRowsResponse, + GridDataSource, + GridDataSourceCache, +} from '@mui/x-data-grid/internals'; export * from './gridApiPro'; export * from './gridGroupingColDefOverride'; export * from './gridRowScrollEndParams'; diff --git a/packages/x-data-grid-pro/src/utils/tree/createRowTree.ts b/packages/x-data-grid-pro/src/utils/tree/createRowTree.ts index 0ad4bd8d4e99..e21c10b17450 100644 --- a/packages/x-data-grid-pro/src/utils/tree/createRowTree.ts +++ b/packages/x-data-grid-pro/src/utils/tree/createRowTree.ts @@ -22,6 +22,7 @@ export const createRowTree = (params: CreateRowTreeParams): GridRowTreeCreationV [GRID_ROOT_GROUP_ID]: buildRootGroup(), }; const treeDepths: GridRowTreeCreationValue['treeDepths'] = {}; + const groupsToFetch = new Set([]); for (let i = 0; i < params.nodes.length; i += 1) { const node = params.nodes[i]; @@ -37,6 +38,7 @@ export const createRowTree = (params: CreateRowTreeParams): GridRowTreeCreationV treeDepths, isGroupExpandedByDefault: params.isGroupExpandedByDefault, defaultGroupingExpansionDepth: params.defaultGroupingExpansionDepth, + groupsToFetch, }); } @@ -45,5 +47,6 @@ export const createRowTree = (params: CreateRowTreeParams): GridRowTreeCreationV treeDepths, groupingName: params.groupingName, dataRowIds, + groupsToFetch: Array.from(groupsToFetch), }; }; diff --git a/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts b/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts index c851a86da8a6..063caf5bc8fe 100644 --- a/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts +++ b/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts @@ -9,6 +9,7 @@ import { import { GridTreeDepths, GridRowTreeUpdatedGroupsManager } from '@mui/x-data-grid/internals'; import { updateGroupDefaultExpansion, + checkGroupChildrenExpansion, getGroupRowIdFromPath, insertNodeInTree, updateGroupNodeIdAndAutoGenerated, @@ -59,6 +60,7 @@ interface InsertDataRowInTreeParams { isGroupExpandedByDefault?: DataGridProProps['isGroupExpandedByDefault']; defaultGroupingExpansionDepth: number; hasServerChildren?: boolean; + groupsToFetch?: Set; } /** @@ -78,6 +80,7 @@ export const insertDataRowInTree = ({ isGroupExpandedByDefault, defaultGroupingExpansionDepth, hasServerChildren, + groupsToFetch, }: InsertDataRowInTreeParams) => { let parentNodeId = GRID_ROOT_GROUP_ID; @@ -113,6 +116,14 @@ export const insertDataRowInTree = ({ isServerSide: true, childrenFetched: false, }; + const shouldFetchChildren = checkGroupChildrenExpansion( + node, + defaultGroupingExpansionDepth, + isGroupExpandedByDefault, + ); + if (shouldFetchChildren) { + groupsToFetch?.add(id); + } } else { node = { type: 'leaf', diff --git a/packages/x-data-grid-pro/src/utils/tree/updateRowTree.ts b/packages/x-data-grid-pro/src/utils/tree/updateRowTree.ts index b7f6f544768f..0c7ddc514403 100644 --- a/packages/x-data-grid-pro/src/utils/tree/updateRowTree.ts +++ b/packages/x-data-grid-pro/src/utils/tree/updateRowTree.ts @@ -24,12 +24,16 @@ interface UpdateRowTreeParams { isGroupExpandedByDefault?: (node: GridGroupNode) => boolean; groupingName: string; onDuplicatePath?: GridTreePathDuplicateHandler; + previousGroupsToFetch?: GridRowId[]; } export const updateRowTree = (params: UpdateRowTreeParams): GridRowTreeCreationValue => { const tree = { ...params.previousTree }; const treeDepths = { ...params.previousTreeDepth }; const updatedGroupsManager = createUpdatedGroupsManager(); + const groupsToFetch = params.previousGroupsToFetch + ? new Set([...params.previousGroupsToFetch]) + : new Set([]); for (let i = 0; i < params.nodes.inserted.length; i += 1) { const { id, path, hasServerChildren } = params.nodes.inserted[i]; @@ -45,6 +49,7 @@ export const updateRowTree = (params: UpdateRowTreeParams): GridRowTreeCreationV onDuplicatePath: params.onDuplicatePath, isGroupExpandedByDefault: params.isGroupExpandedByDefault, defaultGroupingExpansionDepth: params.defaultGroupingExpansionDepth, + groupsToFetch, }); } @@ -83,6 +88,7 @@ export const updateRowTree = (params: UpdateRowTreeParams): GridRowTreeCreationV onDuplicatePath: params.onDuplicatePath, isGroupExpandedByDefault: params.isGroupExpandedByDefault, defaultGroupingExpansionDepth: params.defaultGroupingExpansionDepth, + groupsToFetch, }); } else { updatedGroupsManager?.addAction(tree[id].parent!, 'modifyChildren'); @@ -98,5 +104,6 @@ export const updateRowTree = (params: UpdateRowTreeParams): GridRowTreeCreationV groupingName: params.groupingName, dataRowIds, updatedGroupsManager, + groupsToFetch: Array.from(groupsToFetch), }; }; diff --git a/packages/x-data-grid-pro/src/utils/tree/utils.ts b/packages/x-data-grid-pro/src/utils/tree/utils.ts index 5de194769e5c..fba2f4c9bfa2 100644 --- a/packages/x-data-grid-pro/src/utils/tree/utils.ts +++ b/packages/x-data-grid-pro/src/utils/tree/utils.ts @@ -49,7 +49,7 @@ export const getNodePathInTree = ({ return path; }; -export const updateGroupDefaultExpansion = ( +export const checkGroupChildrenExpansion = ( node: GridGroupNode, defaultGroupingExpansionDepth: number, isGroupExpandedByDefault?: DataGridProProps['isGroupExpandedByDefault'], @@ -64,8 +64,20 @@ export const updateGroupDefaultExpansion = ( defaultGroupingExpansionDepth === -1 || defaultGroupingExpansionDepth > node.depth; } - node.childrenExpanded = childrenExpanded; + return childrenExpanded; +}; +export const updateGroupDefaultExpansion = ( + node: GridGroupNode, + defaultGroupingExpansionDepth: number, + isGroupExpandedByDefault?: DataGridProProps['isGroupExpandedByDefault'], +) => { + const childrenExpanded = checkGroupChildrenExpansion( + node, + defaultGroupingExpansionDepth, + isGroupExpandedByDefault, + ); + node.childrenExpanded = childrenExpanded; return node; }; diff --git a/packages/x-data-grid/src/hooks/features/rows/gridRowsInterfaces.ts b/packages/x-data-grid/src/hooks/features/rows/gridRowsInterfaces.ts index f46cf6efa70f..f8b2980348a6 100644 --- a/packages/x-data-grid/src/hooks/features/rows/gridRowsInterfaces.ts +++ b/packages/x-data-grid/src/hooks/features/rows/gridRowsInterfaces.ts @@ -70,6 +70,12 @@ export interface GridRowsState { additionalRowGroups?: { pinnedRows?: GridPinnedRowsState; }; + /** + * Contains some values of type `GridRowId` that have been requested to be fetched + * either by `defaultGroupingExpansionDepth` or `isGroupExpandedByDefault` props. + * Applicable with server-side grouped data and `unstable_dataSource` only. + */ + groupsToFetch?: GridRowId[]; } export interface GridRowTreeCreationParams { @@ -78,6 +84,7 @@ export interface GridRowTreeCreationParams { updates: GridRowsPartialUpdates | GridRowsFullUpdate; dataRowIdToIdLookup: GridRowIdToIdLookup; dataRowIdToModelLookup: GridRowIdToModelLookup; + previousGroupsToFetch?: GridRowId[]; } export type GridRowTreeUpdateGroupAction = 'removeChildren' | 'insertChildren' | 'modifyChildren'; @@ -93,7 +100,7 @@ export type GridRowTreeUpdatedGroupsManager = { export type GridRowTreeCreationValue = Pick< GridRowsState, - 'groupingName' | 'tree' | 'treeDepths' | 'dataRowIds' + 'groupingName' | 'tree' | 'treeDepths' | 'dataRowIds' | 'groupsToFetch' > & { updatedGroupsManager?: GridRowTreeUpdatedGroupsManager; }; diff --git a/packages/x-data-grid/src/hooks/features/rows/gridRowsSelector.ts b/packages/x-data-grid/src/hooks/features/rows/gridRowsSelector.ts index 6dd24b5256bc..c0f3614d0ad3 100644 --- a/packages/x-data-grid/src/hooks/features/rows/gridRowsSelector.ts +++ b/packages/x-data-grid/src/hooks/features/rows/gridRowsSelector.ts @@ -31,6 +31,11 @@ export const gridRowsDataRowIdToIdLookupSelector = createSelector( export const gridRowTreeSelector = createSelector(gridRowsStateSelector, (rows) => rows.tree); +export const gridRowGroupsToFetchSelector = createSelector( + gridRowsStateSelector, + (rows) => rows.groupsToFetch, +); + export const gridRowGroupingNameSelector = createSelector( gridRowsStateSelector, (rows) => rows.groupingName, diff --git a/packages/x-data-grid/src/hooks/features/rows/gridRowsUtils.ts b/packages/x-data-grid/src/hooks/features/rows/gridRowsUtils.ts index a2704b611c8e..22655f6352cb 100644 --- a/packages/x-data-grid/src/hooks/features/rows/gridRowsUtils.ts +++ b/packages/x-data-grid/src/hooks/features/rows/gridRowsUtils.ts @@ -132,7 +132,11 @@ export const getRowsStateFromCache = ({ loadingProp, previousTree, previousTreeDepths, -}: Pick & { + previousGroupsToFetch, +}: Pick< + GridRowTreeCreationParams, + 'previousTree' | 'previousTreeDepths' | 'previousGroupsToFetch' +> & { apiRef: React.MutableRefObject; rowCountProp: number | undefined; loadingProp: boolean | undefined; @@ -145,12 +149,14 @@ export const getRowsStateFromCache = ({ treeDepths: unProcessedTreeDepths, dataRowIds: unProcessedDataRowIds, groupingName, + groupsToFetch = [], } = apiRef.current.applyStrategyProcessor('rowTreeCreation', { previousTree, previousTreeDepths, updates: cache.updates, dataRowIdToIdLookup: cache.dataRowIdToIdLookup, dataRowIdToModelLookup: cache.dataRowIdToModelLookup, + previousGroupsToFetch, }); // 2. Apply the "hydrateRows" pipe-processing. @@ -182,6 +188,7 @@ export const getRowsStateFromCache = ({ }), groupingName, loading: loadingProp, + groupsToFetch, }; }; 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 b5787d22cb6c..b3469657cfbb 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts @@ -20,6 +20,7 @@ import { gridDataRowIdsSelector, gridRowsDataRowIdToIdLookupSelector, gridRowMaximumTreeDepthSelector, + gridRowGroupsToFetchSelector, } from './gridRowsSelector'; import { useTimeout } from '../../utils/useTimeout'; import { GridSignature, useGridApiEventHandler } from '../../utils/useGridApiEventHandler'; @@ -146,6 +147,7 @@ export const useGridRows = ( loadingProp: props.loading, previousTree: gridRowTreeSelector(apiRef), previousTreeDepths: gridRowTreeDepthsSelector(apiRef), + previousGroupsToFetch: gridRowGroupsToFetchSelector(apiRef), }), })); apiRef.current.publishEvent('rowsSet'); @@ -193,7 +195,7 @@ export const useGridRows = ( ); const updateRows = React.useCallback( - (updates) => { + (updates, throttle = true) => { if (props.signature === GridSignature.DataGrid && updates.length > 1) { throw new Error( [ @@ -234,7 +236,7 @@ export const useGridRows = ( previousCache: apiRef.current.caches.rows, }); - throttledRowsChange({ cache, throttle: true }); + throttledRowsChange({ cache, throttle }); }, [props.signature, props.getRowId, throttledRowsChange, apiRef], ); diff --git a/packages/x-data-grid/src/internals/index.ts b/packages/x-data-grid/src/internals/index.ts index 74cd94589c64..1387f50016d3 100644 --- a/packages/x-data-grid/src/internals/index.ts +++ b/packages/x-data-grid/src/internals/index.ts @@ -134,6 +134,7 @@ export type * from '../models/gridDataSource'; export { getColumnsToExport, defaultGetRowsToExport } from '../hooks/features/export/utils'; export * from '../utils/createControllablePromise'; export { createSelector, createSelectorMemoized } from '../utils/createSelector'; +export { gridRowGroupsToFetchSelector } from '../hooks/features/rows/gridRowsSelector'; export { findParentElementFromClassName, getActiveElement, diff --git a/packages/x-data-grid/src/models/api/gridApiCommon.ts b/packages/x-data-grid/src/models/api/gridApiCommon.ts index 2c542bea2611..1e59d123c8a5 100644 --- a/packages/x-data-grid/src/models/api/gridApiCommon.ts +++ b/packages/x-data-grid/src/models/api/gridApiCommon.ts @@ -28,10 +28,7 @@ import type { GridDimensionsApi, GridDimensionsPrivateApi, } from '../../hooks/features/dimensions/gridDimensionsApi'; -import type { - GridPaginationModelApi, - GridPaginationRowCountApi, -} from '../../hooks/features/pagination'; +import type { GridPaginationApi } from '../../hooks/features/pagination'; import type { GridStatePersistenceApi } from '../../hooks/features/statePersistence'; import { GridColumnGroupingApi } from './gridColumnGroupingApi'; import type { GridInitialStateCommunity, GridStateCommunity } from '../gridStateCommunity'; @@ -53,8 +50,7 @@ export interface GridApiCommon< GridColumnApi, GridRowSelectionApi, GridSortApi, - GridPaginationModelApi, - GridPaginationRowCountApi, + GridPaginationApi, GridCsvExportApi, GridFocusApi, GridFilterApi, diff --git a/packages/x-data-grid/src/models/api/gridRowApi.ts b/packages/x-data-grid/src/models/api/gridRowApi.ts index f35f06c80ed5..22ab74d56238 100644 --- a/packages/x-data-grid/src/models/api/gridRowApi.ts +++ b/packages/x-data-grid/src/models/api/gridRowApi.ts @@ -61,8 +61,9 @@ export interface GridRowApi { /** * Allows to update, insert and delete rows. * @param {GridRowModelUpdate[]} updates An array of rows with an `action` specifying what to do. + * @param {boolean} throttle Whether to throttle the updates or not. (default: `true`) */ - updateRows: (updates: GridRowModelUpdate[]) => void; + updateRows: (updates: GridRowModelUpdate[], throttle?: boolean) => void; /** * Gets the row data with a given id. * @param {GridRowId} id The id of the row. From 6a7b9aced99a1d91db1c2ecc558720cf878b047b Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 29 Apr 2024 15:13:52 +0500 Subject: [PATCH 09/90] Cache BE data --- .../server-side-data/ServerSideTreeData.tsx | 2 +- .../src/hooks/useDemoData.ts | 2 +- .../src/hooks/useDemoDataSource.ts | 54 ++++++++++++++++--- .../serverSideData/useGridDataSource.ts | 12 ++--- 4 files changed, 55 insertions(+), 15 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx index aeae75b747b4..19799fb2b02c 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx @@ -75,7 +75,7 @@ export default function ServerSideTreeData() { initialState={initialState} slots={{ toolbar: GridToolbar }} slotProps={{ toolbar: { showQuickFilter: true } }} - defaultGroupingExpansionDepth={-1} + defaultGroupingExpansionDepth={1} filterDebounceMs={1000} />
diff --git a/packages/x-data-grid-generator/src/hooks/useDemoData.ts b/packages/x-data-grid-generator/src/hooks/useDemoData.ts index bb36089a8d1f..2344e7000a8a 100644 --- a/packages/x-data-grid-generator/src/hooks/useDemoData.ts +++ b/packages/x-data-grid-generator/src/hooks/useDemoData.ts @@ -73,7 +73,7 @@ export async function extrapolateSeed( }); } -const deepFreeze = (object: T): T => { +export const deepFreeze = (object: T): T => { // Retrieve the property names defined on object const propNames = Object.getOwnPropertyNames(object); diff --git a/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts b/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts index 318ba338dc0e..af9861db75db 100644 --- a/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts +++ b/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts @@ -1,4 +1,5 @@ import * as React from 'react'; +import LRUCache from 'lru-cache'; import { getGridDefaultColumnTypes, GridRowModel, @@ -9,7 +10,12 @@ import { GridColumnVisibilityModel, GridDataSource, } from '@mui/x-data-grid-pro'; -import { UseDemoDataOptions, getColumnsFromOptions, extrapolateSeed } from './useDemoData'; +import { + UseDemoDataOptions, + getColumnsFromOptions, + extrapolateSeed, + deepFreeze, +} from './useDemoData'; import { GridColDefGenerator } from '../services/gridColDefGenerator'; import { getRealGridData, GridDemoData } from '../services/real-data-service'; import { addTreeDataOptionsToDemoData } from '../services/tree-data-generator'; @@ -21,6 +27,11 @@ import { } from './serverUtils'; import type { ServerOptions } from './serverUtils'; +const dataCache = new LRUCache({ + max: 10, + ttl: 60 * 5 * 1e3, // 5 minutes +}); + type CreateDummyDataSourceResponse = { columns: GridColDef[]; initialState: GridInitialState; @@ -28,6 +39,7 @@ type CreateDummyDataSourceResponse = { hasChildren?: (row: GridRowModel) => boolean; getChildrenCount?: (row: GridRowModel) => number; getRows: GridDataSource['getRows']; + loadNewData: () => void; }; const getInitialState = (columns: GridColDefGenerator[], groupingField?: string) => { @@ -52,7 +64,7 @@ export const useDemoDataSource = ( serverOptions?: ServerOptions, ): CreateDummyDataSourceResponse => { const [data, setData] = React.useState(); - const previousRowLength = React.useRef(); + const [index, setIndex] = React.useState(0); const options = { ...DEFAULT_DATASET_OPTIONS, ...dataSetOptions }; const columns = React.useMemo(() => { @@ -103,8 +115,19 @@ export const useDemoDataSource = ( }, [isTreeData]); React.useEffect(() => { - const fetchData = async () => { - // Fetch all the data on the first request + const cacheKey = `${options.dataSet}-${options.rowLength}-${index}-${options.maxColumns}`; + + // Cache to allow fast switch between the JavaScript and TypeScript version + // of the demos. + if (dataCache.has(cacheKey)) { + const newData = dataCache.get(cacheKey)!; + setData(newData); + return undefined; + } + + let active = true; + + (async () => { let rowData; const rowLength = options.rowLength; if (rowLength > 1000) { @@ -113,6 +136,11 @@ export const useDemoDataSource = ( } else { rowData = await getRealGridData(rowLength, columns); } + + if (!active) { + return; + } + if (isTreeData) { rowData = addTreeDataOptionsToDemoData(rowData, { maxDepth: options.treeData?.maxDepth, @@ -120,10 +148,18 @@ export const useDemoDataSource = ( averageChildren: options.treeData?.averageChildren, }); } + + if (process.env.NODE_ENV !== 'production') { + deepFreeze(rowData); + } + + dataCache.set(cacheKey, rowData); setData(rowData); - previousRowLength.current = rowLength; + })(); + + return () => { + active = false; }; - fetchData(); }, [ columns, isTreeData, @@ -131,6 +167,9 @@ export const useDemoDataSource = ( options.treeData?.maxDepth, options.treeData?.groupingField, options.treeData?.averageChildren, + options.dataSet, + options.maxColumns, + index, ]); const getRows = React.useCallback( @@ -180,5 +219,8 @@ export const useDemoDataSource = ( hasChildren, getChildrenCount, getRows, + loadNewData: () => { + setIndex((oldIndex) => oldIndex + 1); + }, }; }; diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts index 25b559078a2b..5abd78d3421b 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts @@ -119,14 +119,11 @@ class NestedDataManager { public clearPendingRequests = () => { this.fetchQueue.clear(); this.fetchQueueIndex = 0; - Array.from(this.pendingRequests).forEach((id) => { - this.fetchQueueStatus.set(id, RequestStatus.CANCELLED); - this.api.setRowLoading(id, false); - this.pendingRequests.delete(id); - }); + Array.from(this.pendingRequests).forEach((id) => this.clearPendingRequest(id)); }; public clearPendingRequest = (id: GridRowId) => { + this.api.setRowLoading(id, false); this.fetchQueueStatus.set(id, RequestStatus.CANCELLED); this.pendingRequests.delete(id); }; @@ -148,6 +145,7 @@ export const useGridDataSource = ( new NestedDataManager(privateApiRef), ).current; const groupsToAutoFetch = gridRowGroupsToFetchSelector(privateApiRef); + const getInputParams = React.useCallback( (additionalParams?: Partial): GridGetRowsParams => { const paginationModel = gridPaginationModelSelector(privateApiRef); @@ -283,7 +281,7 @@ export const useGridDataSource = ( (id, isLoading) => { const currentNode = privateApiRef.current.getRowNode(id) as GridServerSideGroupNode; if (!currentNode) { - throw new Error(`MUI: No row with id #${id} found`); + return; } const newNode: GridServerSideGroupNode = { ...currentNode, isLoading }; @@ -304,7 +302,7 @@ export const useGridDataSource = ( (id, childrenFetched) => { const currentNode = privateApiRef.current.getRowNode(id) as GridServerSideGroupNode; if (!currentNode) { - throw new Error(`MUI: No row with id #${id} found`); + return; } const newNode: GridServerSideGroupNode = { ...currentNode, childrenFetched }; From 000ca992b1f969274d0eecd58e518f11e8535981 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 29 Apr 2024 17:17:28 +0500 Subject: [PATCH 10/90] Move the getRowsParams logic outside the hook --- .../src/hooks/useDemoDataSource.ts | 4 +- .../gridServerSideDataSelector.ts | 30 +++ .../serverSideData/serverSideInterfaces.ts | 3 +- .../serverSideData/useGridDataSource.ts | 172 ++++++++---------- .../x-data-grid/src/models/gridDataSource.ts | 8 +- 5 files changed, 109 insertions(+), 108 deletions(-) create mode 100644 packages/x-data-grid-pro/src/hooks/features/serverSideData/gridServerSideDataSelector.ts diff --git a/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts b/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts index af9861db75db..64ed8567b175 100644 --- a/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts +++ b/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts @@ -32,7 +32,7 @@ const dataCache = new LRUCache({ ttl: 60 * 5 * 1e3, // 5 minutes }); -type CreateDummyDataSourceResponse = { +type UseDemoDataSourceResponse = { columns: GridColDef[]; initialState: GridInitialState; getGroupKey?: (row: GridRowModel) => string; @@ -62,7 +62,7 @@ const defaultColDef = getGridDefaultColumnTypes(); export const useDemoDataSource = ( dataSetOptions?: Partial, serverOptions?: ServerOptions, -): CreateDummyDataSourceResponse => { +): UseDemoDataSourceResponse => { const [data, setData] = React.useState(); const [index, setIndex] = React.useState(0); const options = { ...DEFAULT_DATASET_OPTIONS, ...dataSetOptions }; diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/gridServerSideDataSelector.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/gridServerSideDataSelector.ts new file mode 100644 index 000000000000..4d1075366f9e --- /dev/null +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/gridServerSideDataSelector.ts @@ -0,0 +1,30 @@ +import { + GridPaginationModel, + gridFilterModelSelector, + gridSortModelSelector, + gridPaginationModelSelector, +} from '@mui/x-data-grid'; +import { createSelector } from '@mui/x-data-grid/internals'; + +const computeStartEnd = (paginationModel: GridPaginationModel) => { + const start = paginationModel.page * paginationModel.pageSize; + const end = start + paginationModel.pageSize - 1; + return { start, end }; +}; + +export const gridGetRowsParamsSelector = createSelector( + gridFilterModelSelector, + gridSortModelSelector, + gridPaginationModelSelector, + (filterModel, sortModel, paginationModel) => { + return { + groupKeys: [], + // TODO: Implement with `rowGrouping` + groupFields: [], + paginationModel, + sortModel, + filterModel, + ...computeStartEnd(paginationModel), + }; + }, +); diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts index afc2d19af0db..31a1c41693a8 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts @@ -8,9 +8,8 @@ export interface GridDataSourceApi { /** * Initiates the fetch of the children of a row. * @param {string} id The id of the rowNode belonging to the group to be fetched. - * @param {boolean} throttle If `true`, the request will be throttled. (default: `true`) */ - fetchRowChildren: (id: GridRowId, throttle?: boolean) => void; + fetchRowChildren: (id: GridRowId) => void; /** * Set the loading state of a row. * @param {string} id The id of the rowNode. diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts index 5abd78d3421b..13fe1547a029 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts @@ -1,44 +1,22 @@ import * as React from 'react'; import { - gridPaginationModelSelector, - gridFilterModelSelector, - gridSortModelSelector, useGridApiEventHandler, - GridPaginationModel, gridRowsLoadingSelector, useGridApiMethod, GridServerSideGroupNode, GridRowId, + useGridSelector, } from '@mui/x-data-grid'; import { gridRowGroupsToFetchSelector } from '@mui/x-data-grid/internals'; -import { GridGetRowsParams, GridGetRowsResponse, GridDataSource } from '../../../models'; +import { GridGetRowsParams, GridGetRowsResponse } from '../../../models'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; +import { gridGetRowsParamsSelector } from './gridServerSideDataSelector'; import { GridDataSourceApi } from './serverSideInterfaces'; -const computeStartEnd = (paginationModel: GridPaginationModel) => { - const start = paginationModel.page * paginationModel.pageSize; - const end = start + paginationModel.pageSize - 1; - return { start, end }; -}; - const getErrorMessage = (inputParams: GridGetRowsParams) => `MUI: Error in fetching rows for the input params: ${JSON.stringify(inputParams)}`; -const fetchRowsWithError = async ( - getRows: GridDataSource['getRows'], - inputParams: GridGetRowsParams, -) => { - try { - const getRowsResponse = await getRows(inputParams); - return getRowsResponse; - } catch (error) { - throw new Error( - `MUI X: Error in fetching rows for the input params: ${JSON.stringify(inputParams)}`, - ); - } -}; - const runIfServerMode = (modeProp: 'server' | 'client', fn: Function) => () => { if (modeProp === 'server') { fn(); @@ -51,87 +29,97 @@ enum RequestStatus { INQUEUE, PENDING, SETTLED, - CANCELLED, UNKNOWN, } /** - * Fetches row children from the server limiting the number of concurrent requests + * 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` + * Uses `GridRowId` to uniquely identify a request */ class NestedDataManager { private pendingRequests: Set = new Set(); private fetchQueue: Set = new Set(); - private fetchQueueStatus: Map = new Map(); - - private fetchQueueIndex = 0; + private settledRequests: Set = new Set(); private api: GridPrivateApiPro; private maxConcurrentRequests: number; + private timer?: string | number | NodeJS.Timeout; + constructor( - private privateApiRef: React.MutableRefObject, - private maxRequests = MAX_CONCURRENT_REQUESTS, + privateApiRef: React.MutableRefObject, + maxRequests = MAX_CONCURRENT_REQUESTS, ) { this.api = privateApiRef.current; this.maxConcurrentRequests = maxRequests; } + private processQueue = async () => { + if (this.fetchQueue.size === 0) { + clearInterval(this.timer); + return; + } + if (this.pendingRequests.size >= this.maxConcurrentRequests) { + return; + } + const fetchQueue = Array.from(this.fetchQueue); + for (let i = 0; i < this.maxConcurrentRequests; i += 1) { + const nextId = fetchQueue[i]; + this.fetchQueue.delete(nextId); + this.api.fetchRowChildren(nextId); + this.pendingRequests.add(nextId); + } + }; + public fetch = async (ids: GridRowId[]) => { ids.forEach((id) => { if (this.pendingRequests.size < this.maxConcurrentRequests) { this.pendingRequests.add(id); - this.api.fetchRowChildren(id, false); + this.api.fetchRowChildren(id); } else { this.fetchQueue.add(id); - this.fetchQueueStatus.set(id, RequestStatus.INQUEUE); + } + + if (this.fetchQueue.size > 0) { + this.timer = setInterval(this.processQueue, 300); } }); }; public setRequestSettled = (id: GridRowId) => { - this.fetchQueueStatus.set(id, RequestStatus.SETTLED); this.pendingRequests.delete(id); - this.processQueue(); - }; - - public processQueue = async () => { - if (this.fetchQueue.size === 0) { - return; - } - const fetchQueue = Array.from(this.fetchQueue); - for ( - let i = this.fetchQueueIndex; - this.fetchQueueIndex < fetchQueue.length; - this.fetchQueueIndex += 1 - ) { - const nextId = fetchQueue[i]; - if (this.fetchQueueStatus.get(nextId) === RequestStatus.INQUEUE) { - this.api.fetchRowChildren(nextId); - this.fetchQueueStatus.set(nextId, RequestStatus.PENDING); - } - } + this.settledRequests.add(id); }; public clearPendingRequests = () => { + clearInterval(this.timer); this.fetchQueue.clear(); - this.fetchQueueIndex = 0; Array.from(this.pendingRequests).forEach((id) => this.clearPendingRequest(id)); }; public clearPendingRequest = (id: GridRowId) => { this.api.setRowLoading(id, false); - this.fetchQueueStatus.set(id, RequestStatus.CANCELLED); this.pendingRequests.delete(id); }; - public getRequestStatus = (id: GridRowId) => - this.fetchQueueStatus.get(id) ?? RequestStatus.UNKNOWN; + public getRequestStatus = (id: GridRowId) => { + if (this.pendingRequests.has(id)) { + return RequestStatus.PENDING; + } + if (this.fetchQueue.has(id)) { + return RequestStatus.SETTLED; + } + if (this.fetchQueue.has(id)) { + return RequestStatus.INQUEUE; + } + return RequestStatus.UNKNOWN; + }; - public getActiveRequestsCount = () => this.pendingRequests.size; + public getActiveRequestsCount = () => this.pendingRequests.size + this.fetchQueue.size; } export const useGridDataSource = ( @@ -144,23 +132,8 @@ export const useGridDataSource = ( const nestedDataManager = React.useRef( new NestedDataManager(privateApiRef), ).current; - const groupsToAutoFetch = gridRowGroupsToFetchSelector(privateApiRef); - - const getInputParams = React.useCallback( - (additionalParams?: Partial): GridGetRowsParams => { - const paginationModel = gridPaginationModelSelector(privateApiRef); - - return { - groupKeys: [], - paginationModel, - sortModel: gridSortModelSelector(privateApiRef), - filterModel: gridFilterModelSelector(privateApiRef), - ...computeStartEnd(paginationModel), - ...additionalParams, - }; - }, - [privateApiRef], - ); + const groupsToAutoFetch = useGridSelector(privateApiRef, gridRowGroupsToFetchSelector); + const fetchParams = useGridSelector(privateApiRef, gridGetRowsParamsSelector); const fetchTopLevelRows = React.useCallback(async () => { const getRows = props.unstable_dataSource?.getRows; @@ -168,8 +141,7 @@ export const useGridDataSource = ( return; } - const inputParams = getInputParams(); - const cachedData = privateApiRef.current.getCacheData(inputParams) as + const cachedData = privateApiRef.current.getCacheData(fetchParams) as | GridGetRowsResponse | undefined; @@ -189,26 +161,24 @@ export const useGridDataSource = ( privateApiRef.current.setLoading(true); } - const getRowsResponse = await fetchRowsWithError(getRows, inputParams); - // TODO: Add respective events - // @ts-expect-error - privateApiRef.current.publishEvent('loadData', { - params: inputParams, - response: getRowsResponse, - }); - privateApiRef.current.setCacheData(inputParams, getRowsResponse); - if (getRowsResponse.rowCount) { - privateApiRef.current.setRowCount(getRowsResponse.rowCount); + try { + const getRowsResponse = await getRows(fetchParams); + privateApiRef.current.setCacheData(fetchParams, getRowsResponse); + if (getRowsResponse.rowCount) { + privateApiRef.current.setRowCount(getRowsResponse.rowCount); + } + privateApiRef.current.caches.groupKeys = []; + privateApiRef.current.setRows(getRowsResponse.rows); + privateApiRef.current.setLoading(false); + } catch (error) { + privateApiRef.current.setLoading(false); + throw new Error(getErrorMessage(fetchParams)); } - privateApiRef.current.caches.groupKeys = []; - privateApiRef.current.setRows(getRowsResponse.rows); - privateApiRef.current.setLoading(false); - // TODO: handle cursor based pagination } - }, [getInputParams, nestedDataManager, privateApiRef, props.unstable_dataSource?.getRows]); + }, [fetchParams, nestedDataManager, privateApiRef, props.unstable_dataSource?.getRows]); const fetchRowChildren = React.useCallback( - async (id, throttle = true) => { + async (id) => { if (!props.treeData) { return; } @@ -221,7 +191,8 @@ export const useGridDataSource = ( if (!rowNode) { return; } - const inputParams = getInputParams({ groupKeys: rowNode.path }); + + const inputParams = { ...fetchParams, groupKeys: rowNode.path }; const cachedData = privateApiRef.current.getCacheData(inputParams) as | GridGetRowsResponse @@ -230,7 +201,8 @@ export const useGridDataSource = ( if (cachedData != null) { const rows = cachedData.rows; privateApiRef.current.caches.groupKeys = rowNode.path; - privateApiRef.current.updateRows(rows, throttle); + nestedDataManager.setRequestSettled(id); + privateApiRef.current.updateRows(rows, false); if (cachedData.rowCount) { privateApiRef.current.setRowCount(cachedData.rowCount); } @@ -244,20 +216,20 @@ export const useGridDataSource = ( try { const getRowsResponse = await getRows(inputParams); - nestedDataManager.setRequestSettled(id); if (!privateApiRef.current.getRowNode(id)) { nestedDataManager.clearPendingRequest(id); return; } - if (nestedDataManager.getRequestStatus(id) === RequestStatus.CANCELLED) { + if (nestedDataManager.getRequestStatus(id) === RequestStatus.UNKNOWN) { return; } + nestedDataManager.setRequestSettled(id); privateApiRef.current.setCacheData(inputParams, getRowsResponse); if (getRowsResponse.rowCount) { privateApiRef.current.setRowCount(getRowsResponse.rowCount); } privateApiRef.current.caches.groupKeys = rowNode.path; - privateApiRef.current.updateRows(getRowsResponse.rows, throttle); + privateApiRef.current.updateRows(getRowsResponse.rows, false); privateApiRef.current.setRowChildrenExpansion(id, true); privateApiRef.current.setChildrenFetched(id, true); privateApiRef.current.setRowLoading(id, false); @@ -269,7 +241,7 @@ export const useGridDataSource = ( } }, [ - getInputParams, + fetchParams, nestedDataManager, privateApiRef, props.treeData, diff --git a/packages/x-data-grid/src/models/gridDataSource.ts b/packages/x-data-grid/src/models/gridDataSource.ts index 631ed3a4eff4..4797be22b433 100644 --- a/packages/x-data-grid/src/models/gridDataSource.ts +++ b/packages/x-data-grid/src/models/gridDataSource.ts @@ -21,16 +21,16 @@ export interface GridGetRowsParams { * Last row index to fetch. */ end: number; // last row index to fetch + /** + * List of grouped columns (only applicable with `rowGrouping`). + */ + groupFields: GridColDef['field'][]; /** * Array of keys returned by `getGroupKey` of all the parent rows until the row for which the data is requested * `getGroupKey` prop must be implemented to use this. * Useful for `treeData` and `rowGrouping` only. */ groupKeys?: string[]; - /** - * List of grouped columns (only applicable with `rowGrouping`). - */ - groupFields?: GridColDef['field'][]; } export interface GridGetRowsResponse { From 66f0d99a3cd3f0dc5ffc633c2f27d753952b314e Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 29 Apr 2024 22:55:20 +0500 Subject: [PATCH 11/90] Minor improvement --- .../hooks/features/serverSideData/useGridDataSource.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts index 13fe1547a029..4f14432473f1 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts @@ -75,7 +75,7 @@ class NestedDataManager { } }; - public fetch = async (ids: GridRowId[]) => { + public enqueue = async (ids: GridRowId[]) => { ids.forEach((id) => { if (this.pendingRequests.size < this.maxConcurrentRequests) { this.pendingRequests.add(id); @@ -85,6 +85,9 @@ class NestedDataManager { } if (this.fetchQueue.size > 0) { + if (this.timer) { + clearInterval(this.timer); + } this.timer = setInterval(this.processQueue, 300); } }); @@ -113,7 +116,7 @@ class NestedDataManager { if (this.fetchQueue.has(id)) { return RequestStatus.SETTLED; } - if (this.fetchQueue.has(id)) { + if (this.settledRequests.has(id)) { return RequestStatus.INQUEUE; } return RequestStatus.UNKNOWN; @@ -331,7 +334,7 @@ export const useGridDataSource = ( React.useEffect(() => { if (groupsToAutoFetch && groupsToAutoFetch.length > 0) { - nestedDataManager.fetch(groupsToAutoFetch); + nestedDataManager.enqueue(groupsToAutoFetch); privateApiRef.current.setState((state) => ({ ...state, rows: { From 24bd8627422c5ee2ed8af547caf03eaf0fe9ae09 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 29 Apr 2024 22:55:42 +0500 Subject: [PATCH 12/90] Minor docs update --- docs/data/data-grid/server-side-data/index.md | 183 ++++++------------ .../data-grid/server-side-data/tree-data.md | 28 ++- docs/data/pages.ts | 2 +- 3 files changed, 91 insertions(+), 122 deletions(-) diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index ae0cffdfe5f3..3353b15aa97a 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -2,23 +2,15 @@ title: React Data Grid - Server-side data --- -# Data Grid - Server-side data 🚧 +# Data Grid - Server-side data [](/x/introduction/licensing/#pro-plan 'Pro plan')

The data grid server-side data.

-## Overview +## Introduction -Managing server-side data efficiently in a React application can become complex as the dataset grows. +Server-side data management in React can become complex with growing datasets. Challenges include manual data fetching, pagination, sorting, filtering, and performance optimization. A dedicated module can help abstract these complexities, improving user experience. -Without a dedicated module that abstracts its complexities, developers often face challenges related to manual data fetching, pagination, sorting, and filtering, and it often gets trickier to tackle performance issues, which can lead to a poor user experience. - -Have a look at an example: - -### Example scenario - -Imagine having a data grid that displays a list of users. The data grid has pagination enabled and the user can sort the data by clicking on the column headers and also apply filters. - -The data grid is configured to fetch data from the server whenever the user changes the page or updates filtering or sorting. +Consider a data grid displaying a list of users. It supports pagination, sorting by column headers, and filtering. The grid fetches data from the server when the user changes the page or updates filtering or sorting. ```tsx const [rows, setRows] = React.useState([]); @@ -72,26 +64,22 @@ Trying to solve these problems one after the other can make the code complex and ## Data source -A very common pattern to solve these problems is to use a centralized data source. A data source is an abstraction layer that sits between the data grid and the server. It provides a simple interface to the data grid to fetch data and update it. It handles a lot of the complexities related to server-side data fetching. Let's delve a bit deeper into how it will look like. +The idea for a centralized data source is to simplify server-side data fetching. It's an abstraction layer between the data grid and the server, providing a simple interface for interacting with server. :::warning -This feature is still under development and the information shared on this page is subject to change. Feel free to subscribe or comment on the official GitHub [issue](https://github.com/mui/mui-x/issues/8179). +This feature is under development and is marked as **unstable**. The information shared on this page could change in future. Feel free to subscribe or comment on the official GitHub [umbrella issue](https://github.com/mui/mui-x/issues/8179). ::: -### Overview - -The Data Grid already supports manual server-side data fetching for features like sorting, filtering, etc. In order to make it more powerful and simple to use, the grid will support a data source interface that you can implement with your existing data fetching logic. - -The datasource will work with all the major data grid features which require server-side data fetching such as sorting, filtering, pagination, grouping, etc. +The Data Grid already supports manual server-side data fetching for many features. To make it even smoother, you can use the data source that is compatible with existing data fetching logic and all major server-side features. -### Usage +It has an initial set of required methods that you need to implement. The data grid will use these methods internally to fetch a specific sub-set of data when needed. -The data grid server-side data source has an initial set of required methods that you need to implement. The data grid will call these methods internally when the data is required for a specific page. +Let's take a look at the `GridDataSource` interface. ```tsx -interface DataSource { +interface GridDataSource { /** Fetcher Functions: - `getRows` is required @@ -100,23 +88,22 @@ interface DataSource { `getRows` will be used by the grid to fetch data for the current page or children for the current parent group It may return a `rowCount` to update the total count of rows in the grid */ - getRows(params: GetRowsParams): Promise; + getRows(params: GridGetRowsParams): Promise; updateRow?(updatedRow: GridRowModel): Promise; } ``` -Here's how the code will look like for the above example when implemented with data source: +Here's how the above mentioned example will look like when implemented with the data source: ```tsx -const customDataSource: DataSource = { - getRows: async (params: GetRowsParams): GetRowsResponse => { - // fetch data from server +const customDataSource: GridDataSource = { + getRows: async (params: GridGetRowsParams): GetRowsResponse => { const response = await fetch('https://my-api.com/data', { method: 'GET', body: JSON.stringify(params), }); const data = await response.json(); - // return the data and the total number of rows + return { rows: data.rows, rowCount: data.totalCount, @@ -126,127 +113,64 @@ const customDataSource: DataSource = { ``` -{{"demo": "ServerSideDataGrid.js", "bg": "inline"}} - -Not only the code has been reduced significantly, it has removed the hassle of managing controlled states and data fetching logic too. - -On top of that, the data source will also handle a lot of other aspects like caching and deduping of requests. - -#### Loading data +The code has been significantly reduced, the need for managing the controlled states is removed, and data fetching logic is centralized. -The method `dataSource.getRows` will be called with the `GetRowsParams` object whenever some data from the server is needed. This object contains all the information that you need to fetch the data from the server. +### Implementation -Since previously, the data grid did not support internal data fetching, the `rows` prop was the way to pass the data to the grid. However, with server-side data, the `rows` prop is no longer needed. Instead, the data grid will call the `getRows` method whenever it needs to fetch data. +When the data grid requires some data, it calls `getRows` method with the arguments of type `GridGetRowsParams` -Here's the `GetRowsParams` object for reference: +Here's the `GridGetRowsParams` interface for reference: ```tsx -interface GetRowsParams { +interface GridGetRowsParams { sortModel: GridSortModel; filterModel: GridFilterModel; - /** - * Alternate to `start` and `end`, maps to `GridPaginationModel` interface - */ paginationModel: GridPaginationModel; /** - * First row index to fetch (number) or cursor information (number | string) + * First row index to fetch (number) */ - start: number | string; // first row index to fetch or cursor information + start: number; /** - * Last row index to fetch + * Last row index to fetch (number) */ - end: number; // last row index to fetch + end: number; /** - * Array of keys returned by `getGroupKey` of all the parent rows until the row for which the data is requested - * `getGroupKey` prop must be implemented to use this - * Useful for `treeData` and `rowGrouping` only + * List of grouped columns fields (only applicable with `rowGrouping`) */ - groupKeys: string[]; + groupFields: GridColDef['field'][]; /** - * List of grouped columns (only applicable with `rowGrouping`) + * Array of parent row keys until the requested row as supplied by the `getGroupKey` prop + * (Applicable with `treeData` and `rowGrouping`) */ - groupFields: GridColDef['field'][]; // list of grouped columns (`rowGrouping`) + groupKeys?: string[]; } ``` -And here's the `GetRowsResponse` object for reference: +And here's how the expected response of `getRows` method (`GridGetRowsResponse`) looks like: ```tsx -interface GetRowsResponse { - /** - * Subset of the rows as per the passed `GetRowsParams` - */ +interface GridGetRowsResponse { rows: GridRowModel[]; - /** - * To reflect updates in total `rowCount` (optional) - * Useful when the `rowCount` is inaccurate (for example when filtering) or not available upfront - */ rowCount?: number; - /** - * Additional `pageInfo` to help the grid determine if there are more rows to fetch (corner-cases) - * `hasNextPage`: When row count is unknown/inaccurate, if `truncated` is set or rowCount is not known, data will keep loading until `hasNextPage` is `false` - * `truncated`: To reflect `rowCount` is inaccurate (will trigger `x-y of many` in pagination after the count of rows fetched is greater than provided `rowCount`) - * It could be useful with: - * 1. Cursor based pagination: - * When rowCount is not known, grid will check for `hasNextPage` to determine - * if there are more rows to fetch. - * 2. Inaccurate `rowCount`: - * `truncated: true` will let the grid know that `rowCount` is estimated/truncated. - * Thus `hasNextPage` will come into play to check more rows are available to fetch after the number becomes >= provided `rowCount` - */ + estimatedRowCount?: number; pageInfo?: { hasNextPage?: boolean; - truncated?: number; }; } ``` -#### Updating data - -If provided, the method `dataSource.updateRow` will be called with the `GridRowModel` object whenever the user edits a row. This method is optional and you can skip it if you don't need to update the data on the server. It will work in a similar way as the `processRowUpdate` prop. - -#### Data Grid props - -These data grid props will work with the server-side data source: +### Server-side filtering, sorting, and pagination -- `dataSource: DataSource`: the data source object that you need to implement -- `rows`: will be ignored, could be skipped when `dataSource` is provided -- `rowCount`: will be used to identify the total number of rows in the grid, if not provided, the grid will check for the _GetRowsResponse.rowCount_ value, unless the feature being used is infinite loading where no `rowCount` is available at all. +The data source changes how the existing server-side features like `filtering`, `sorting`, and `pagination` work. -Props related to grouped data (`treeData` and `rowGrouping`): +#### Without data source -- `getGroupKey(row: GridRowModel): string` - - will be used by the grid to group rows by their parent group - This effectively replaces `getTreeDataPath`. - Consider this structure: - - ```js - - (1) Sarah // groupKey 'Sarah' - - (2) Thomas // groupKey 'Thomas' - ``` - - When (2) is expanded, the `getRows` function will be called with group keys `['Sarah', 'Thomas']`. - -- `hasChildren?(row: GridRowModel): boolean` - - Will be used by the grid to determine if a row has children on server - -- `getChildrenCount?: (row: GridRowModel) => number` - - Will be used by the grid to determine the number of children of a row on server - -#### Existing server-side features - -The server-side data source will change a bit the way existing server-side features like `filtering`, `sorting`, and `pagination` work. - -**Without data source**: -When there's no data source, the features `filtering`, `sorting`, `pagination` will work on `client` by default. In order for them to work with server-side data, you need to set them to `server` explicitly and listen to the [`onFilterModelChange`](https://mui.com/x/react-data-grid/filtering/server-side/), [`onSortModelChange`](https://mui.com/x/react-data-grid/sorting/#server-side-sorting), [`onPaginationModelChange`](https://mui.com/x/react-data-grid/pagination/#server-side-pagination) events to fetch the data from the server based on the updated variables. +When there's no data source, the features `filtering`, `sorting`, `pagination` will work on `client` by default. In order for them to work with server-side data, you need to set them to `server` explicitly and provide the [`onFilterModelChange`](https://mui.com/x/react-data-grid/filtering/server-side/), [`onSortModelChange`](https://mui.com/x/react-data-grid/sorting/#server-side-sorting), [`onPaginationModelChange`](https://mui.com/x/react-data-grid/pagination/#server-side-pagination) event handlers to fetch the data from the server based on the updated variables. ```tsx ``` -**With data source**: -However, with a valid data source passed the features `filtering`, `sorting`, `pagination` will automatically be set to `server`. +#### With data source + +With the data source, the features `filtering`, `sorting`, `pagination` will automatically be set to `server`. -You just need to implement the `getRows` method and the data grid will call the `getRows` method with the proper params whenever it needs data. +When the corresponding models update, the data grid will call the `getRows` method with the updated values of type `GridGetRowsParams` to get updated data. ```tsx ``` -#### Caching +The following demo uses the prop `unstable_dataSource` to support server-side data fetching. + +{{"demo": "ServerSideDataGrid.js", "bg": "inline"}} + +### Data caching + +The data grid supports caching the data it receives from the server to dedupe the requests. This means that if the user navigates to a page or expands a node that has already been fetched, the grid will not call the `getRows` function again to avoid unnecessary calls to the server. + +Use `unstable_dataSourceCache` prop to initialize a cache, it accepts a generic interface of type `GridDataSourceCache`. + +```tsx +interface GridDataSourceCache { + set: (key: any[], value: unknown) => void; + get: (key: any[]) => unknown; + clear: () => void; +} +``` + +You can use an existing library or write you own custom implementation of the cache. + +### Updating data 🚧 -The data grid will cache the data it receives from the server. This means that if the user navigates to a page that has already been fetched, the grid will not call the `getRows` function again. This is to avoid unnecessary calls to the server. +This feature is yet to be implemented, when completed, the method `dataSource.updateRow` will be called with the `GridRowModel` whenever the user edits a row. It will work in a similar way as the `processRowUpdate` prop. ## API 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 65b2d000fbe3..6bf685bcc884 100644 --- a/docs/data/data-grid/server-side-data/tree-data.md +++ b/docs/data/data-grid/server-side-data/tree-data.md @@ -6,8 +6,32 @@ title: React Server-side tree data

Tree data lazy-loading with server-side data source.

-To use the server-side tree data, pass the `unstable_dataSource` prop as explained in the overview section, in addition to that passing of some additional props is required for the server-side tree data to work properly. +To use the server-side tree data, pass the `unstable_dataSource` prop as explained in the [overview section](/x/react-data-grid/server-side-data/), in addition to that passing of some additional props is required for the server-side tree data to work properly. -Following is a demo of the server-side tree data working with server side data source. It supports server side filtering, sorting and pagination. It also uses the `unstable_dataSourceCache` prop to pass a cache object based on the `QueryClient` exposed by `@tanstack/query-core`. +- `getGroupKey(row: GridRowModel): string` + + Used by the grid to group rows by their parent group. Replaces `getTreeDataPath` used in client-side tree-data. + For example, consider this tree structure for tree data. + + ```js + - (1) Sarah // groupKey 'Sarah' + - (2) Thomas // groupKey 'Thomas' + ``` + + When `(2) Thomas` is expanded, the `getRows` function will be called with group keys `['Sarah', 'Thomas']`. + +- `hasChildren(row: GridRowModel): boolean` + + Used by the grid to check if a row has children on server + +- `getChildrenCount?: (row: GridRowModel) => number` + + Used by the grid to determine the number of children of a row on server + +Following is a demo of the server-side tree data with the server side data source which supports server side filtering, sorting, and pagination. It also uses supports the caching using the `unstable_dataSourceCache` prop based on the `QueryClient` exposed by `@tanstack/query-core`. {{"demo": "ServerSideTreeData.js", "bg": "inline"}} + +:::info +The demo above uses a utility `useDemoDataSource` which uses a data generator service to generate data for testing of the application. It exposes a function called `getRows` which could be used directly as `GridDataSource.getRows`. +::: diff --git a/docs/data/pages.ts b/docs/data/pages.ts index b80fea48f961..10f6791d5670 100644 --- a/docs/data/pages.ts +++ b/docs/data/pages.ts @@ -120,7 +120,7 @@ const pages: MuiPage[] = [ { pathname: '/x/react-data-grid/server-side-data-group', title: 'Server-side data', - planned: true, + plan: 'pro', children: [ { pathname: '/x/react-data-grid/server-side-data', title: 'Overview' }, { From ddccb003ea4c9972f785afb7363a5eccde2c8f48 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 29 Apr 2024 23:13:26 +0500 Subject: [PATCH 13/90] Updates --- docs/data/data-grid/server-side-data/ServerSideTreeData.js | 2 -- docs/data/data-grid/server-side-data/ServerSideTreeData.tsx | 2 -- docs/data/data-grid/server-side-data/tree-data.md | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.js b/docs/data/data-grid/server-side-data/ServerSideTreeData.js index dfccd2fda468..1b817e680c4d 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.js @@ -69,8 +69,6 @@ export default function ServerSideTreeData() { initialState={initialState} slots={{ toolbar: GridToolbar }} slotProps={{ toolbar: { showQuickFilter: true } }} - defaultGroupingExpansionDepth={-1} - filterDebounceMs={1000} />
diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx index 19799fb2b02c..d167b42e1f79 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx @@ -75,8 +75,6 @@ export default function ServerSideTreeData() { initialState={initialState} slots={{ toolbar: GridToolbar }} slotProps={{ toolbar: { showQuickFilter: true } }} - defaultGroupingExpansionDepth={1} - filterDebounceMs={1000} /> 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 6bf685bcc884..ede5a9b68ade 100644 --- a/docs/data/data-grid/server-side-data/tree-data.md +++ b/docs/data/data-grid/server-side-data/tree-data.md @@ -28,7 +28,7 @@ To use the server-side tree data, pass the `unstable_dataSource` prop as explain Used by the grid to determine the number of children of a row on server -Following is a demo of the server-side tree data with the server side data source which supports server side filtering, sorting, and pagination. It also uses supports the caching using the `unstable_dataSourceCache` prop based on the `QueryClient` exposed by `@tanstack/query-core`. +Following is a demo of the server-side tree data with the data source which supports filtering, sorting, and pagination on the server. It also supports the caching using the `unstable_dataSourceCache` prop based on the `QueryClient` exposed by `@tanstack/query-core`. {{"demo": "ServerSideTreeData.js", "bg": "inline"}} From 1618447ca01f26317bdcd30fc79dfd14d5b3ff16 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Sun, 5 May 2024 08:35:49 +0500 Subject: [PATCH 14/90] Improvements --- docs/data/data-grid/server-side-data/index.md | 26 ++--- docs/pages/x/api/data-grid/grid-api.json | 16 +-- .../api-docs/data-grid/grid-api.json | 3 +- .../GridServerSideTreeDataGroupingCell.tsx | 2 +- .../serverSideData/serverSideInterfaces.ts | 11 +- .../serverSideData/useGridDataSource.ts | 101 +++++++++--------- .../src/utils/tree/insertDataRowInTree.ts | 1 - .../x-data-grid/src/models/gridDataSource.ts | 31 +++--- packages/x-data-grid/src/models/gridRows.ts | 4 - scripts/x-data-grid-generator.exports.json | 1 + 10 files changed, 94 insertions(+), 102 deletions(-) diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index 3353b15aa97a..201fad7406b2 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -72,23 +72,25 @@ This feature is under development and is marked as **unstable**. The information ::: -The Data Grid already supports manual server-side data fetching for many features. To make it even smoother, you can use the data source that is compatible with existing data fetching logic and all major server-side features. +The Data Grid already supports manual server-side data fetching for many features. To make it even smoother, you can use the data source. Think of it like a descriptor of the actual data source on server. -It has an initial set of required methods that you need to implement. The data grid will use these methods internally to fetch a specific sub-set of data when needed. +It has an initial set of required methods that you need to implement. The data grid will use these methods internally to fetch a sub-set of data when needed. Let's take a look at the `GridDataSource` interface. ```tsx interface GridDataSource { /** - Fetcher Functions: - - `getRows` is required - - `updateRow` is optional - - `getRows` will be used by the grid to fetch data for the current page or children for the current parent group - It may return a `rowCount` to update the total count of rows in the grid - */ + * This method will be called when the grid needs to fetch some rows + * @param {GridGetRowsParams} params The parameters required to fetch the rows + * @returns {Promise} A promise that resolves to the data of type [GridGetRowsResponse] + */ getRows(params: GridGetRowsParams): Promise; + /** + * This method will be called when the user updates a row [Not yet implemented] + * @param {GridRowModel} updatedRow The updated row + * @returns {Promise} If resolved (synced on the backend), the grid will update the row and mutate the cache + */ updateRow?(updatedRow: GridRowModel): Promise; } ``` @@ -120,7 +122,7 @@ const customDataSource: GridDataSource = { The code has been significantly reduced, the need for managing the controlled states is removed, and data fetching logic is centralized. -### Implementation +### How it works When the data grid requires some data, it calls `getRows` method with the arguments of type `GridGetRowsParams` @@ -194,9 +196,9 @@ When there's no data source, the features `filtering`, `sorting`, `pagination` w #### With data source -With the data source, the features `filtering`, `sorting`, `pagination` will automatically be set to `server`. +With the data source, the features `filtering`, `sorting`, `pagination` are automatically be set to `server`. -When the corresponding models update, the data grid will call the `getRows` method with the updated values of type `GridGetRowsParams` to get updated data. +When the corresponding models update, the data grid calls the `getRows` method with the updated values of type `GridGetRowsParams` to get updated data. ```tsx GridFilterItem." }, + "enqueueChildrenFetch": { "description": "Enqueues the fetch of the children of a row." }, "exportDataAsCsv": { "description": "Downloads and exports a CSV of the grid's data." }, "exportDataAsExcel": { "description": "Downloads and exports an Excel file of the grid's data." @@ -156,7 +157,6 @@ "setCellSelectionModel": { "description": "Updates the selected cells to be those passed to the newModel argument.
Any cell already selected will be unselected." }, - "setChildrenFetched": { "description": "Set the fetched children state of a row." }, "setColumnHeaderFilterFocus": { "description": "Sets the focus to the column header filter at the given field." }, @@ -191,6 +191,7 @@ "setPageSize": { "description": "Sets the number of displayed rows to the value given by pageSize." }, + "setPaginationMeta": { "description": "Sets the paginationMeta to a new value." }, "setPaginationModel": { "description": "Sets the paginationModel to a new value." }, diff --git a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx b/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx index 8c16daefbd47..f47b79d0ef07 100644 --- a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx +++ b/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx @@ -60,7 +60,7 @@ function GridTreeDataGroupingCellIcon(props: GridTreeDataGroupingCellIconProps) const handleClick = (event: React.MouseEvent) => { if (isServerSideNode && !rowNode.childrenExpanded) { // always fetch/get from cache the children when the node is collapsed - apiRef.current.fetchRowChildren(id); + apiRef.current.enqueueChildrenFetch(id); } else { apiRef.current.setRowChildrenExpansion(id, !rowNode.childrenExpanded); } diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts index 31a1c41693a8..8a681fd43ea7 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts @@ -16,16 +16,15 @@ export interface GridDataSourceApi { * @param {boolean} loading The loading state to set. */ setRowLoading: (id: GridRowId, loading: boolean) => void; - /** - * Set the fetched children state of a row. - * @param {string} id The id of the rowNode. - * @param {boolean} childrenFetched The children to set. - */ - setChildrenFetched: (id: GridRowId, childrenFetched: boolean) => void; /** * Fetches the top level rows. */ fetchTopLevelRows: () => void; + /** + * Enqueues the fetch of the children of a row. + * @param {GridRowId} id The id of the rowNode belonging to the group to be fetched. + */ + enqueueChildrenFetch: (id: GridRowId) => void; } /** diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts index 4f14432473f1..403bf422727c 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts @@ -23,7 +23,9 @@ const runIfServerMode = (modeProp: 'server' | 'client', fn: Function) => () => { } }; +// Make these configurable using dedicated props? const MAX_CONCURRENT_REQUESTS = Infinity; +const QUEUE_PROCESS_INTERVAL_MS = 300; enum RequestStatus { INQUEUE, @@ -40,7 +42,7 @@ enum RequestStatus { class NestedDataManager { private pendingRequests: Set = new Set(); - private fetchQueue: Set = new Set(); + private queuedRequests: Set = new Set(); private settledRequests: Set = new Set(); @@ -48,28 +50,36 @@ class NestedDataManager { private maxConcurrentRequests: number; + private queueProcessInterval: number; + private timer?: string | number | NodeJS.Timeout; constructor( privateApiRef: React.MutableRefObject, - maxRequests = MAX_CONCURRENT_REQUESTS, + maxConcurrentRequests = MAX_CONCURRENT_REQUESTS, + queueProcessInterval = QUEUE_PROCESS_INTERVAL_MS, ) { this.api = privateApiRef.current; - this.maxConcurrentRequests = maxRequests; + this.maxConcurrentRequests = maxConcurrentRequests; + this.queueProcessInterval = queueProcessInterval; } private processQueue = async () => { - if (this.fetchQueue.size === 0) { + if (this.queuedRequests.size === 0) { clearInterval(this.timer); return; } if (this.pendingRequests.size >= this.maxConcurrentRequests) { return; } - const fetchQueue = Array.from(this.fetchQueue); + const fetchQueue = Array.from(this.queuedRequests); for (let i = 0; i < this.maxConcurrentRequests; i += 1) { const nextId = fetchQueue[i]; - this.fetchQueue.delete(nextId); + if (!nextId) { + clearInterval(this.timer); + return; + } + this.queuedRequests.delete(nextId); this.api.fetchRowChildren(nextId); this.pendingRequests.add(nextId); } @@ -81,14 +91,14 @@ class NestedDataManager { this.pendingRequests.add(id); this.api.fetchRowChildren(id); } else { - this.fetchQueue.add(id); + this.queuedRequests.add(id); } - if (this.fetchQueue.size > 0) { + if (this.queuedRequests.size > 0) { if (this.timer) { clearInterval(this.timer); } - this.timer = setInterval(this.processQueue, 300); + this.timer = setInterval(this.processQueue, this.queueProcessInterval); } }); }; @@ -100,7 +110,7 @@ class NestedDataManager { public clearPendingRequests = () => { clearInterval(this.timer); - this.fetchQueue.clear(); + this.queuedRequests.clear(); Array.from(this.pendingRequests).forEach((id) => this.clearPendingRequest(id)); }; @@ -113,16 +123,16 @@ class NestedDataManager { if (this.pendingRequests.has(id)) { return RequestStatus.PENDING; } - if (this.fetchQueue.has(id)) { - return RequestStatus.SETTLED; + if (this.queuedRequests.has(id)) { + return RequestStatus.INQUEUE; } if (this.settledRequests.has(id)) { - return RequestStatus.INQUEUE; + return RequestStatus.SETTLED; } return RequestStatus.UNKNOWN; }; - public getActiveRequestsCount = () => this.pendingRequests.size + this.fetchQueue.size; + public getActiveRequestsCount = () => this.pendingRequests.size + this.queuedRequests.size; } export const useGridDataSource = ( @@ -136,7 +146,6 @@ export const useGridDataSource = ( new NestedDataManager(privateApiRef), ).current; const groupsToAutoFetch = useGridSelector(privateApiRef, gridRowGroupsToFetchSelector); - const fetchParams = useGridSelector(privateApiRef, gridGetRowsParamsSelector); const fetchTopLevelRows = React.useCallback(async () => { const getRows = props.unstable_dataSource?.getRows; @@ -144,6 +153,8 @@ export const useGridDataSource = ( return; } + const fetchParams = gridGetRowsParamsSelector(privateApiRef); + const cachedData = privateApiRef.current.getCacheData(fetchParams) as | GridGetRowsResponse | undefined; @@ -178,26 +189,36 @@ export const useGridDataSource = ( throw new Error(getErrorMessage(fetchParams)); } } - }, [fetchParams, nestedDataManager, privateApiRef, props.unstable_dataSource?.getRows]); + }, [nestedDataManager, privateApiRef, props.unstable_dataSource?.getRows]); + + const enqueueChildrenFetch = React.useCallback( + (id: GridRowId) => { + nestedDataManager.enqueue([id]); + }, + [nestedDataManager], + ); const fetchRowChildren = React.useCallback( async (id) => { if (!props.treeData) { + nestedDataManager.clearPendingRequest(id); return; } const getRows = props.unstable_dataSource?.getRows; if (!getRows) { + nestedDataManager.clearPendingRequest(id); return; } const rowNode = privateApiRef.current.getRowNode(id) as GridServerSideGroupNode; if (!rowNode) { + nestedDataManager.clearPendingRequest(id); return; } - const inputParams = { ...fetchParams, groupKeys: rowNode.path }; + const fetchParams = { ...gridGetRowsParamsSelector(privateApiRef), groupKeys: rowNode.path }; - const cachedData = privateApiRef.current.getCacheData(inputParams) as + const cachedData = privateApiRef.current.getCacheData(fetchParams) as | GridGetRowsResponse | undefined; @@ -210,7 +231,6 @@ export const useGridDataSource = ( privateApiRef.current.setRowCount(cachedData.rowCount); } privateApiRef.current.setRowChildrenExpansion(id, true); - privateApiRef.current.setChildrenFetched(id, true); } else { const isLoading = rowNode.isLoading; if (!isLoading) { @@ -218,38 +238,35 @@ export const useGridDataSource = ( } try { - const getRowsResponse = await getRows(inputParams); + const getRowsResponse = await getRows(fetchParams); if (!privateApiRef.current.getRowNode(id)) { + // The row has been removed from the grid nestedDataManager.clearPendingRequest(id); + privateApiRef.current.setRowLoading(id, false); return; } if (nestedDataManager.getRequestStatus(id) === RequestStatus.UNKNOWN) { + // Unregistered or cancelled request + privateApiRef.current.setRowLoading(id, false); return; } nestedDataManager.setRequestSettled(id); - privateApiRef.current.setCacheData(inputParams, getRowsResponse); + privateApiRef.current.setCacheData(fetchParams, getRowsResponse); if (getRowsResponse.rowCount) { privateApiRef.current.setRowCount(getRowsResponse.rowCount); } privateApiRef.current.caches.groupKeys = rowNode.path; privateApiRef.current.updateRows(getRowsResponse.rows, false); privateApiRef.current.setRowChildrenExpansion(id, true); - privateApiRef.current.setChildrenFetched(id, true); privateApiRef.current.setRowLoading(id, false); } catch (error) { nestedDataManager.setRequestSettled(id); privateApiRef.current.setRowLoading(id, false); - throw new Error(getErrorMessage(inputParams)); + throw new Error(getErrorMessage(fetchParams)); } } }, - [ - fetchParams, - nestedDataManager, - privateApiRef, - props.treeData, - props.unstable_dataSource?.getRows, - ], + [nestedDataManager, privateApiRef, props.treeData, props.unstable_dataSource?.getRows], ); const setRowLoading = React.useCallback( @@ -273,31 +290,11 @@ export const useGridDataSource = ( [privateApiRef], ); - const setChildrenFetched = React.useCallback( - (id, childrenFetched) => { - const currentNode = privateApiRef.current.getRowNode(id) as GridServerSideGroupNode; - if (!currentNode) { - return; - } - - const newNode: GridServerSideGroupNode = { ...currentNode, childrenFetched }; - privateApiRef.current.setState((state) => { - return { - ...state, - rows: { - ...state.rows, - tree: { ...state.rows.tree, [id]: newNode }, - }, - }; - }); - }, - [privateApiRef], - ); - const dataSourceApi: GridDataSourceApi = { + enqueueChildrenFetch, + // TODO: Make `fetchRowChildren` private fetchRowChildren, setRowLoading, - setChildrenFetched, fetchTopLevelRows, }; diff --git a/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts b/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts index 063caf5bc8fe..c2dbd7c1f821 100644 --- a/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts +++ b/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts @@ -114,7 +114,6 @@ export const insertDataRowInTree = ({ childrenExpanded: false, isLoading: false, isServerSide: true, - childrenFetched: false, }; const shouldFetchChildren = checkGroupChildrenExpansion( node, diff --git a/packages/x-data-grid/src/models/gridDataSource.ts b/packages/x-data-grid/src/models/gridDataSource.ts index 4797be22b433..29c1a5445c7f 100644 --- a/packages/x-data-grid/src/models/gridDataSource.ts +++ b/packages/x-data-grid/src/models/gridDataSource.ts @@ -41,35 +41,28 @@ export interface GridGetRowsResponse { */ rowCount?: number; /** - * Additional `pageInfo` to help the grid determine if there are more rows to fetch (corner-cases). - * `hasNextPage`: When row count is unknown/inaccurate, if `truncated` is set or rowCount is not known, data will keep loading until `hasNextPage` is `false` - * `truncated`: To reflect `rowCount` is inaccurate (will trigger `x-y of many` in pagination after the count of rows fetched is greater than provided `rowCount`) - * It could be useful with: - * 1. Cursor based pagination: - * When rowCount is not known, grid will check for `hasNextPage` to determine - * if there are more rows to fetch. - * 2. Inaccurate `rowCount`: - * `truncated: true` will let the grid know that `rowCount` is estimated/truncated. - * Thus `hasNextPage` will come into play to check more rows are available to fetch after the number becomes >= provided `rowCount` + * Additional `pageInfo` for advanced use-cases. + * `hasNextPage`: When row count is unknown/estimated, `hasNextPage` will be used to check if more records are available on server */ pageInfo?: { - nextCursor?: number | string; hasNextPage?: boolean; - truncated?: number; + nextCursor?: string; }; } export interface GridDataSource { /** - * Fetcher Functions: - * - `getRows` is required - * - `updateRow` is optional - * - * `getRows` will be used by the grid to fetch data for the current page or children for the current parent group. - * It may return a `rowCount` to update the total count of rows in the grid along with the optional `pageInfo`. + * This method will be called when the grid needs to fetch some rows + * @param {GridGetRowsParams} params The parameters required to fetch the rows + * @returns {Promise} A promise that resolves to the data of type [GridGetRowsResponse] */ getRows(params: GridGetRowsParams): Promise; - updateRow?(rows: GridRowModel): Promise; + /** + * This method will be called when the user updates a row [Not yet implemented] + * @param {GridRowModel} updatedRow The updated row + * @returns {Promise} If resolved (synced on the backend), the grid will update the row and mutate the cache + */ + updateRow?(updatedRow: GridRowModel): Promise; } export interface GridDataSourceCache { diff --git a/packages/x-data-grid/src/models/gridRows.ts b/packages/x-data-grid/src/models/gridRows.ts index c89b55647e3c..aa6992d14791 100644 --- a/packages/x-data-grid/src/models/gridRows.ts +++ b/packages/x-data-grid/src/models/gridRows.ts @@ -123,10 +123,6 @@ export interface GridServerSideGroupNode extends GridDataGroupNode { * If true, this node is a server side group node. */ isServerSide: boolean; - /** - * If true, this node has been expanded by the user and the children have been fetched. - */ - childrenFetched: boolean; /** * The cached path to be passed on as `groupKey` to the server. */ diff --git a/scripts/x-data-grid-generator.exports.json b/scripts/x-data-grid-generator.exports.json index 18dc8b0ac178..189f25f961a1 100644 --- a/scripts/x-data-grid-generator.exports.json +++ b/scripts/x-data-grid-generator.exports.json @@ -2,6 +2,7 @@ { "name": "ColumnsOptions", "kind": "Interface" }, { "name": "createFakeServer", "kind": "Variable" }, { "name": "currencyPairs", "kind": "Variable" }, + { "name": "deepFreeze", "kind": "Variable" }, { "name": "DemoDataReturnType", "kind": "TypeAlias" }, { "name": "DemoLink", "kind": "Variable" }, { "name": "extrapolateSeed", "kind": "Function" }, From 931831c49d5723afe70b3f5328fd71b83910bb3a Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 6 May 2024 15:13:22 +0500 Subject: [PATCH 15/90] Improve defaultGroupingExpansionDepth fetch logic --- .../serverSideData/useGridDataSource.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts index 403bf422727c..9b5bc186c834 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts @@ -146,6 +146,7 @@ export const useGridDataSource = ( new NestedDataManager(privateApiRef), ).current; const groupsToAutoFetch = useGridSelector(privateApiRef, gridRowGroupsToFetchSelector); + const scheduledGroups = React.useRef(0); const fetchTopLevelRows = React.useCallback(async () => { const getRows = props.unstable_dataSource?.getRows; @@ -162,6 +163,7 @@ export const useGridDataSource = ( if (nestedDataManager.getActiveRequestsCount() > 0) { nestedDataManager.clearPendingRequests(); } + scheduledGroups.current = 0; if (cachedData != null) { const rows = cachedData.rows; privateApiRef.current.caches.groupKeys = []; @@ -330,15 +332,14 @@ export const useGridDataSource = ( }, [privateApiRef, props.unstable_dataSource]); React.useEffect(() => { - if (groupsToAutoFetch && groupsToAutoFetch.length > 0) { - nestedDataManager.enqueue(groupsToAutoFetch); - privateApiRef.current.setState((state) => ({ - ...state, - rows: { - ...state.rows, - groupsToFetch: [], - }, - })); + if ( + groupsToAutoFetch && + groupsToAutoFetch.length && + scheduledGroups.current < groupsToAutoFetch.length + ) { + const groupsToSchedule = groupsToAutoFetch.slice(scheduledGroups.current); + nestedDataManager.enqueue(groupsToSchedule); + scheduledGroups.current = groupsToAutoFetch.length; } }, [privateApiRef, nestedDataManager, groupsToAutoFetch]); }; From 62669212f5838a885d11aef67f0e039b6bdec783 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Fri, 10 May 2024 21:05:56 +0500 Subject: [PATCH 16/90] Add error handling demo --- .../ServerSideErrorHandling.js | 116 +++++++++++++++++ .../ServerSideErrorHandling.tsx | 121 ++++++++++++++++++ .../ServerSideTreeData.tsx.preview | 15 +++ docs/data/data-grid/server-side-data/index.md | 24 +++- .../src/hooks/useDemoDataSource.ts | 13 ++ .../serverSideData/useGridDataSource.ts | 22 ++-- .../src/internals/propValidation.ts | 5 - .../src/models/dataGridProProps.ts | 2 + 8 files changed, 301 insertions(+), 17 deletions(-) create mode 100644 docs/data/data-grid/server-side-data/ServerSideErrorHandling.js create mode 100644 docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx create mode 100644 docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview diff --git a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js new file mode 100644 index 000000000000..84d8e2b138cc --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js @@ -0,0 +1,116 @@ +import * as React from 'react'; +import { DataGridPro, useGridApiRef, GridToolbar } from '@mui/x-data-grid-pro'; +import Button from '@mui/material/Button'; +import Slider from '@mui/material/Slider'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import { alpha, styled, darken, lighten } from '@mui/material/styles'; +import { useDemoDataSource } from '@mui/x-data-grid-generator'; + +const pageSizeOptions = [5, 10, 50]; + +function getBorderColor(theme) { + if (theme.palette.mode === 'light') { + return lighten(alpha(theme.palette.divider, 1), 0.88); + } + return darken(alpha(theme.palette.divider, 1), 0.68); +} + +const StyledDiv = styled('div')(({ theme: t }) => ({ + position: 'absolute', + zIndex: 10, + fontSize: '0.875em', + top: 0, + height: '100%', + width: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + borderRadius: '4px', + border: `1px solid ${getBorderColor(t)}`, + backgroundColor: t.palette.background.default, +})); + +function ErrorOverlay({ error }) { + if (!error?.message) { + return null; + } + return {error.message}; +} + +export default function ServerSideErrorHandling() { + const apiRef = useGridApiRef(); + const [error, setError] = React.useState(); + const [serverOptions, setServerOptions] = React.useState({ + useCursorPagination: false, + successRate: 0.5, + }); + + const { getRows, ...props } = useDemoDataSource({}, serverOptions); + + const dataSource = React.useMemo(() => { + return { + getRows, + }; + }, [getRows]); + + const initialState = React.useMemo( + () => ({ + ...props.initialState, + pagination: { + paginationModel: { + pageSize: 5, + }, + rowCount: 0, + }, + }), + [props.initialState], + ); + + return ( +
+
+ + + Success Rate + + setServerOptions((prev) => ({ + ...prev, + successRate: Number(value) / 100, + })) + } + /> + +
+
+ setError(e)} + apiRef={apiRef} + pagination + pageSizeOptions={pageSizeOptions} + initialState={initialState} + slots={{ toolbar: GridToolbar }} + /> + {error && } +
+
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx new file mode 100644 index 000000000000..a91ab785d005 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx @@ -0,0 +1,121 @@ +import * as React from 'react'; +import { + DataGridPro, + useGridApiRef, + GridInitialState, + GridToolbar, +} from '@mui/x-data-grid-pro'; +import Button from '@mui/material/Button'; +import Slider from '@mui/material/Slider'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import { alpha, styled, darken, lighten, Theme } from '@mui/material/styles'; +import { useDemoDataSource } from '@mui/x-data-grid-generator'; + +const pageSizeOptions = [5, 10, 50]; + +function getBorderColor(theme: Theme) { + if (theme.palette.mode === 'light') { + return lighten(alpha(theme.palette.divider, 1), 0.88); + } + return darken(alpha(theme.palette.divider, 1), 0.68); +} + +const StyledDiv = styled('div')(({ theme: t }) => ({ + position: 'absolute', + zIndex: 10, + fontSize: '0.875em', + top: 0, + height: '100%', + width: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + borderRadius: '4px', + border: `1px solid ${getBorderColor(t)}`, + backgroundColor: t.palette.background.default, +})); + +function ErrorOverlay({ error }: { error: Error }) { + if (!error?.message) { + return null; + } + return {error.message}; +} + +export default function ServerSideErrorHandling() { + const apiRef = useGridApiRef(); + const [error, setError] = React.useState(); + const [serverOptions, setServerOptions] = React.useState({ + useCursorPagination: false, + successRate: 0.5, + }); + + const { getRows, ...props } = useDemoDataSource({}, serverOptions); + + const dataSource = React.useMemo(() => { + return { + getRows, + }; + }, [getRows]); + + const initialState: GridInitialState = React.useMemo( + () => ({ + ...props.initialState, + pagination: { + paginationModel: { + pageSize: 5, + }, + rowCount: 0, + }, + }), + [props.initialState], + ); + + return ( +
+
+ + + Success Rate + + setServerOptions((prev) => ({ + ...prev, + successRate: Number(value) / 100, + })) + } + /> + +
+
+ setError(e)} + apiRef={apiRef} + pagination + pageSizeOptions={pageSizeOptions} + initialState={initialState} + slots={{ toolbar: GridToolbar }} + /> + {error && } +
+
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview new file mode 100644 index 000000000000..df30522114e6 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview @@ -0,0 +1,15 @@ + +
+ +
\ No newline at end of file diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index 201fad7406b2..7b019a130b74 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -64,7 +64,7 @@ Trying to solve these problems one after the other can make the code complex and ## Data source -The idea for a centralized data source is to simplify server-side data fetching. It's an abstraction layer between the data grid and the server, providing a simple interface for interacting with server. +The idea for a centralized data source is to simplify server-side data fetching. It's an abstraction layer between the data grid and the server, providing a simple interface for interacting with server. Think of it like a middle-man handling the communication between the Data Grid (client) and the actual data source (server). :::warning @@ -72,8 +72,6 @@ This feature is under development and is marked as **unstable**. The information ::: -The Data Grid already supports manual server-side data fetching for many features. To make it even smoother, you can use the data source. Think of it like a descriptor of the actual data source on server. - It has an initial set of required methods that you need to implement. The data grid will use these methods internally to fetch a sub-set of data when needed. Let's take a look at the `GridDataSource` interface. @@ -227,6 +225,26 @@ interface GridDataSourceCache { You can use an existing library or write you own custom implementation of the cache. +### Error handling + +You could handle the errors with the data source by providing an error handler function using the `unstable_onDataSourceError`. It will be called whenever there's an error in fetching the data. + +The first argument of this function is the error object, and the second argument is the fetch parameters of type `GridGetRowsParams`. + +```tsx + { + console.error(error); + }} +/> +``` + +The demo below uses the `useDemoDataSource` utility to simulate the server-side error. Change the value of success rate make the server-side error occur randomly. + +{{"demo": "ServerSideErrorHandling.js", "bg": "inline"}} + ### Updating data 🚧 This feature is yet to be implemented, when completed, the method `dataSource.updateRow` will be called with the `GridRowModel` whenever the user edits a row. It will work in a similar way as the `processRowUpdate` prop. diff --git a/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts b/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts index 64ed8567b175..3373faa75feb 100644 --- a/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts +++ b/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts @@ -26,6 +26,7 @@ import { DEFAULT_SERVER_OPTIONS, } from './serverUtils'; import type { ServerOptions } from './serverUtils'; +import { randomInt, random } from '../services'; const dataCache = new LRUCache({ max: 10, @@ -182,6 +183,18 @@ export const useDemoDataSource = ( let getRowsResponse: GridGetRowsResponse; const serverOptionsWithDefault = { ...DEFAULT_SERVER_OPTIONS, ...serverOptions }; + const { successRate = 1 } = serverOptionsWithDefault; + if (successRate !== 1) { + const rate = random(0.01, 0.99); + if (rate > successRate) { + const { minDelay, maxDelay } = serverOptionsWithDefault; + const delay = randomInt(minDelay, maxDelay); + return new Promise((_, reject) => { + setTimeout(() => reject(new Error('Could not fetch the data')), delay); + }); + } + } + if (isTreeData /* || TODO: `isRowGrouping` */) { const { rows, rootRowCount } = await processTreeDataRows( data.rows, diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts index 9b5bc186c834..d97fabf853fe 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts @@ -8,15 +8,12 @@ import { useGridSelector, } from '@mui/x-data-grid'; import { gridRowGroupsToFetchSelector } from '@mui/x-data-grid/internals'; -import { GridGetRowsParams, GridGetRowsResponse } from '../../../models'; +import { GridGetRowsResponse } from '../../../models'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; import { gridGetRowsParamsSelector } from './gridServerSideDataSelector'; import { GridDataSourceApi } from './serverSideInterfaces'; -const getErrorMessage = (inputParams: GridGetRowsParams) => - `MUI: Error in fetching rows for the input params: ${JSON.stringify(inputParams)}`; - const runIfServerMode = (modeProp: 'server' | 'client', fn: Function) => () => { if (modeProp === 'server') { fn(); @@ -139,7 +136,12 @@ export const useGridDataSource = ( privateApiRef: React.MutableRefObject, props: Pick< DataGridProProcessedProps, - 'unstable_dataSource' | 'sortingMode' | 'filterMode' | 'paginationMode' | 'treeData' + | 'unstable_dataSource' + | 'unstable_onDataSourceError' + | 'sortingMode' + | 'filterMode' + | 'paginationMode' + | 'treeData' >, ): void => { const nestedDataManager = React.useRef( @@ -147,6 +149,7 @@ export const useGridDataSource = ( ).current; const groupsToAutoFetch = useGridSelector(privateApiRef, gridRowGroupsToFetchSelector); const scheduledGroups = React.useRef(0); + const onError = props.unstable_onDataSourceError; const fetchTopLevelRows = React.useCallback(async () => { const getRows = props.unstable_dataSource?.getRows; @@ -187,11 +190,12 @@ export const useGridDataSource = ( privateApiRef.current.setRows(getRowsResponse.rows); privateApiRef.current.setLoading(false); } catch (error) { + privateApiRef.current.setRows([]); privateApiRef.current.setLoading(false); - throw new Error(getErrorMessage(fetchParams)); + onError?.(error as Error, fetchParams); } } - }, [nestedDataManager, privateApiRef, props.unstable_dataSource?.getRows]); + }, [nestedDataManager, privateApiRef, props.unstable_dataSource?.getRows, onError]); const enqueueChildrenFetch = React.useCallback( (id: GridRowId) => { @@ -264,11 +268,11 @@ export const useGridDataSource = ( } catch (error) { nestedDataManager.setRequestSettled(id); privateApiRef.current.setRowLoading(id, false); - throw new Error(getErrorMessage(fetchParams)); + onError?.(error as Error, fetchParams); } } }, - [nestedDataManager, privateApiRef, props.treeData, props.unstable_dataSource?.getRows], + [nestedDataManager, onError, privateApiRef, props.treeData, props.unstable_dataSource?.getRows], ); const setRowLoading = React.useCallback( diff --git a/packages/x-data-grid-pro/src/internals/propValidation.ts b/packages/x-data-grid-pro/src/internals/propValidation.ts index 30304fa52fad..13f138529d47 100644 --- a/packages/x-data-grid-pro/src/internals/propValidation.ts +++ b/packages/x-data-grid-pro/src/internals/propValidation.ts @@ -24,11 +24,6 @@ export const propValidatorsDataGridPro: PropValidator props.checkboxSelectionVisibleOnly && 'MUI X: The `checkboxSelectionVisibleOnly` prop has no effect when the pagination is not enabled.') || undefined, - (props) => - (props.unstable_dataSource && - props.rows && - 'MUI X: The `rows` prop has no effect when the `unstable_dataSource` prop is passed.') || - undefined, (props) => (props.signature !== GridSignature.DataGrid && props.paginationMode === 'client' && diff --git a/packages/x-data-grid-pro/src/models/dataGridProProps.ts b/packages/x-data-grid-pro/src/models/dataGridProProps.ts index bf9a83ed1cc8..ef1bfcea5e65 100644 --- a/packages/x-data-grid-pro/src/models/dataGridProProps.ts +++ b/packages/x-data-grid-pro/src/models/dataGridProProps.ts @@ -18,6 +18,7 @@ import type { DataGridProSharedPropsWithDefaultValue, DataGridProSharedPropsWithoutDefaultValue, GridDataSourceCache, + GridGetRowsParams, } from '@mui/x-data-grid/internals'; import type { GridPinnedRowsProp } from '../hooks/features/rowPinning'; import { GridApiPro } from './gridApiPro'; @@ -145,6 +146,7 @@ export interface DataGridProPropsWithDefaultValue void; getGroupKey?: (row: GridValidRowModel) => string; hasChildren?: (row: GridValidRowModel) => boolean; getChildrenCount?: (row: GridValidRowModel) => number; From 67e03847ea718491633db40cb63b6746be63e7c5 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 13 May 2024 01:17:56 +0500 Subject: [PATCH 17/90] Add an internal cache + a few demos --- .../ServerSideDataGridDummyJson.js | 71 ++++++++++++ .../ServerSideDataGridDummyJson.tsx | 77 +++++++++++++ .../ServerSideDataGridDummyJson.tsx.preview | 15 +++ .../ServerSideDataGridNoCache.js | 41 +++++++ .../ServerSideDataGridNoCache.tsx | 41 +++++++ .../ServerSideDataGridNoCache.tsx.preview | 8 ++ .../ServerSideDataGridWithSWR.js | 62 ++++++++++ .../ServerSideDataGridWithSWR.tsx | 66 +++++++++++ .../server-side-data/ServerSideTreeData.js | 24 +--- .../server-side-data/ServerSideTreeData.tsx | 25 +--- .../ServerSideTreeData.tsx.preview | 3 +- docs/data/data-grid/server-side-data/index.md | 108 +++++++++--------- .../data-grid/server-side-data/tree-data.md | 22 +++- docs/package.json | 1 + docs/pages/x/api/data-grid/grid-api.json | 4 +- .../api-docs/data-grid/grid-api.json | 6 +- .../src/DataGridPremium/DataGridPremium.tsx | 2 + .../src/DataGridPro/DataGridPro.tsx | 2 + .../serverSideData/serverSideInterfaces.ts | 12 +- .../serverSideData/useGridDataSource.ts | 96 ++++++++-------- .../serverSideData/useGridServerSideCache.ts | 59 +++++++--- .../x-data-grid/src/models/gridDataSource.ts | 23 +++- pnpm-lock.yaml | 21 ++++ 23 files changed, 605 insertions(+), 184 deletions(-) create mode 100644 docs/data/data-grid/server-side-data/ServerSideDataGridDummyJson.js create mode 100644 docs/data/data-grid/server-side-data/ServerSideDataGridDummyJson.tsx create mode 100644 docs/data/data-grid/server-side-data/ServerSideDataGridDummyJson.tsx.preview create mode 100644 docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js create mode 100644 docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx create mode 100644 docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx.preview create mode 100644 docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js create mode 100644 docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridDummyJson.js b/docs/data/data-grid/server-side-data/ServerSideDataGridDummyJson.js new file mode 100644 index 000000000000..3ff558cdb400 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridDummyJson.js @@ -0,0 +1,71 @@ +import * as React from 'react'; +import { DataGridPro } from '@mui/x-data-grid-pro'; + +function ThumbnailCell(props) { + if (!props.value) { + return null; + } + const url = props.value; + return ( + {props.row.product} + ); +} + +const columns = [ + { + field: 'thumbnail', + headerName: 'Preview', + width: 120, + renderCell: ThumbnailCell, + }, + { field: 'title', headerName: 'Product', width: 200 }, + { field: 'description', headerName: 'Description', width: 200 }, + { field: 'brand', headerName: 'Brand', width: 150 }, + { + field: 'price', + type: 'number', + headerName: 'Price', + width: 80, + valueFormatter: (value) => `$${value}`, + }, +]; + +const dataSource = { + getRows: async (params) => { + const { pageSize, page } = params.paginationModel; + const response = await fetch( + `https://dummyjson.com/products?limit=${pageSize}&skip=${pageSize * page}`, + ); + const data = await response.json(); + return { + rows: data.products, + rowCount: data.total, + }; + }, +}; + +export default function ServerSideDataGridDummyJson() { + return ( +
+ 100} + pinnedColumns={{ left: ['thumbnail'], right: ['price'] }} + disableColumnSorting + disableColumnFilter + /> +
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridDummyJson.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridDummyJson.tsx new file mode 100644 index 000000000000..89174796b8a5 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridDummyJson.tsx @@ -0,0 +1,77 @@ +import * as React from 'react'; +import { + DataGridPro, + GridCellParams, + GridColDef, + GridDataSource, + GridGetRowsParams, +} from '@mui/x-data-grid-pro'; + +function ThumbnailCell(props: GridCellParams) { + if (!props.value) { + return null; + } + const url = props.value as string; + return ( + {props.row.product} + ); +} + +const columns: GridColDef[] = [ + { + field: 'thumbnail', + headerName: 'Preview', + width: 120, + renderCell: ThumbnailCell, + }, + { field: 'title', headerName: 'Product', width: 200 }, + { field: 'description', headerName: 'Description', width: 200 }, + { field: 'brand', headerName: 'Brand', width: 150 }, + { + field: 'price', + type: 'number', + headerName: 'Price', + width: 80, + valueFormatter: (value) => `$${value}`, + }, +]; + +const dataSource: GridDataSource = { + getRows: async (params: GridGetRowsParams) => { + const { pageSize, page } = params.paginationModel; + const response = await fetch( + `https://dummyjson.com/products?limit=${pageSize}&skip=${pageSize * page}`, + ); + const data = await response.json(); + return { + rows: data.products, + rowCount: data.total, + }; + }, +}; + +export default function ServerSideDataGridDummyJson() { + return ( +
+ 100} + pinnedColumns={{ left: ['thumbnail'], right: ['price'] }} + disableColumnSorting + disableColumnFilter + /> +
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridDummyJson.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideDataGridDummyJson.tsx.preview new file mode 100644 index 000000000000..71c2c1343d34 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridDummyJson.tsx.preview @@ -0,0 +1,15 @@ + 100} + pinnedColumns={{ left: ['thumbnail'], right: ['price'] }} + disableColumnSorting + disableColumnFilter +/> \ No newline at end of file diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js new file mode 100644 index 000000000000..21331cbc7498 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js @@ -0,0 +1,41 @@ +import * as React from 'react'; +import { DataGridPro } from '@mui/x-data-grid-pro'; +import { useDemoDataSource } from '@mui/x-data-grid-generator'; + +const pageSizeOptions = [5, 10, 50]; + +export default function ServerSideDataGridNoCache() { + const { getRows, columns, initialState } = useDemoDataSource( + {}, + { useCursorPagination: false }, + ); + + const dataSource = React.useMemo(() => { + return { + getRows, + }; + }, [getRows]); + + const initialStateWithPagination = React.useMemo( + () => ({ + ...initialState, + pagination: { + paginationModel: { pageSize: 10, page: 0 }, + }, + }), + [initialState], + ); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx new file mode 100644 index 000000000000..21331cbc7498 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx @@ -0,0 +1,41 @@ +import * as React from 'react'; +import { DataGridPro } from '@mui/x-data-grid-pro'; +import { useDemoDataSource } from '@mui/x-data-grid-generator'; + +const pageSizeOptions = [5, 10, 50]; + +export default function ServerSideDataGridNoCache() { + const { getRows, columns, initialState } = useDemoDataSource( + {}, + { useCursorPagination: false }, + ); + + const dataSource = React.useMemo(() => { + return { + getRows, + }; + }, [getRows]); + + const initialStateWithPagination = React.useMemo( + () => ({ + ...initialState, + pagination: { + paginationModel: { pageSize: 10, page: 0 }, + }, + }), + [initialState], + ); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx.preview new file mode 100644 index 000000000000..2dfc61d6c612 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx.preview @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js new file mode 100644 index 000000000000..4b3177e43484 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js @@ -0,0 +1,62 @@ +import * as React from 'react'; +import { DataGridPro } from '@mui/x-data-grid-pro'; +import { useDemoDataSource } from '@mui/x-data-grid-generator'; +import { useSWRConfig } from 'swr'; + +function ServerSideDataGridWithSWR() { + const { getRows, columns, initialState } = useDemoDataSource( + {}, + { useCursorPagination: false }, + ); + const { cache: swrCache } = useSWRConfig(); + + const cache = React.useMemo( + () => ({ + getKey: (params) => JSON.stringify(params), + set: (key, value) => { + swrCache.set(key, { data: value }); + }, + get: (key) => { + return swrCache.get(key)?.data; + }, + clear: () => { + const keys = swrCache.keys(); + Array.from(keys).forEach((key) => { + swrCache.delete(key); + }); + }, + }), + [swrCache], + ); + + const initialStateWithPagination = React.useMemo( + () => ({ + ...initialState, + pagination: { + paginationModel: { pageSize: 10, page: 0 }, + }, + }), + [initialState], + ); + + const dataSource = React.useMemo(() => { + return { + getRows, + }; + }, [getRows]); + + return ( +
+ +
+ ); +} + +export default ServerSideDataGridWithSWR; diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx new file mode 100644 index 000000000000..ea87364c3e3b --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx @@ -0,0 +1,66 @@ +import * as React from 'react'; +import { + DataGridPro, + GridGetRowsParams, + GridGetRowsResponse, +} from '@mui/x-data-grid-pro'; +import { useDemoDataSource } from '@mui/x-data-grid-generator'; +import { useSWRConfig } from 'swr'; + +function ServerSideDataGridWithSWR() { + const { getRows, columns, initialState } = useDemoDataSource( + {}, + { useCursorPagination: false }, + ); + const { cache: swrCache } = useSWRConfig(); + + const cache = React.useMemo( + () => ({ + getKey: (params: GridGetRowsParams) => JSON.stringify(params), + set: (key: unknown, value: GridGetRowsResponse) => { + swrCache.set(key as string, { data: value }); + }, + get: (key: unknown) => { + return swrCache.get(key as string)?.data; + }, + clear: () => { + const keys = swrCache.keys(); + Array.from(keys).forEach((key) => { + swrCache.delete(key); + }); + }, + }), + [swrCache], + ); + + const initialStateWithPagination = React.useMemo( + () => ({ + ...initialState, + pagination: { + paginationModel: { pageSize: 10, page: 0 }, + }, + }), + [initialState], + ); + + const dataSource = React.useMemo(() => { + return { + getRows, + }; + }, [getRows]); + + return ( +
+ +
+ ); +} + +export default ServerSideDataGridWithSWR; diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.js b/docs/data/data-grid/server-side-data/ServerSideTreeData.js index 1b817e680c4d..ca6b4eaf5da2 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.js @@ -2,27 +2,6 @@ import * as React from 'react'; import { DataGridPro, useGridApiRef, GridToolbar } from '@mui/x-data-grid-pro'; import Button from '@mui/material/Button'; import { useDemoDataSource } from '@mui/x-data-grid-generator'; -import { QueryClient } from '@tanstack/query-core'; - -const cacheInstance = new QueryClient({ - defaultOptions: { - queries: { - staleTime: 1000 * 60 * 5, - }, - }, -}); - -const cache = { - set: (key, value) => { - cacheInstance.setQueryData(key, value); - }, - get: (key) => { - return cacheInstance.getQueryData(key); - }, - clear: () => { - cacheInstance.clear(); - }, -}; const pageSizeOptions = [5, 10, 50]; @@ -56,12 +35,11 @@ export default function ServerSideTreeData() { return (
- +
{ - cacheInstance.setQueryData(key, value); - }, - get: (key) => { - return cacheInstance.getQueryData(key); - }, - clear: () => { - cacheInstance.clear(); - }, -}; const pageSizeOptions = [5, 10, 50]; @@ -62,12 +40,11 @@ export default function ServerSideTreeData() { return (
- +
cache.clear()}>Reset cache +
; ``` -### Server-side filtering, sorting, and pagination +::: + +## Server-side filtering, sorting, and pagination The data source changes how the existing server-side features like `filtering`, `sorting`, and `pagination` work. -#### Without data source +**Without data source** -When there's no data source, the features `filtering`, `sorting`, `pagination` will work on `client` by default. In order for them to work with server-side data, you need to set them to `server` explicitly and provide the [`onFilterModelChange`](https://mui.com/x/react-data-grid/filtering/server-side/), [`onSortModelChange`](https://mui.com/x/react-data-grid/sorting/#server-side-sorting), [`onPaginationModelChange`](https://mui.com/x/react-data-grid/pagination/#server-side-pagination) event handlers to fetch the data from the server based on the updated variables. +When there's no data source, the features `filtering`, `sorting`, `pagination` work on `client` by default. In order for them to work with server-side data, you need to set them to `server` explicitly and provide the [`onFilterModelChange`](https://mui.com/x/react-data-grid/filtering/server-side/), [`onSortModelChange`](https://mui.com/x/react-data-grid/sorting/#server-side-sorting), [`onPaginationModelChange`](https://mui.com/x/react-data-grid/pagination/#server-side-pagination) event handlers to fetch the data from the server based on the updated variables. ```tsx ``` -#### With data source +**With data source** -With the data source, the features `filtering`, `sorting`, `pagination` are automatically be set to `server`. +With the data source, the features `filtering`, `sorting`, `pagination` are automatically set to `server`. When the corresponding models update, the data grid calls the `getRows` method with the updated values of type `GridGetRowsParams` to get updated data. @@ -205,27 +184,50 @@ When the corresponding models update, the data grid calls the `getRows` method w /> ``` -The following demo uses the prop `unstable_dataSource` to support server-side data fetching. +The following demo showcases this behavior. {{"demo": "ServerSideDataGrid.js", "bg": "inline"}} -### Data caching +## Data caching + +The Data source supports caching the data it receives from the server by default. This means that if the user navigates to a page or expands a node that has already been fetched, the grid will not call the `getRows` function again to avoid unnecessary calls to the server. + +The out-of-the-box cache is a simple in-memory cache that stores the data in a plain object. It could be seen in action in the demo below. + +{{"demo": "ServerSideDataGrid.js", "bg": "inline"}} -The data grid supports caching the data it receives from the server to dedupe the requests. This means that if the user navigates to a page or expands a node that has already been fetched, the grid will not call the `getRows` function again to avoid unnecessary calls to the server. +### Custom cache -Use `unstable_dataSourceCache` prop to initialize a cache, it accepts a generic interface of type `GridDataSourceCache`. +To provide a custom cache, use `unstable_dataSourceCache` prop, which could be either written from scratch or based out of another cache library. This prop accepts a generic interface of type `GridDataSourceCache`. ```tsx interface GridDataSourceCache { - set: (key: any[], value: unknown) => void; - get: (key: any[]) => unknown; - clear: () => void; + getKey(key: GridGetRowsParams): unknown; + set(key: unknown, value: unknown): void; + get(key: unknown): unknown; + clear(): void; } ``` -You can use an existing library or write you own custom implementation of the cache. +The following demo uses cache used by a popular library [`swr`](https://github.com/vercel/swr) to cache the server-side data. + +{{"demo": "ServerSideDataGridWithSWR.js", "bg": "inline"}} + +### Disable caching + +To disable the caching on the server-side data, pass the `disableServerSideCache` prop. + +```tsx + +``` + +{{"demo": "ServerSideDataGridNoCache.js", "bg": "inline"}} -### Error handling +## Error handling You could handle the errors with the data source by providing an error handler function using the `unstable_onDataSourceError`. It will be called whenever there's an error in fetching the data. @@ -241,11 +243,11 @@ The first argument of this function is the error object, and the second argument /> ``` -The demo below uses the `useDemoDataSource` utility to simulate the server-side error. Change the value of success rate make the server-side error occur randomly. +The demo below uses the `useDemoDataSource` utility to simulate the server-side error. Change the value of success rate to make the server-side error occur randomly. {{"demo": "ServerSideErrorHandling.js", "bg": "inline"}} -### Updating data 🚧 +## Updating data 🚧 This feature is yet to be implemented, when completed, the method `dataSource.updateRow` will be called with the `GridRowModel` whenever the user edits a row. It will work in a similar way as the `processRowUpdate` prop. 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 ede5a9b68ade..f3da583d623f 100644 --- a/docs/data/data-grid/server-side-data/tree-data.md +++ b/docs/data/data-grid/server-side-data/tree-data.md @@ -6,11 +6,23 @@ title: React Server-side tree data

Tree data lazy-loading with server-side data source.

-To use the server-side tree data, pass the `unstable_dataSource` prop as explained in the [overview section](/x/react-data-grid/server-side-data/), in addition to that passing of some additional props is required for the server-side tree data to work properly. +To dynamically load tree data from the server, including lazy-loading of children, you must create a data source and pass the `unstable_dataSource` prop to the Data Grid, as detailed in the [overview section](/x/react-data-grid/server-side-data/). + +Additionally, you must supply the following required props, listed and explained below. + +```tsx + +``` - `getGroupKey(row: GridRowModel): string` - Used by the grid to group rows by their parent group. Replaces `getTreeDataPath` used in client-side tree-data. + Used to group rows by their parent group. Replaces `getTreeDataPath` used in client-side tree-data. For example, consider this tree structure for tree data. ```js @@ -18,7 +30,7 @@ To use the server-side tree data, pass the `unstable_dataSource` prop as explain - (2) Thomas // groupKey 'Thomas' ``` - When `(2) Thomas` is expanded, the `getRows` function will be called with group keys `['Sarah', 'Thomas']`. + When **(2) Thomas** is expanded, the `getRows` function will be called with group keys `['Sarah', 'Thomas']`. - `hasChildren(row: GridRowModel): boolean` @@ -28,10 +40,10 @@ To use the server-side tree data, pass the `unstable_dataSource` prop as explain Used by the grid to determine the number of children of a row on server -Following is a demo of the server-side tree data with the data source which supports filtering, sorting, and pagination on the server. It also supports the caching using the `unstable_dataSourceCache` prop based on the `QueryClient` exposed by `@tanstack/query-core`. +Following is a demo of the server-side tree data with the data source which supports filtering, sorting, and pagination on the server. It also caches the data by default. {{"demo": "ServerSideTreeData.js", "bg": "inline"}} :::info -The demo above uses a utility `useDemoDataSource` which uses a data generator service to generate data for testing of the application. It exposes a function called `getRows` which could be used directly as `GridDataSource.getRows`. +The demo above uses a utility `useDemoDataSource` which uses a data generator service to generate data for testing of the application. Apart from providing the additional props, it exposes a function called `getRows` which could be used directly as `GridDataSource.getRows`. ::: diff --git a/docs/package.json b/docs/package.json index 2d6e9990650d..53986d9a504f 100644 --- a/docs/package.json +++ b/docs/package.json @@ -94,6 +94,7 @@ "styled-components": "^6.1.8", "stylis": "^4.3.1", "stylis-plugin-rtl": "^2.1.1", + "swr": "^2.2.5", "webpack-bundle-analyzer": "^4.10.1" }, "devDependencies": { diff --git a/docs/pages/x/api/data-grid/grid-api.json b/docs/pages/x/api/data-grid/grid-api.json index 9d528b29d354..c945a34961e4 100644 --- a/docs/pages/x/api/data-grid/grid-api.json +++ b/docs/pages/x/api/data-grid/grid-api.json @@ -68,7 +68,9 @@ }, "getAllRowIds": { "type": { "description": "() => GridRowId[]" }, "required": true }, "getCacheData": { - "type": { "description": "(params: GridGetRowsParams) => unknown" }, + "type": { + "description": "(params: GridGetRowsParams) => GridGetRowsResponse | undefined" + }, "required": true, "isProPlan": true }, diff --git a/docs/translations/api-docs/data-grid/grid-api.json b/docs/translations/api-docs/data-grid/grid-api.json index caa133eca6a9..dfc6a4bf8840 100644 --- a/docs/translations/api-docs/data-grid/grid-api.json +++ b/docs/translations/api-docs/data-grid/grid-api.json @@ -6,7 +6,7 @@ "autosizeColumns": { "description": "Auto-size the columns of the grid based on the cells' content and the space available." }, - "clearCache": { "description": "Clears the cache." }, + "clearCache": { "description": "Clear the cache" }, "deleteFilterItem": { "description": "Deletes a GridFilterItem." }, @@ -29,7 +29,7 @@ }, "getAllGroupDetails": { "description": "Returns the column group lookup." }, "getAllRowIds": { "description": "Gets the list of row ids." }, - "getCacheData": { "description": "Tries to search for some data in cache" }, + "getCacheData": { "description": "Get data from the cache" }, "getCellElement": { "description": "Gets the underlying DOM element for a cell at the given id and field." }, @@ -150,7 +150,7 @@ "setAggregationModel": { "description": "Sets the aggregation model to the one given by model." }, - "setCacheData": { "description": "Tries to search for some data in cache" }, + "setCacheData": { "description": "Set data in the cache" }, "setCellFocus": { "description": "Sets the focus to the cell at the given id and field." }, diff --git a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx index 85953e01dd86..ae2c01307b18 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx @@ -1056,8 +1056,10 @@ DataGridPremiumRaw.propTypes = { unstable_dataSourceCache: PropTypes.shape({ clear: PropTypes.func.isRequired, get: PropTypes.func.isRequired, + getKey: PropTypes.func.isRequired, set: PropTypes.func.isRequired, }), + unstable_onDataSourceError: PropTypes.func, } as any; interface DataGridPremiumComponent { diff --git a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx index aca9abcb9d5d..12f53e145929 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx @@ -956,6 +956,8 @@ DataGridProRaw.propTypes = { unstable_dataSourceCache: PropTypes.shape({ clear: PropTypes.func.isRequired, get: PropTypes.func.isRequired, + getKey: PropTypes.func.isRequired, set: PropTypes.func.isRequired, }), + unstable_onDataSourceError: PropTypes.func, } as any; diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts index 8a681fd43ea7..259675df7773 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts @@ -32,19 +32,19 @@ export interface GridDataSourceApi { */ export interface GridServerSideCacheApi { /** - * Tries to search for some data in cache - * @param {GridGetRowsParams} params The params of type [[GridGetRowsParams]]. - * @returns {GridGetRowsResponse | null} The data of type [[GridGetRowsResponse]] or `null` for cache miss. + * Get data from the cache + * @param {GridGetRowsParams} params The params of type `GridGetRowsParams`. + * @returns {GridGetRowsResponse | undefined} The data of type `GridGetRowsResponse` or `undefined` for cache miss. */ - getCacheData: (params: GridGetRowsParams) => unknown; + getCacheData: (params: GridGetRowsParams) => GridGetRowsResponse | undefined; /** - * Tries to search for some data in cache + * Set data in the cache * @param {GridGetRowsParams} params The params of type [[GridGetRowsParams]]. * @param {GridGetRowsResponse} data The data of type [[GridGetRowsResponse]]. */ setCacheData: (params: GridGetRowsParams, data: GridGetRowsResponse) => void; /** - * Clears the cache. + * Clear the cache */ clearCache: () => void; } diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts index d97fabf853fe..193809469804 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts @@ -174,26 +174,27 @@ export const useGridDataSource = ( if (cachedData.rowCount) { privateApiRef.current.setRowCount(cachedData.rowCount); } - } else { - const isLoading = gridRowsLoadingSelector(privateApiRef); - if (!isLoading) { - privateApiRef.current.setLoading(true); - } + return; + } - try { - const getRowsResponse = await getRows(fetchParams); - privateApiRef.current.setCacheData(fetchParams, getRowsResponse); - if (getRowsResponse.rowCount) { - privateApiRef.current.setRowCount(getRowsResponse.rowCount); - } - privateApiRef.current.caches.groupKeys = []; - privateApiRef.current.setRows(getRowsResponse.rows); - privateApiRef.current.setLoading(false); - } catch (error) { - privateApiRef.current.setRows([]); - privateApiRef.current.setLoading(false); - onError?.(error as Error, fetchParams); + const isLoading = gridRowsLoadingSelector(privateApiRef); + if (!isLoading) { + privateApiRef.current.setLoading(true); + } + + try { + const getRowsResponse = await getRows(fetchParams); + privateApiRef.current.setCacheData(fetchParams, getRowsResponse); + if (getRowsResponse.rowCount) { + privateApiRef.current.setRowCount(getRowsResponse.rowCount); } + privateApiRef.current.caches.groupKeys = []; + privateApiRef.current.setRows(getRowsResponse.rows); + privateApiRef.current.setLoading(false); + } catch (error) { + privateApiRef.current.setRows([]); + privateApiRef.current.setLoading(false); + onError?.(error as Error, fetchParams); } }, [nestedDataManager, privateApiRef, props.unstable_dataSource?.getRows, onError]); @@ -237,39 +238,40 @@ export const useGridDataSource = ( privateApiRef.current.setRowCount(cachedData.rowCount); } privateApiRef.current.setRowChildrenExpansion(id, true); - } else { - const isLoading = rowNode.isLoading; - if (!isLoading) { - privateApiRef.current.setRowLoading(id, true); - } + return; + } - try { - const getRowsResponse = await getRows(fetchParams); - if (!privateApiRef.current.getRowNode(id)) { - // The row has been removed from the grid - nestedDataManager.clearPendingRequest(id); - privateApiRef.current.setRowLoading(id, false); - return; - } - if (nestedDataManager.getRequestStatus(id) === RequestStatus.UNKNOWN) { - // Unregistered or cancelled request - privateApiRef.current.setRowLoading(id, false); - return; - } - nestedDataManager.setRequestSettled(id); - privateApiRef.current.setCacheData(fetchParams, getRowsResponse); - if (getRowsResponse.rowCount) { - privateApiRef.current.setRowCount(getRowsResponse.rowCount); - } - privateApiRef.current.caches.groupKeys = rowNode.path; - privateApiRef.current.updateRows(getRowsResponse.rows, false); - privateApiRef.current.setRowChildrenExpansion(id, true); + const isLoading = rowNode.isLoading; + if (!isLoading) { + privateApiRef.current.setRowLoading(id, true); + } + + try { + const getRowsResponse = await getRows(fetchParams); + if (!privateApiRef.current.getRowNode(id)) { + // The row has been removed from the grid + nestedDataManager.clearPendingRequest(id); privateApiRef.current.setRowLoading(id, false); - } catch (error) { - nestedDataManager.setRequestSettled(id); + return; + } + if (nestedDataManager.getRequestStatus(id) === RequestStatus.UNKNOWN) { + // Unregistered or cancelled request privateApiRef.current.setRowLoading(id, false); - onError?.(error as Error, fetchParams); + return; } + nestedDataManager.setRequestSettled(id); + privateApiRef.current.setCacheData(fetchParams, getRowsResponse); + if (getRowsResponse.rowCount) { + privateApiRef.current.setRowCount(getRowsResponse.rowCount); + } + privateApiRef.current.caches.groupKeys = rowNode.path; + privateApiRef.current.updateRows(getRowsResponse.rows, false); + privateApiRef.current.setRowChildrenExpansion(id, true); + privateApiRef.current.setRowLoading(id, false); + } catch (error) { + nestedDataManager.setRequestSettled(id); + privateApiRef.current.setRowLoading(id, false); + onError?.(error as Error, fetchParams); } }, [nestedDataManager, onError, privateApiRef, props.treeData, props.unstable_dataSource?.getRows], diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridServerSideCache.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridServerSideCache.ts index 8b10caa6eb93..b8ee6f25913c 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridServerSideCache.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridServerSideCache.ts @@ -5,17 +5,42 @@ import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; import { GridGetRowsParams, GridGetRowsResponse, GridDataSourceCache } from '../../../models'; import { GridServerSideCacheApi } from './serverSideInterfaces'; -const noop = () => undefined; +class SimpleServerSideCache { + private cache: Record; -const defaultCache: GridDataSourceCache = { - // TODO: Implement an internal cache - set: noop, - get: noop, - clear: noop, -}; + constructor() { + this.cache = {}; + } + + static getKey(params: GridGetRowsParams) { + return JSON.stringify([ + params.paginationModel, + params.filterModel, + params.sortModel, + params.groupKeys, + ]); + } + + set(key: string, value: GridGetRowsResponse) { + this.cache[key] = value; + } + + get(key: string) { + return this.cache[key]; + } -const getQueryKey = (params: GridGetRowsParams) => { - return [params.paginationModel, params.sortModel, params.filterModel, params.groupKeys]; + clear() { + this.cache = {}; + } +} + +const cacheInstance = new SimpleServerSideCache(); + +const defaultCache: GridDataSourceCache = { + getKey: SimpleServerSideCache.getKey, + set: (key, value) => cacheInstance.set(key as string, value as GridGetRowsResponse), + get: (key) => cacheInstance.get(key as string), + clear: () => cacheInstance.clear(), }; export const useGridServerSideCache = ( @@ -25,17 +50,15 @@ export const useGridServerSideCache = ( 'unstable_dataSource' | 'disableServerSideCache' | 'unstable_dataSourceCache' >, ): void => { - const cacheRef = React.useRef( - props.unstable_dataSourceCache || defaultCache, - ); + const cache = React.useRef(props.unstable_dataSourceCache || defaultCache); const getCacheData = React.useCallback( (params: GridGetRowsParams) => { if (props.disableServerSideCache) { return undefined; } - const queryKey = getQueryKey(params); - return cacheRef.current.get(queryKey); + const key = cache.current.getKey(params); + return cache.current.get(key); }, [props.disableServerSideCache], ); @@ -45,8 +68,8 @@ export const useGridServerSideCache = ( if (props.disableServerSideCache) { return; } - const queryKey = getQueryKey(params); - cacheRef.current.set(queryKey, data); + const key = cache.current.getKey(params); + cache.current.set(key, data); }, [props.disableServerSideCache], ); @@ -55,7 +78,7 @@ export const useGridServerSideCache = ( if (props.disableServerSideCache) { return; } - cacheRef.current.clear(); + cache.current.clear(); }, [props.disableServerSideCache]); const serverSideCacheApi: GridServerSideCacheApi = { @@ -73,7 +96,7 @@ export const useGridServerSideCache = ( return; } if (props.unstable_dataSourceCache) { - cacheRef.current = props.unstable_dataSourceCache; + cache.current = props.unstable_dataSourceCache; } }, [props.unstable_dataSourceCache]); }; diff --git a/packages/x-data-grid/src/models/gridDataSource.ts b/packages/x-data-grid/src/models/gridDataSource.ts index 29c1a5445c7f..eeb96b7bafd9 100644 --- a/packages/x-data-grid/src/models/gridDataSource.ts +++ b/packages/x-data-grid/src/models/gridDataSource.ts @@ -66,7 +66,26 @@ export interface GridDataSource { } export interface GridDataSourceCache { - set: (key: any[], value: unknown) => void; - get: (key: any[]) => unknown; + /** + * Provides a key for the cache to be used in `set` and `get` + * @param {GridGetRowsParams} params The parameters required to fetch the rows + * @returns {unknown} The key for the cache to be used in `set` and `get` + */ + getKey: (params: GridGetRowsParams) => unknown; + /** + * Sets the cache entry for the given key + * @param {unknown} key The key for the cache + * @param {GridGetRowsResponse} value The value to be stored in the cache + */ + set: (key: unknown, value: GridGetRowsResponse) => void; + /** + * Gets the cache entry for the given key + * @param {unknown} key The key for the cache + * @returns {GridGetRowsResponse} The value stored in the cache + */ + get: (key: unknown) => GridGetRowsResponse; + /** + * Clears the cache + */ clear: () => void; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2825ca705569..571b58bcc4a9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -617,6 +617,9 @@ importers: stylis-plugin-rtl: specifier: ^2.1.1 version: 2.1.1(stylis@4.3.1) + swr: + specifier: ^2.2.5 + version: 2.2.5(react@18.2.0) webpack-bundle-analyzer: specifier: ^4.10.1 version: 4.10.1 @@ -16618,6 +16621,16 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + /swr@2.2.5(react@18.2.0): + resolution: {integrity: sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 + dependencies: + client-only: 0.0.1 + react: 18.2.0 + use-sync-external-store: 1.2.2(react@18.2.0) + dev: false + /symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} dev: true @@ -17377,6 +17390,14 @@ packages: resolution: {integrity: sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==} dev: true + /use-sync-external-store@1.2.2(react@18.2.0): + resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + dev: false + /use@3.1.1: resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==} engines: {node: '>=0.10.0'} From 7f351084c0446005d9b2f7d41ca4ab307d00ef2e Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 13 May 2024 08:52:13 +0500 Subject: [PATCH 18/90] Fix client-side tree-data regression --- .../useGridServerSideTreeDataPreProcessors.tsx | 5 ++++- .../hooks/features/treeData/useGridTreeDataPreProcessors.tsx | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx index ebcbe5e39096..fe8265e63c55 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx @@ -97,6 +97,9 @@ export const useGridServerSideTreeDataPreProcessors = ( const updateGroupingColumn = React.useCallback>( (columnsState) => { + if (!props.unstable_dataSource) { + return columnsState; + } const groupingColDefField = GRID_TREE_DATA_GROUPING_COL_DEF_FORCED_PROPERTIES.field; const shouldHaveGroupingColumn = props.treeData; @@ -126,7 +129,7 @@ export const useGridServerSideTreeDataPreProcessors = ( return columnsState; }, - [props.treeData, getGroupingColDef], + [props.treeData, props.unstable_dataSource, getGroupingColDef], ); const createRowTreeForTreeData = React.useCallback>( 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 ec9227a45156..74c0fcfcda38 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 @@ -94,6 +94,9 @@ export const useGridTreeDataPreProcessors = ( const updateGroupingColumn = React.useCallback>( (columnsState) => { + if (props.unstable_dataSource) { + return columnsState; + } const groupingColDefField = GRID_TREE_DATA_GROUPING_COL_DEF_FORCED_PROPERTIES.field; const shouldHaveGroupingColumn = props.treeData; @@ -123,7 +126,7 @@ export const useGridTreeDataPreProcessors = ( return columnsState; }, - [props.treeData, getGroupingColDef], + [props.treeData, props.unstable_dataSource, getGroupingColDef], ); const createRowTreeForTreeData = React.useCallback>( From 0bad3b463b0ec0abaec33578b0c0c99fd12852d7 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 13 May 2024 12:24:18 +0500 Subject: [PATCH 19/90] Add tree data error handling, add state for data source, other minor improvements --- .../ServerSideErrorHandling.js | 56 ++-- .../ServerSideErrorHandling.tsx | 60 ++--- .../ServerSideTreeDataErrorHandling.js | 128 ++++++++++ .../ServerSideTreeDataErrorHandling.tsx | 128 ++++++++++ docs/data/data-grid/server-side-data/index.md | 6 +- .../data-grid/server-side-data/tree-data.md | 10 + docs/pages/x/api/data-grid/grid-api.json | 20 +- .../api-docs/data-grid/grid-api.json | 8 +- .../src/hooks/serverUtils.ts | 7 - .../src/hooks/useDemoDataSource.ts | 25 +- .../src/DataGridPremium/DataGridPremium.tsx | 2 +- .../useDataGridPremiumComponent.tsx | 2 + .../src/models/gridApiPremium.ts | 2 + .../src/DataGridPro/DataGridPro.tsx | 2 +- .../DataGridPro/useDataGridProComponent.tsx | 6 +- .../GridServerSideTreeDataGroupingCell.tsx | 27 +- .../gridServerSideDataSelector.ts | 13 + .../features/serverSideData/interfaces.ts | 10 + .../serverSideData/serverSideInterfaces.ts | 29 ++- .../serverSideData/useGridDataSource.ts | 239 +++++++----------- .../hooks/features/serverSideData/utils.ts | 120 +++++++++ ...useGridServerSideTreeDataPreProcessors.tsx | 2 +- .../x-data-grid-pro/src/internals/index.ts | 5 +- .../src/models/dataGridProProps.ts | 2 +- .../x-data-grid-pro/src/models/gridApiPro.ts | 4 +- .../src/models/gridStatePro.ts | 2 + .../src/typeOverloads/modules.ts | 2 + .../src/utils/tree/insertDataRowInTree.ts | 1 - .../x-data-grid/src/models/gridApiCaches.ts | 1 - packages/x-data-grid/src/models/gridRows.ts | 4 - scripts/x-data-grid-premium.exports.json | 1 + scripts/x-data-grid-pro.exports.json | 1 + 32 files changed, 649 insertions(+), 276 deletions(-) create mode 100644 docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js create mode 100644 docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx create mode 100644 packages/x-data-grid-pro/src/hooks/features/serverSideData/interfaces.ts create mode 100644 packages/x-data-grid-pro/src/hooks/features/serverSideData/utils.ts diff --git a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js index 84d8e2b138cc..811a745f3091 100644 --- a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js +++ b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js @@ -1,13 +1,13 @@ import * as React from 'react'; import { DataGridPro, useGridApiRef, GridToolbar } from '@mui/x-data-grid-pro'; import Button from '@mui/material/Button'; -import Slider from '@mui/material/Slider'; -import Box from '@mui/material/Box'; -import Typography from '@mui/material/Typography'; +import Checkbox from '@mui/material/Checkbox'; +import FormControlLabel from '@mui/material/FormControlLabel'; import { alpha, styled, darken, lighten } from '@mui/material/styles'; import { useDemoDataSource } from '@mui/x-data-grid-generator'; const pageSizeOptions = [5, 10, 50]; +const serverOptions = { useCursorPagination: false }; function getBorderColor(theme) { if (theme.palette.mode === 'light') { @@ -32,21 +32,22 @@ const StyledDiv = styled('div')(({ theme: t }) => ({ })); function ErrorOverlay({ error }) { - if (!error?.message) { + if (!error) { return null; } - return {error.message}; + return {error}; } export default function ServerSideErrorHandling() { const apiRef = useGridApiRef(); const [error, setError] = React.useState(); - const [serverOptions, setServerOptions] = React.useState({ - useCursorPagination: false, - successRate: 0.5, - }); + const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); - const { getRows, ...props } = useDemoDataSource({}, serverOptions); + const { getRows, ...props } = useDemoDataSource( + {}, + serverOptions, + shouldRequestsFail, + ); const dataSource = React.useMemo(() => { return { @@ -72,37 +73,28 @@ export default function ServerSideErrorHandling() {
- - Success Rate - - setServerOptions((prev) => ({ - ...prev, - successRate: Number(value) / 100, - })) - } - /> - + setShouldRequestsFail(e.target.checked)} + /> + } + label="Make the requests fail" + />
setError(e)} + unstable_onServerSideError={(e) => setError(e.message)} + disableServerSideCache apiRef={apiRef} pagination pageSizeOptions={pageSizeOptions} diff --git a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx index a91ab785d005..1217f9ec400f 100644 --- a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx @@ -6,13 +6,13 @@ import { GridToolbar, } from '@mui/x-data-grid-pro'; import Button from '@mui/material/Button'; -import Slider from '@mui/material/Slider'; -import Box from '@mui/material/Box'; -import Typography from '@mui/material/Typography'; +import Checkbox from '@mui/material/Checkbox'; +import FormControlLabel from '@mui/material/FormControlLabel'; import { alpha, styled, darken, lighten, Theme } from '@mui/material/styles'; import { useDemoDataSource } from '@mui/x-data-grid-generator'; const pageSizeOptions = [5, 10, 50]; +const serverOptions = { useCursorPagination: false }; function getBorderColor(theme: Theme) { if (theme.palette.mode === 'light') { @@ -36,22 +36,23 @@ const StyledDiv = styled('div')(({ theme: t }) => ({ backgroundColor: t.palette.background.default, })); -function ErrorOverlay({ error }: { error: Error }) { - if (!error?.message) { +function ErrorOverlay({ error }: { error: string }) { + if (!error) { return null; } - return {error.message}; + return {error}; } export default function ServerSideErrorHandling() { const apiRef = useGridApiRef(); - const [error, setError] = React.useState(); - const [serverOptions, setServerOptions] = React.useState({ - useCursorPagination: false, - successRate: 0.5, - }); + const [error, setError] = React.useState(); + const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); - const { getRows, ...props } = useDemoDataSource({}, serverOptions); + const { getRows, ...props } = useDemoDataSource( + {}, + serverOptions, + shouldRequestsFail, + ); const dataSource = React.useMemo(() => { return { @@ -77,37 +78,28 @@ export default function ServerSideErrorHandling() {
- - Success Rate - - setServerOptions((prev) => ({ - ...prev, - successRate: Number(value) / 100, - })) - } - /> - + setShouldRequestsFail(e.target.checked)} + /> + } + label="Make the requests fail" + />
setError(e)} + unstable_onServerSideError={(e) => setError(e.message)} + disableServerSideCache apiRef={apiRef} pagination pageSizeOptions={pageSizeOptions} diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js new file mode 100644 index 000000000000..70675287b5e1 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js @@ -0,0 +1,128 @@ +import * as React from 'react'; +import { DataGridPro, useGridApiRef } from '@mui/x-data-grid-pro'; +import Snackbar from '@mui/material/Snackbar'; +import Button from '@mui/material/Button'; +import Checkbox from '@mui/material/Checkbox'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import { alpha, styled, darken, lighten } from '@mui/material/styles'; +import { useDemoDataSource } from '@mui/x-data-grid-generator'; + +const pageSizeOptions = [5, 10, 50]; +const serverOptions = { useCursorPagination: false }; + +function getBorderColor(theme) { + if (theme.palette.mode === 'light') { + return lighten(alpha(theme.palette.divider, 1), 0.88); + } + return darken(alpha(theme.palette.divider, 1), 0.68); +} + +const StyledDiv = styled('div')(({ theme: t }) => ({ + position: 'absolute', + zIndex: 10, + fontSize: '0.875em', + top: 0, + height: '100%', + width: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + borderRadius: '4px', + border: `1px solid ${getBorderColor(t)}`, + backgroundColor: t.palette.background.default, +})); + +function ErrorOverlay({ error }) { + if (!error) { + return null; + } + return {error}; +} + +export default function ServerSideTreeDataErrorHandling() { + const apiRef = useGridApiRef(); + const [rootError, setRootError] = React.useState(); + const [childrenError, setChildrenError] = React.useState(); + const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); + + const { getRows, ...props } = useDemoDataSource( + { + dataSet: 'Employee', + rowLength: 1000, + treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, + }, + serverOptions, + shouldRequestsFail, + ); + + const dataSource = React.useMemo(() => { + return { + getRows, + }; + }, [getRows]); + + const initialState = React.useMemo( + () => ({ + ...props.initialState, + pagination: { + paginationModel: { + pageSize: 5, + }, + rowCount: 0, + }, + }), + [props.initialState], + ); + + return ( +
+
+ + setShouldRequestsFail(e.target.checked)} + /> + } + label="Make the requests fail" + /> +
+
+ { + if (!params.groupKeys || params.groupKeys.length === 0) { + setRootError(e.message); + } else { + setChildrenError( + `${e.message} (Requested level: ${params.groupKeys.join(', ')})`, + ); + } + }} + disableServerSideCache + apiRef={apiRef} + pagination + pageSizeOptions={pageSizeOptions} + initialState={initialState} + /> + {rootError && } + setChildrenError('')} + message={childrenError} + /> +
+
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx new file mode 100644 index 000000000000..b8c6b9905f40 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx @@ -0,0 +1,128 @@ +import * as React from 'react'; +import { DataGridPro, useGridApiRef, GridInitialState } from '@mui/x-data-grid-pro'; +import Snackbar from '@mui/material/Snackbar'; +import Button from '@mui/material/Button'; +import Checkbox from '@mui/material/Checkbox'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import { alpha, styled, darken, lighten, Theme } from '@mui/material/styles'; +import { useDemoDataSource } from '@mui/x-data-grid-generator'; + +const pageSizeOptions = [5, 10, 50]; +const serverOptions = { useCursorPagination: false }; + +function getBorderColor(theme: Theme) { + if (theme.palette.mode === 'light') { + return lighten(alpha(theme.palette.divider, 1), 0.88); + } + return darken(alpha(theme.palette.divider, 1), 0.68); +} + +const StyledDiv = styled('div')(({ theme: t }) => ({ + position: 'absolute', + zIndex: 10, + fontSize: '0.875em', + top: 0, + height: '100%', + width: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + borderRadius: '4px', + border: `1px solid ${getBorderColor(t)}`, + backgroundColor: t.palette.background.default, +})); + +function ErrorOverlay({ error }: { error: string }) { + if (!error) { + return null; + } + return {error}; +} + +export default function ServerSideTreeDataErrorHandling() { + const apiRef = useGridApiRef(); + const [rootError, setRootError] = React.useState(); + const [childrenError, setChildrenError] = React.useState(); + const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); + + const { getRows, ...props } = useDemoDataSource( + { + dataSet: 'Employee', + rowLength: 1000, + treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, + }, + serverOptions, + shouldRequestsFail, + ); + + const dataSource = React.useMemo(() => { + return { + getRows, + }; + }, [getRows]); + + const initialState: GridInitialState = React.useMemo( + () => ({ + ...props.initialState, + pagination: { + paginationModel: { + pageSize: 5, + }, + rowCount: 0, + }, + }), + [props.initialState], + ); + + return ( +
+
+ + setShouldRequestsFail(e.target.checked)} + /> + } + label="Make the requests fail" + /> +
+
+ { + if (!params.groupKeys || params.groupKeys.length === 0) { + setRootError(e.message); + } else { + setChildrenError( + `${e.message} (Requested level: ${params.groupKeys.join(', ')})`, + ); + } + }} + disableServerSideCache + apiRef={apiRef} + pagination + pageSizeOptions={pageSizeOptions} + initialState={initialState} + /> + {rootError && } + setChildrenError('')} + message={childrenError} + /> +
+
+ ); +} diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index 246393afd134..f692ee2e557f 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -198,10 +198,10 @@ The out-of-the-box cache is a simple in-memory cache that stores the data in a p ### Custom cache -To provide a custom cache, use `unstable_dataSourceCache` prop, which could be either written from scratch or based out of another cache library. This prop accepts a generic interface of type `GridDataSourceCache`. +To provide a custom cache, use `unstable_serverSideCache` prop, which could be either written from scratch or based out of another cache library. This prop accepts a generic interface of type `GridServerSideCache`. ```tsx -interface GridDataSourceCache { +interface GridServerSideCache { getKey(key: GridGetRowsParams): unknown; set(key: unknown, value: unknown): void; get(key: unknown): unknown; @@ -229,7 +229,7 @@ To disable the caching on the server-side data, pass the `disableServerSideCache ## Error handling -You could handle the errors with the data source by providing an error handler function using the `unstable_onDataSourceError`. It will be called whenever there's an error in fetching the data. +You could handle the errors with the data source by providing an error handler function using the `unstable_onServerSideError`. It will be called whenever there's an error in fetching the data. The first argument of this function is the error object, and the second argument is the fetch parameters of type `GridGetRowsParams`. 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 f3da583d623f..951fc27ef379 100644 --- a/docs/data/data-grid/server-side-data/tree-data.md +++ b/docs/data/data-grid/server-side-data/tree-data.md @@ -40,6 +40,8 @@ Additionally, you must supply the following required props, listed and explained Used by the grid to determine the number of children of a row on server +## Demo + Following is a demo of the server-side tree data with the data source which supports filtering, sorting, and pagination on the server. It also caches the data by default. {{"demo": "ServerSideTreeData.js", "bg": "inline"}} @@ -47,3 +49,11 @@ Following is a demo of the server-side tree data with the data source which supp :::info The demo above uses a utility `useDemoDataSource` which uses a data generator service to generate data for testing of the application. Apart from providing the additional props, it exposes a function called `getRows` which could be used directly as `GridDataSource.getRows`. ::: + +## Error handling + +For each row group expansion, the data source is called to fetch the children. If an error occurs during the fetch, the grid will display an error message in the row group cell. `unstable_onServerSideError` is also triggered with the error and the fetch params. + +The demo below shows a toast apart from the default error message in the grouping cell. Cache has been disabled in this demo for simplicity. + +{{"demo": "ServerSideTreeDataErrorHandling.js", "bg": "inline"}} diff --git a/docs/pages/x/api/data-grid/grid-api.json b/docs/pages/x/api/data-grid/grid-api.json index c945a34961e4..94ef9d20ded6 100644 --- a/docs/pages/x/api/data-grid/grid-api.json +++ b/docs/pages/x/api/data-grid/grid-api.json @@ -50,11 +50,6 @@ "type": { "description": "(params?: GridExportStateParams) => InitialState" }, "required": true }, - "fetchRowChildren": { - "type": { "description": "(id: GridRowId) => void" }, - "required": true, - "isProPlan": true - }, "fetchTopLevelRows": { "type": { "description": "() => void" }, "required": true, @@ -328,6 +323,16 @@ "required": true, "isPremiumPlan": true }, + "setChildrenFetchError": { + "type": { "description": "(parentId: GridRowId, error: Error | null) => void" }, + "required": true, + "isProPlan": true + }, + "setChildrenLoading": { + "type": { "description": "(parentId: GridRowId, loading: boolean) => void" }, + "required": true, + "isProPlan": true + }, "setColumnHeaderFilterFocus": { "type": { "description": "(field: string, event?: MuiBaseEvent) => void" }, "required": true @@ -421,11 +426,6 @@ "required": true, "isProPlan": true }, - "setRowLoading": { - "type": { "description": "(id: GridRowId, loading: boolean) => void" }, - "required": true, - "isProPlan": true - }, "setRows": { "type": { "description": "(rows: GridRowModel[]) => void" }, "required": true }, "setRowSelectionModel": { "type": { "description": "(rowIds: GridRowId[]) => void" }, diff --git a/docs/translations/api-docs/data-grid/grid-api.json b/docs/translations/api-docs/data-grid/grid-api.json index dfc6a4bf8840..3c76cef2f7f8 100644 --- a/docs/translations/api-docs/data-grid/grid-api.json +++ b/docs/translations/api-docs/data-grid/grid-api.json @@ -19,8 +19,7 @@ "exportState": { "description": "Generates a serializable object containing the exportable parts of the DataGrid state.
These values can then be passed to the initialState prop or injected using the restoreState method." }, - "fetchRowChildren": { "description": "Initiates the fetch of the children of a row." }, - "fetchTopLevelRows": { "description": "Fetches the top level rows." }, + "fetchTopLevelRows": { "description": "Fetch/refetch the top level rows." }, "forceUpdate": { "description": "Forces the grid to rerender. It's often used after a state update." }, @@ -157,6 +156,10 @@ "setCellSelectionModel": { "description": "Updates the selected cells to be those passed to the newModel argument.
Any cell already selected will be unselected." }, + "setChildrenFetchError": { + "description": "Set error occured while fetching the children of a row." + }, + "setChildrenLoading": { "description": "Set the loading state of a parent row." }, "setColumnHeaderFilterFocus": { "description": "Sets the focus to the column header filter at the given field." }, @@ -208,7 +211,6 @@ "setRowIndex": { "description": "Moves a row from its original position to the position given by targetIndex." }, - "setRowLoading": { "description": "Set the loading state of a row." }, "setRows": { "description": "Sets a new set of rows." }, "setRowSelectionModel": { "description": "Updates the selected rows to be those passed to the rowIds argument.
Any row already selected will be unselected." diff --git a/packages/x-data-grid-generator/src/hooks/serverUtils.ts b/packages/x-data-grid-generator/src/hooks/serverUtils.ts index 5ecbca3fc0a3..34852a79e5c1 100644 --- a/packages/x-data-grid-generator/src/hooks/serverUtils.ts +++ b/packages/x-data-grid-generator/src/hooks/serverUtils.ts @@ -31,13 +31,6 @@ export interface DefaultServerOptions { minDelay: number; maxDelay: number; useCursorPagination?: boolean; - /* - * The success rate of the server response. It is a number between 0 and 1. - * 0 means that the server will always return an error. - * 1 means that the server will always return a success. - * `@default 1` - */ - successRate?: number; } export type ServerOptions = Partial; diff --git a/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts b/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts index 3373faa75feb..4b491b5dcfac 100644 --- a/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts +++ b/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts @@ -63,9 +63,18 @@ const defaultColDef = getGridDefaultColumnTypes(); export const useDemoDataSource = ( dataSetOptions?: Partial, serverOptions?: ServerOptions, + shouldRequestsFail?: boolean, ): UseDemoDataSourceResponse => { const [data, setData] = React.useState(); const [index, setIndex] = React.useState(0); + const shouldRequestsFailRef = React.useRef(shouldRequestsFail ?? false); + + React.useEffect(() => { + if (shouldRequestsFail !== undefined) { + shouldRequestsFailRef.current = shouldRequestsFail; + } + }, [shouldRequestsFail]); + const options = { ...DEFAULT_DATASET_OPTIONS, ...dataSetOptions }; const columns = React.useMemo(() => { @@ -183,16 +192,12 @@ export const useDemoDataSource = ( let getRowsResponse: GridGetRowsResponse; const serverOptionsWithDefault = { ...DEFAULT_SERVER_OPTIONS, ...serverOptions }; - const { successRate = 1 } = serverOptionsWithDefault; - if (successRate !== 1) { - const rate = random(0.01, 0.99); - if (rate > successRate) { - const { minDelay, maxDelay } = serverOptionsWithDefault; - const delay = randomInt(minDelay, maxDelay); - return new Promise((_, reject) => { - setTimeout(() => reject(new Error('Could not fetch the data')), delay); - }); - } + if (shouldRequestsFailRef.current) { + const { minDelay, maxDelay } = serverOptionsWithDefault; + const delay = randomInt(minDelay, maxDelay); + return new Promise((_, reject) => { + setTimeout(() => reject(new Error('Could not fetch the data')), delay); + }); } if (isTreeData /* || TODO: `isRowGrouping` */) { diff --git a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx index ae2c01307b18..476538f54544 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx @@ -1059,7 +1059,7 @@ DataGridPremiumRaw.propTypes = { getKey: PropTypes.func.isRequired, set: PropTypes.func.isRequired, }), - unstable_onDataSourceError: PropTypes.func, + unstable_onServerSideError: PropTypes.func, } as any; interface DataGridPremiumComponent { diff --git a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx index 7e6100bc8683..0e46c65113e2 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx @@ -67,6 +67,7 @@ import { useGridVirtualization, useGridServerSideTreeDataPreProcessors, useGridDataSource, + dataSourceStateInitializer, useGridServerSideCache, } from '@mui/x-data-grid-pro/internals'; import { GridApiPremium, GridPrivateApiPremium } from '../models/gridApiPremium'; @@ -139,6 +140,7 @@ export const useDataGridPremiumComponent = ( useGridInitializeState(columnMenuStateInitializer, apiRef, props); useGridInitializeState(columnGroupsStateInitializer, apiRef, props); useGridInitializeState(virtualizationStateInitializer, apiRef, props); + useGridInitializeState(dataSourceStateInitializer, apiRef, props); useGridRowGrouping(apiRef, props); useGridHeaderFiltering(apiRef, props); diff --git a/packages/x-data-grid-premium/src/models/gridApiPremium.ts b/packages/x-data-grid-premium/src/models/gridApiPremium.ts index 3a15639a0e68..4300169af23b 100644 --- a/packages/x-data-grid-premium/src/models/gridApiPremium.ts +++ b/packages/x-data-grid-premium/src/models/gridApiPremium.ts @@ -10,6 +10,7 @@ import { GridRowProApi, GridDataSourceApi, GridServerSideCacheApi, + GridDataSourcePrivateApi, } from '@mui/x-data-grid-pro'; import { GridInitialStatePremium, GridStatePremium } from './gridStatePremium'; import type { GridRowGroupingApi, GridExcelExportApi, GridAggregationApi } from '../hooks'; @@ -39,4 +40,5 @@ export interface GridApiPremium export interface GridPrivateApiPremium extends GridApiPremium, GridPrivateOnlyApiCommon, + GridDataSourcePrivateApi, GridDetailPanelPrivateApi {} diff --git a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx index 12f53e145929..f3412ece79c0 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx @@ -959,5 +959,5 @@ DataGridProRaw.propTypes = { getKey: PropTypes.func.isRequired, set: PropTypes.func.isRequired, }), - unstable_onDataSourceError: PropTypes.func, + unstable_onServerSideError: PropTypes.func, } as any; diff --git a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx index 134f979457ed..f29f4e0c4212 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx @@ -78,7 +78,10 @@ import { rowPinningStateInitializer, } from '../hooks/features/rowPinning/useGridRowPinning'; import { useGridRowPinningPreProcessors } from '../hooks/features/rowPinning/useGridRowPinningPreProcessors'; -import { useGridDataSource } from '../hooks/features/serverSideData/useGridDataSource'; +import { + useGridDataSource, + dataSourceStateInitializer, +} from '../hooks/features/serverSideData/useGridDataSource'; import { useGridServerSideCache } from '../hooks/features/serverSideData/useGridServerSideCache'; export const useDataGridProComponent = ( @@ -126,6 +129,7 @@ export const useDataGridProComponent = ( useGridInitializeState(columnMenuStateInitializer, apiRef, props); useGridInitializeState(columnGroupsStateInitializer, apiRef, props); useGridInitializeState(virtualizationStateInitializer, apiRef, props); + useGridInitializeState(dataSourceStateInitializer, apiRef, props); useGridHeaderFiltering(apiRef, props); useGridTreeData(apiRef); diff --git a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx b/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx index f47b79d0ef07..fa5b9c804667 100644 --- a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx +++ b/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx @@ -3,16 +3,19 @@ import PropTypes from 'prop-types'; import { unstable_composeClasses as composeClasses } from '@mui/utils'; import { styled } from '@mui/system'; import Box from '@mui/material/Box'; +import Badge from '@mui/material/Badge'; import { getDataGridUtilityClass, GridRenderCellParams, GridServerSideGroupNode, + useGridSelector, } from '@mui/x-data-grid'; import CircularProgress from '@mui/material/CircularProgress'; import { useGridRootProps } from '../hooks/utils/useGridRootProps'; import { useGridPrivateApiContext } from '../hooks/utils/useGridPrivateApiContext'; import { DataGridProProcessedProps } from '../models/dataGridProProps'; import { GridPrivateApiPro } from '../models/gridApiPro'; +import { GridStatePro } from '../models/gridStatePro'; type OwnerState = { classes: DataGridProProcessedProps['classes'] }; @@ -50,16 +53,26 @@ const LoadingContainer = styled('div')({ }); function GridTreeDataGroupingCellIcon(props: GridTreeDataGroupingCellIconProps) { - const { rowNode, id, field, descendantCount } = props; const apiRef = useGridPrivateApiContext() as React.MutableRefObject; const rootProps = useGridRootProps(); + const { rowNode, id, field, descendantCount } = props; + + const loadingSelector = React.useCallback( + (state: GridStatePro) => state.serverSideData.loading[id] ?? false, + [id], + ); + const errorSelector = React.useCallback( + (state: GridStatePro) => state.serverSideData.errors[id] ?? null, + [id], + ); + const isDataLoading = useGridSelector(apiRef, loadingSelector); + const error = useGridSelector(apiRef, errorSelector); const isServerSideNode = rowNode.isServerSide; - const isDataLoading = rowNode.isLoading; const handleClick = (event: React.MouseEvent) => { - if (isServerSideNode && !rowNode.childrenExpanded) { - // always fetch/get from cache the children when the node is collapsed + if (!rowNode.childrenExpanded) { + // always fetch/get from cache the children when the node is expanded apiRef.current.enqueueChildrenFetch(id); } else { apiRef.current.setRowChildrenExpansion(id, !rowNode.childrenExpanded); @@ -91,7 +104,11 @@ function GridTreeDataGroupingCellIcon(props: GridTreeDataGroupingCellIconProps) } {...rootProps?.slotProps?.baseIconButton} > - + + + + + ) : null; } diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/gridServerSideDataSelector.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/gridServerSideDataSelector.ts index 4d1075366f9e..425273bc0b0b 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/gridServerSideDataSelector.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/gridServerSideDataSelector.ts @@ -5,6 +5,7 @@ import { gridPaginationModelSelector, } from '@mui/x-data-grid'; import { createSelector } from '@mui/x-data-grid/internals'; +import { GridStatePro } from '../../../models/gridStatePro'; const computeStartEnd = (paginationModel: GridPaginationModel) => { const start = paginationModel.page * paginationModel.pageSize; @@ -28,3 +29,15 @@ export const gridGetRowsParamsSelector = createSelector( }; }, ); + +export const gridServerSideDataStateSelector = (state: GridStatePro) => state.serverSideData; + +export const gridServerSideDataLoadingSelector = createSelector( + gridServerSideDataStateSelector, + (serverSideData) => serverSideData.loading, +); + +export const gridServerSideDataErrorsSelector = createSelector( + gridServerSideDataStateSelector, + (serverSideData) => serverSideData.errors, +); diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/interfaces.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/interfaces.ts new file mode 100644 index 000000000000..921debb8fcd5 --- /dev/null +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/interfaces.ts @@ -0,0 +1,10 @@ +import { GridRowId } from '@mui/x-data-grid'; + +export interface GridServerSideDataInternalCache { + groupKeys: any[]; +} + +export interface GridServerSideDataState { + loading: Record; + errors: Record; +} diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts index 259675df7773..5209a2c9c2c3 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts @@ -6,18 +6,19 @@ import { GridGetRowsParams, GridGetRowsResponse } from '../../../models'; */ export interface GridDataSourceApi { /** - * Initiates the fetch of the children of a row. - * @param {string} id The id of the rowNode belonging to the group to be fetched. + * Set the loading state of a parent row. + * @param {string} parentId The id of the parent node. + * @param {boolean} loading The loading state to set. */ - fetchRowChildren: (id: GridRowId) => void; + setChildrenLoading: (parentId: GridRowId, loading: boolean) => void; /** - * Set the loading state of a row. - * @param {string} id The id of the rowNode. - * @param {boolean} loading The loading state to set. + * Set error occured while fetching the children of a row. + * @param {string} parentId The id of the parent node. + * @param {Error} error The error of type `Error` or `null`. */ - setRowLoading: (id: GridRowId, loading: boolean) => void; + setChildrenFetchError: (parentId: GridRowId, error: Error | null) => void; /** - * Fetches the top level rows. + * Fetch/refetch the top level rows. */ fetchTopLevelRows: () => void; /** @@ -27,6 +28,18 @@ export interface GridDataSourceApi { enqueueChildrenFetch: (id: GridRowId) => void; } +export interface GridDataSourcePrivateApi { + /** + * Initiates the fetch of the children of a row. + * @param {string} id The id of the rowNode belonging to the group to be fetched. + */ + fetchRowChildren: (id: GridRowId) => void; + /** + * Resets the server side state. + */ + resetServerSideState: () => void; +} + /** * The server side cache API interface that is available in the grid [[apiRef]]. */ diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts index 193809469804..2890992cc7e4 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts @@ -7,149 +7,52 @@ import { GridRowId, useGridSelector, } from '@mui/x-data-grid'; -import { gridRowGroupsToFetchSelector } from '@mui/x-data-grid/internals'; +import { gridRowGroupsToFetchSelector, GridStateInitializer } from '@mui/x-data-grid/internals'; import { GridGetRowsResponse } from '../../../models'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; -import { gridGetRowsParamsSelector } from './gridServerSideDataSelector'; -import { GridDataSourceApi } from './serverSideInterfaces'; - -const runIfServerMode = (modeProp: 'server' | 'client', fn: Function) => () => { - if (modeProp === 'server') { - fn(); - } +import { + gridGetRowsParamsSelector, + gridServerSideDataLoadingSelector, + gridServerSideDataErrorsSelector, +} from './gridServerSideDataSelector'; +import { GridDataSourceApi, GridDataSourcePrivateApi } from './serverSideInterfaces'; +import { runIfServerMode, NestedDataManager, RequestStatus } from './utils'; + +const INITIAL_STATE = { + loading: {}, + errors: {}, }; -// Make these configurable using dedicated props? -const MAX_CONCURRENT_REQUESTS = Infinity; -const QUEUE_PROCESS_INTERVAL_MS = 300; - -enum RequestStatus { - INQUEUE, - PENDING, - SETTLED, - UNKNOWN, -} - -/** - * 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` - * Uses `GridRowId` to uniquely identify a request - */ -class NestedDataManager { - private pendingRequests: Set = new Set(); - - private queuedRequests: Set = new Set(); - - private settledRequests: Set = new Set(); - - private api: GridPrivateApiPro; - - private maxConcurrentRequests: number; - - private queueProcessInterval: number; - - private timer?: string | number | NodeJS.Timeout; - - constructor( - privateApiRef: React.MutableRefObject, - maxConcurrentRequests = MAX_CONCURRENT_REQUESTS, - queueProcessInterval = QUEUE_PROCESS_INTERVAL_MS, - ) { - this.api = privateApiRef.current; - this.maxConcurrentRequests = maxConcurrentRequests; - this.queueProcessInterval = queueProcessInterval; - } - - private processQueue = async () => { - if (this.queuedRequests.size === 0) { - clearInterval(this.timer); - return; - } - if (this.pendingRequests.size >= this.maxConcurrentRequests) { - return; - } - const fetchQueue = Array.from(this.queuedRequests); - for (let i = 0; i < this.maxConcurrentRequests; i += 1) { - const nextId = fetchQueue[i]; - if (!nextId) { - clearInterval(this.timer); - return; - } - this.queuedRequests.delete(nextId); - this.api.fetchRowChildren(nextId); - this.pendingRequests.add(nextId); - } - }; - - public enqueue = async (ids: GridRowId[]) => { - ids.forEach((id) => { - if (this.pendingRequests.size < this.maxConcurrentRequests) { - this.pendingRequests.add(id); - this.api.fetchRowChildren(id); - } else { - this.queuedRequests.add(id); - } - - if (this.queuedRequests.size > 0) { - if (this.timer) { - clearInterval(this.timer); - } - this.timer = setInterval(this.processQueue, this.queueProcessInterval); - } - }); - }; - - public setRequestSettled = (id: GridRowId) => { - this.pendingRequests.delete(id); - this.settledRequests.add(id); - }; - - public clearPendingRequests = () => { - clearInterval(this.timer); - this.queuedRequests.clear(); - Array.from(this.pendingRequests).forEach((id) => this.clearPendingRequest(id)); - }; - - public clearPendingRequest = (id: GridRowId) => { - this.api.setRowLoading(id, false); - this.pendingRequests.delete(id); +export const dataSourceStateInitializer: GridStateInitializer = (state, _, apiRef) => { + apiRef.current.caches.serverSideData = { + groupKeys: [], }; - public getRequestStatus = (id: GridRowId) => { - if (this.pendingRequests.has(id)) { - return RequestStatus.PENDING; - } - if (this.queuedRequests.has(id)) { - return RequestStatus.INQUEUE; - } - if (this.settledRequests.has(id)) { - return RequestStatus.SETTLED; - } - return RequestStatus.UNKNOWN; + return { + ...state, + serverSideData: INITIAL_STATE, }; - - public getActiveRequestsCount = () => this.pendingRequests.size + this.queuedRequests.size; -} +}; export const useGridDataSource = ( privateApiRef: React.MutableRefObject, props: Pick< DataGridProProcessedProps, | 'unstable_dataSource' - | 'unstable_onDataSourceError' + | 'unstable_onServerSideError' | 'sortingMode' | 'filterMode' | 'paginationMode' | 'treeData' >, -): void => { +) => { const nestedDataManager = React.useRef( new NestedDataManager(privateApiRef), ).current; const groupsToAutoFetch = useGridSelector(privateApiRef, gridRowGroupsToFetchSelector); const scheduledGroups = React.useRef(0); - const onError = props.unstable_onDataSourceError; + const onError = props.unstable_onServerSideError; const fetchTopLevelRows = React.useCallback(async () => { const getRows = props.unstable_dataSource?.getRows; @@ -157,19 +60,25 @@ export const useGridDataSource = ( return; } + // Reset the nested data variables + if (nestedDataManager.getActiveRequestsCount() > 0) { + nestedDataManager.clearPendingRequests(); + } + scheduledGroups.current = 0; + const serverSideState = privateApiRef.current.state.serverSideData; + if (serverSideState !== INITIAL_STATE) { + privateApiRef.current.resetServerSideState(); + } + const fetchParams = gridGetRowsParamsSelector(privateApiRef); const cachedData = privateApiRef.current.getCacheData(fetchParams) as | GridGetRowsResponse | undefined; - if (nestedDataManager.getActiveRequestsCount() > 0) { - nestedDataManager.clearPendingRequests(); - } - scheduledGroups.current = 0; if (cachedData != null) { const rows = cachedData.rows; - privateApiRef.current.caches.groupKeys = []; + privateApiRef.current.caches.serverSideData.groupKeys = []; privateApiRef.current.setRows(rows); if (cachedData.rowCount) { privateApiRef.current.setRowCount(cachedData.rowCount); @@ -188,7 +97,7 @@ export const useGridDataSource = ( if (getRowsResponse.rowCount) { privateApiRef.current.setRowCount(getRowsResponse.rowCount); } - privateApiRef.current.caches.groupKeys = []; + privateApiRef.current.caches.serverSideData.groupKeys = []; privateApiRef.current.setRows(getRowsResponse.rows); privateApiRef.current.setLoading(false); } catch (error) { @@ -205,7 +114,7 @@ export const useGridDataSource = ( [nestedDataManager], ); - const fetchRowChildren = React.useCallback( + const fetchRowChildren = React.useCallback( async (id) => { if (!props.treeData) { nestedDataManager.clearPendingRequest(id); @@ -225,13 +134,11 @@ export const useGridDataSource = ( const fetchParams = { ...gridGetRowsParamsSelector(privateApiRef), groupKeys: rowNode.path }; - const cachedData = privateApiRef.current.getCacheData(fetchParams) as - | GridGetRowsResponse - | undefined; + const cachedData = privateApiRef.current.getCacheData(fetchParams); if (cachedData != null) { const rows = cachedData.rows; - privateApiRef.current.caches.groupKeys = rowNode.path; + privateApiRef.current.caches.serverSideData.groupKeys = rowNode.path; nestedDataManager.setRequestSettled(id); privateApiRef.current.updateRows(rows, false); if (cachedData.rowCount) { @@ -241,9 +148,14 @@ export const useGridDataSource = ( return; } - const isLoading = rowNode.isLoading; + const isLoading = gridServerSideDataLoadingSelector(privateApiRef)[id] ?? false; if (!isLoading) { - privateApiRef.current.setRowLoading(id, true); + privateApiRef.current.setChildrenLoading(id, true); + } + + const existingError = gridServerSideDataErrorsSelector(privateApiRef)[id] ?? null; + if (existingError) { + privateApiRef.current.setChildrenFetchError(id, null); } try { @@ -251,12 +163,12 @@ export const useGridDataSource = ( if (!privateApiRef.current.getRowNode(id)) { // The row has been removed from the grid nestedDataManager.clearPendingRequest(id); - privateApiRef.current.setRowLoading(id, false); + privateApiRef.current.setChildrenLoading(id, false); return; } if (nestedDataManager.getRequestStatus(id) === RequestStatus.UNKNOWN) { // Unregistered or cancelled request - privateApiRef.current.setRowLoading(id, false); + privateApiRef.current.setChildrenLoading(id, false); return; } nestedDataManager.setRequestSettled(id); @@ -264,33 +176,44 @@ export const useGridDataSource = ( if (getRowsResponse.rowCount) { privateApiRef.current.setRowCount(getRowsResponse.rowCount); } - privateApiRef.current.caches.groupKeys = rowNode.path; + privateApiRef.current.caches.serverSideData.groupKeys = rowNode.path; privateApiRef.current.updateRows(getRowsResponse.rows, false); privateApiRef.current.setRowChildrenExpansion(id, true); - privateApiRef.current.setRowLoading(id, false); + privateApiRef.current.setChildrenLoading(id, false); } catch (error) { + const e = error as Error; nestedDataManager.setRequestSettled(id); - privateApiRef.current.setRowLoading(id, false); - onError?.(error as Error, fetchParams); + privateApiRef.current.setChildrenLoading(id, false); + privateApiRef.current.setChildrenFetchError(id, e); + onError?.(e as Error, fetchParams); } }, [nestedDataManager, onError, privateApiRef, props.treeData, props.unstable_dataSource?.getRows], ); - const setRowLoading = React.useCallback( - (id, isLoading) => { - const currentNode = privateApiRef.current.getRowNode(id) as GridServerSideGroupNode; - if (!currentNode) { - return; - } + const setChildrenLoading = React.useCallback( + (parentId, isLoading) => { + privateApiRef.current.setState((state) => { + return { + ...state, + serverSideData: { + ...state.serverSideData, + loading: { ...state.serverSideData.loading, [parentId]: isLoading }, + }, + }; + }); + }, + [privateApiRef], + ); - const newNode: GridServerSideGroupNode = { ...currentNode, isLoading }; + const setChildrenFetchError = React.useCallback( + (parentId, error) => { privateApiRef.current.setState((state) => { return { ...state, - rows: { - ...state.rows, - tree: { ...state.rows.tree, [id]: newNode }, + serverSideData: { + ...state.serverSideData, + errors: { ...state.serverSideData.errors, [parentId]: error }, }, }; }); @@ -298,15 +221,29 @@ export const useGridDataSource = ( [privateApiRef], ); + const resetServerSideState = React.useCallback(() => { + privateApiRef.current.setState((state) => { + return { + ...state, + serverSideData: INITIAL_STATE, + }; + }); + }, [privateApiRef]); + const dataSourceApi: GridDataSourceApi = { enqueueChildrenFetch, - // TODO: Make `fetchRowChildren` private - fetchRowChildren, - setRowLoading, + setChildrenLoading, + setChildrenFetchError, fetchTopLevelRows, }; + const dataSourcePrivateApi: GridDataSourcePrivateApi = { + fetchRowChildren, + resetServerSideState, + }; + useGridApiMethod(privateApiRef, dataSourceApi, 'public'); + useGridApiMethod(privateApiRef, dataSourcePrivateApi, 'private'); /* * EVENTS diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/utils.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/utils.ts new file mode 100644 index 000000000000..1ccea95d7542 --- /dev/null +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/utils.ts @@ -0,0 +1,120 @@ +import { GridRowId } from '@mui/x-data-grid'; +import { GridPrivateApiPro } from '../../../models/gridApiPro'; + +// Make these configurable using dedicated props? +const MAX_CONCURRENT_REQUESTS = Infinity; +const QUEUE_PROCESS_INTERVAL_MS = 300; + +export const runIfServerMode = (modeProp: 'server' | 'client', fn: Function) => () => { + if (modeProp === 'server') { + fn(); + } +}; + +export enum RequestStatus { + INQUEUE, + PENDING, + SETTLED, + UNKNOWN, +} + +/** + * 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` + * Uses `GridRowId` to uniquely identify a request + */ +export class NestedDataManager { + private pendingRequests: Set = new Set(); + + private queuedRequests: Set = new Set(); + + private settledRequests: Set = new Set(); + + private api: GridPrivateApiPro; + + private maxConcurrentRequests: number; + + private queueProcessInterval: number; + + private timer?: string | number | NodeJS.Timeout; + + constructor( + privateApiRef: React.MutableRefObject, + maxConcurrentRequests = MAX_CONCURRENT_REQUESTS, + queueProcessInterval = QUEUE_PROCESS_INTERVAL_MS, + ) { + this.api = privateApiRef.current; + this.maxConcurrentRequests = maxConcurrentRequests; + this.queueProcessInterval = queueProcessInterval; + } + + private processQueue = async () => { + if (this.queuedRequests.size === 0) { + clearInterval(this.timer); + return; + } + if (this.pendingRequests.size >= this.maxConcurrentRequests) { + return; + } + const fetchQueue = Array.from(this.queuedRequests); + for (let i = 0; i < this.maxConcurrentRequests; i += 1) { + const nextId = fetchQueue[i]; + if (!nextId) { + clearInterval(this.timer); + return; + } + this.queuedRequests.delete(nextId); + this.api.fetchRowChildren(nextId); + this.pendingRequests.add(nextId); + } + }; + + public enqueue = async (ids: GridRowId[]) => { + ids.forEach((id) => { + if (this.pendingRequests.size < this.maxConcurrentRequests) { + this.pendingRequests.add(id); + this.api.fetchRowChildren(id); + } else { + this.queuedRequests.add(id); + } + + if (this.queuedRequests.size > 0) { + if (this.timer) { + clearInterval(this.timer); + } + this.timer = setInterval(this.processQueue, this.queueProcessInterval); + } + }); + }; + + public setRequestSettled = (id: GridRowId) => { + this.pendingRequests.delete(id); + this.settledRequests.add(id); + }; + + public clearPendingRequests = () => { + clearInterval(this.timer); + this.queuedRequests.clear(); + Array.from(this.pendingRequests).forEach((id) => this.clearPendingRequest(id)); + }; + + public clearPendingRequest = (id: GridRowId) => { + this.api.setChildrenLoading(id, false); + this.pendingRequests.delete(id); + }; + + public getRequestStatus = (id: GridRowId) => { + if (this.pendingRequests.has(id)) { + return RequestStatus.PENDING; + } + if (this.queuedRequests.has(id)) { + return RequestStatus.INQUEUE; + } + if (this.settledRequests.has(id)) { + return RequestStatus.SETTLED; + } + return RequestStatus.UNKNOWN; + }; + + public getActiveRequestsCount = () => this.pendingRequests.size + this.queuedRequests.size; +} diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx index fe8265e63c55..ef15376943a8 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx @@ -142,7 +142,7 @@ export const useGridServerSideTreeDataPreProcessors = ( throw new Error('MUI X: No `hasChildren` prop provided.'); } - const parentPath = privateApiRef.current.caches.groupKeys || []; + const parentPath = privateApiRef.current.caches.serverSideData?.groupKeys || []; const getRowTreeBuilderNode = (rowId: GridRowId) => ({ id: rowId, diff --git a/packages/x-data-grid-pro/src/internals/index.ts b/packages/x-data-grid-pro/src/internals/index.ts index f9ef2b6a1556..bb128e661545 100644 --- a/packages/x-data-grid-pro/src/internals/index.ts +++ b/packages/x-data-grid-pro/src/internals/index.ts @@ -37,7 +37,10 @@ export { } from '../hooks/features/rowPinning/useGridRowPinningPreProcessors'; export { useGridLazyLoader } from '../hooks/features/lazyLoader/useGridLazyLoader'; export { useGridLazyLoaderPreProcessors } from '../hooks/features/lazyLoader/useGridLazyLoaderPreProcessors'; -export { useGridDataSource } from '../hooks/features/serverSideData/useGridDataSource'; +export { + useGridDataSource, + dataSourceStateInitializer, +} from '../hooks/features/serverSideData/useGridDataSource'; export { useGridServerSideCache } from '../hooks/features/serverSideData/useGridServerSideCache'; export type { diff --git a/packages/x-data-grid-pro/src/models/dataGridProProps.ts b/packages/x-data-grid-pro/src/models/dataGridProProps.ts index ef1bfcea5e65..dafe77620a9b 100644 --- a/packages/x-data-grid-pro/src/models/dataGridProProps.ts +++ b/packages/x-data-grid-pro/src/models/dataGridProProps.ts @@ -146,7 +146,7 @@ export interface DataGridProPropsWithDefaultValue void; + unstable_onServerSideError?: (error: Error, params: GridGetRowsParams) => void; getGroupKey?: (row: GridValidRowModel) => string; hasChildren?: (row: GridValidRowModel) => boolean; getChildrenCount?: (row: GridValidRowModel) => number; diff --git a/packages/x-data-grid-pro/src/models/gridApiPro.ts b/packages/x-data-grid-pro/src/models/gridApiPro.ts index 5a05ccc483eb..dd538df80bb9 100644 --- a/packages/x-data-grid-pro/src/models/gridApiPro.ts +++ b/packages/x-data-grid-pro/src/models/gridApiPro.ts @@ -12,6 +12,7 @@ import type { GridRowPinningApi, GridDetailPanelPrivateApi, GridDataSourceApi, + GridDataSourcePrivateApi, GridServerSideCacheApi, } from '../hooks'; import type { DataGridProProcessedProps } from './dataGridProProps'; @@ -35,4 +36,5 @@ export interface GridPrivateApiPro extends GridApiPro, GridPrivateOnlyApiCommon, GridDetailPanelPrivateApi, - GridInfiniteLoaderPrivateApi {} + GridInfiniteLoaderPrivateApi, + GridDataSourcePrivateApi {} diff --git a/packages/x-data-grid-pro/src/models/gridStatePro.ts b/packages/x-data-grid-pro/src/models/gridStatePro.ts index 662a9bed10b0..4e6d27dc05ce 100644 --- a/packages/x-data-grid-pro/src/models/gridStatePro.ts +++ b/packages/x-data-grid-pro/src/models/gridStatePro.ts @@ -9,6 +9,7 @@ import type { GridDetailPanelInitialState, GridColumnReorderState, } from '../hooks'; +import type { GridServerSideDataState } from '../hooks/features/serverSideData/interfaces'; /** * The state of `DataGridPro`. @@ -17,6 +18,7 @@ export interface GridStatePro extends GridStateCommunity { columnReorder: GridColumnReorderState; pinnedColumns: GridColumnPinningState; detailPanel: GridDetailPanelState; + serverSideData: GridServerSideDataState; } /** diff --git a/packages/x-data-grid-pro/src/typeOverloads/modules.ts b/packages/x-data-grid-pro/src/typeOverloads/modules.ts index 1d0fe7a306e6..e0d17c9e5e03 100644 --- a/packages/x-data-grid-pro/src/typeOverloads/modules.ts +++ b/packages/x-data-grid-pro/src/typeOverloads/modules.ts @@ -5,6 +5,7 @@ import type { GridFetchRowsParams, } from '../models'; import type { GridRenderHeaderFilterProps } from '../components/headerFiltering/GridHeaderFilterCell'; +import type { GridServerSideDataInternalCache } from '../hooks/features/serverSideData/interfaces'; import type { GridColumnPinningInternalCache } from '../hooks/features/columnPinning/gridColumnPinningInterface'; import type { GridCanBeReorderedPreProcessingContext } from '../hooks/features/columnReorder/columnReorderInterfaces'; import { GridRowPinningInternalCache } from '../hooks/features/rowPinning/gridRowPinningInterface'; @@ -56,6 +57,7 @@ export interface GridPipeProcessingLookupPro { export interface GridApiCachesPro { columnPinning: GridColumnPinningInternalCache; pinnedRows: GridRowPinningInternalCache; + serverSideData: GridServerSideDataInternalCache; } declare module '@mui/x-data-grid' { diff --git a/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts b/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts index c2dbd7c1f821..5fa55303d645 100644 --- a/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts +++ b/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts @@ -112,7 +112,6 @@ export const insertDataRowInTree = ({ children: [], childrenFromPath: {}, childrenExpanded: false, - isLoading: false, isServerSide: true, }; const shouldFetchChildren = checkGroupChildrenExpansion( diff --git a/packages/x-data-grid/src/models/gridApiCaches.ts b/packages/x-data-grid/src/models/gridApiCaches.ts index 78bf52db8caa..aa2e8932b91d 100644 --- a/packages/x-data-grid/src/models/gridApiCaches.ts +++ b/packages/x-data-grid/src/models/gridApiCaches.ts @@ -2,5 +2,4 @@ import { GridRowsInternalCache } from '../hooks/features/rows/gridRowsInterfaces export interface GridApiCaches { rows: GridRowsInternalCache; - groupKeys: string[]; } diff --git a/packages/x-data-grid/src/models/gridRows.ts b/packages/x-data-grid/src/models/gridRows.ts index aa6992d14791..f4a12bf36bf9 100644 --- a/packages/x-data-grid/src/models/gridRows.ts +++ b/packages/x-data-grid/src/models/gridRows.ts @@ -115,10 +115,6 @@ export interface GridDataGroupNode extends GridBasicGroupNode { } export interface GridServerSideGroupNode extends GridDataGroupNode { - /** - * The children for this node are currently being fetched - */ - isLoading: boolean; /** * If true, this node is a server side group node. */ diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 47b1b37496be..20875d189b2f 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -246,6 +246,7 @@ { "name": "GridDataSource", "kind": "Interface" }, { "name": "GridDataSourceApi", "kind": "Interface" }, { "name": "GridDataSourceCache", "kind": "Interface" }, + { "name": "GridDataSourcePrivateApi", "kind": "Interface" }, { "name": "gridDateComparator", "kind": "Variable" }, { "name": "gridDateFormatter", "kind": "Variable" }, { "name": "gridDateTimeFormatter", "kind": "Variable" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index 0ba5140ee0ac..a8a60b1bf9ea 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -220,6 +220,7 @@ { "name": "GridDataSource", "kind": "Interface" }, { "name": "GridDataSourceApi", "kind": "Interface" }, { "name": "GridDataSourceCache", "kind": "Interface" }, + { "name": "GridDataSourcePrivateApi", "kind": "Interface" }, { "name": "gridDateComparator", "kind": "Variable" }, { "name": "gridDateFormatter", "kind": "Variable" }, { "name": "gridDateTimeFormatter", "kind": "Variable" }, From ac7b58c1c08dd268052ea44a0d8957debd2edc7b Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 13 May 2024 14:54:59 +0500 Subject: [PATCH 20/90] Add more demos + rename dataSourceCache --- .../ServerSideDataGridWithSWR.js | 2 +- .../ServerSideDataGridWithSWR.tsx | 2 +- .../ServerSideTreeDataCustomCache.js | 84 +++++++++++++++++ .../ServerSideTreeDataCustomCache.tsx | 90 +++++++++++++++++++ .../ServerSideTreeDataCustomCache.tsx.preview | 15 ++++ .../ServerSideTreeDataGroupExpansion.js | 55 ++++++++++++ .../ServerSideTreeDataGroupExpansion.tsx | 60 +++++++++++++ ...rverSideTreeDataGroupExpansion.tsx.preview | 15 ++++ .../data-grid/server-side-data/tree-data.md | 22 +++++ .../src/DataGridPremium/DataGridPremium.tsx | 4 +- .../src/DataGridPro/DataGridPro.tsx | 4 +- .../serverSideData/useGridServerSideCache.ts | 26 +++--- .../src/models/dataGridProProps.ts | 4 +- packages/x-data-grid-pro/src/models/index.ts | 2 +- .../x-data-grid/src/models/gridDataSource.ts | 22 ++--- scripts/x-data-grid-premium.exports.json | 2 +- scripts/x-data-grid-pro.exports.json | 2 +- 17 files changed, 377 insertions(+), 34 deletions(-) create mode 100644 docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js create mode 100644 docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx create mode 100644 docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx.preview create mode 100644 docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js create mode 100644 docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx create mode 100644 docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx.preview diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js index 4b3177e43484..7e2b61d7b248 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js @@ -50,7 +50,7 @@ function ServerSideDataGridWithSWR() { { + queryClient.setQueryData(key, value); + }, + get: (key) => { + return queryClient.getQueryData(key); + }, + clear: () => { + queryClient.clear(); + }, + getKey: (params) => { + return [ + params.paginationModel, + params.sortModel, + params.filterModel, + params.groupKeys, + ]; + }, +}; + +const pageSizeOptions = [5, 10, 50]; + +export default function ServerSideTreeDataCustomCache() { + const apiRef = useGridApiRef(); + + const { getRows, ...props } = useDemoDataSource({ + dataSet: 'Employee', + rowLength: 1000, + treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, + }); + + const dataSource = React.useMemo(() => { + return { + getRows, + }; + }, [getRows]); + + const initialState = React.useMemo( + () => ({ + ...props.initialState, + pagination: { + paginationModel: { + pageSize: 5, + }, + rowCount: 0, + }, + }), + [props.initialState], + ); + + return ( +
+ +
+ +
+
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx new file mode 100644 index 000000000000..77276f45fcaf --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx @@ -0,0 +1,90 @@ +import * as React from 'react'; +import { + DataGridPro, + useGridApiRef, + GridInitialState, + GridToolbar, + GridServerSideCache, +} from '@mui/x-data-grid-pro'; +import Button from '@mui/material/Button'; +import { useDemoDataSource } from '@mui/x-data-grid-generator'; +import { QueryClient } from '@tanstack/query-core'; + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 1000 * 60 * 60, + }, + }, +}); + +const cache: GridServerSideCache = { + set: (key: any[], value) => { + queryClient.setQueryData(key, value); + }, + get: (key: any[]) => { + return queryClient.getQueryData(key); + }, + clear: () => { + queryClient.clear(); + }, + getKey: (params) => { + return [ + params.paginationModel, + params.sortModel, + params.filterModel, + params.groupKeys, + ]; + }, +}; + +const pageSizeOptions = [5, 10, 50]; + +export default function ServerSideTreeDataCustomCache() { + const apiRef = useGridApiRef(); + + const { getRows, ...props } = useDemoDataSource({ + dataSet: 'Employee', + rowLength: 1000, + treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, + }); + + const dataSource = React.useMemo(() => { + return { + getRows, + }; + }, [getRows]); + + const initialState: GridInitialState = React.useMemo( + () => ({ + ...props.initialState, + pagination: { + paginationModel: { + pageSize: 5, + }, + rowCount: 0, + }, + }), + [props.initialState], + ); + + return ( +
+ +
+ +
+
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx.preview new file mode 100644 index 000000000000..ef89b01d59e2 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx.preview @@ -0,0 +1,15 @@ + +
+ +
\ No newline at end of file diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js new file mode 100644 index 000000000000..2cced6425928 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js @@ -0,0 +1,55 @@ +import * as React from 'react'; +import { DataGridPro, useGridApiRef, GridToolbar } from '@mui/x-data-grid-pro'; +import Button from '@mui/material/Button'; +import { useDemoDataSource } from '@mui/x-data-grid-generator'; + +const pageSizeOptions = [5, 10, 50]; + +export default function ServerSideTreeDataGroupExpansion() { + const apiRef = useGridApiRef(); + + const { getRows, ...props } = useDemoDataSource({ + dataSet: 'Employee', + rowLength: 1000, + treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, + }); + + const dataSource = React.useMemo(() => { + return { + getRows, + }; + }, [getRows]); + + const initialState = React.useMemo( + () => ({ + ...props.initialState, + pagination: { + paginationModel: { + pageSize: 5, + }, + rowCount: 0, + }, + }), + [props.initialState], + ); + + return ( +
+ +
+ +
+
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx new file mode 100644 index 000000000000..a1acd5e88565 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx @@ -0,0 +1,60 @@ +import * as React from 'react'; +import { + DataGridPro, + useGridApiRef, + GridInitialState, + GridToolbar, +} from '@mui/x-data-grid-pro'; +import Button from '@mui/material/Button'; +import { useDemoDataSource } from '@mui/x-data-grid-generator'; + +const pageSizeOptions = [5, 10, 50]; + +export default function ServerSideTreeDataGroupExpansion() { + const apiRef = useGridApiRef(); + + const { getRows, ...props } = useDemoDataSource({ + dataSet: 'Employee', + rowLength: 1000, + treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, + }); + + const dataSource = React.useMemo(() => { + return { + getRows, + }; + }, [getRows]); + + const initialState: GridInitialState = React.useMemo( + () => ({ + ...props.initialState, + pagination: { + paginationModel: { + pageSize: 5, + }, + rowCount: 0, + }, + }), + [props.initialState], + ); + + return ( +
+ +
+ +
+
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx.preview new file mode 100644 index 000000000000..8556c2bc578b --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx.preview @@ -0,0 +1,15 @@ + +
+ +
\ No newline at end of file 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 951fc27ef379..1aeae9915b61 100644 --- a/docs/data/data-grid/server-side-data/tree-data.md +++ b/docs/data/data-grid/server-side-data/tree-data.md @@ -57,3 +57,25 @@ For each row group expansion, the data source is called to fetch the children. I The demo below shows a toast apart from the default error message in the grouping cell. Cache has been disabled in this demo for simplicity. {{"demo": "ServerSideTreeDataErrorHandling.js", "bg": "inline"}} + +## Group expansion + +The idea behind the group expansion is the same as explained in the [Row grouping](/x/react-data-grid/row-grouping/#group-expansion) section. The difference is that the data is not readily available and is fetched automatically on Data Grid mount based on the props `defaultGroupingExpansionDepth` and `isGroupExpandedByDefault` in a waterfall manner. + +The following demo uses `defaultGroupingExpansionDepth='-1'` to expand all the level of the tree by default. + +{{"demo": "ServerSideTreeDataGroupExpansion.js", "bg": "inline"}} + +## Custom cache + +The data source uses a cache by default to store the fetched data. Use `unstable_serverSideCache` to provide a custom cache to the data source to manage the cache as per your requirements. See more about caching in the [overview section](/x/react-data-grid/server-side-data/#data-caching). + +The following demo uses `QueryClient` from `@tanstack/react-core` to provide a custom cache to the Grid which could be manipulated on the userland. + +{{"demo": "ServerSideTreeDataCustomCache.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/) diff --git a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx index 476538f54544..6d8efccde094 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx @@ -1053,13 +1053,13 @@ DataGridPremiumRaw.propTypes = { getRows: PropTypes.func.isRequired, updateRow: PropTypes.func, }), - unstable_dataSourceCache: PropTypes.shape({ + unstable_onServerSideError: PropTypes.func, + unstable_serverSideCache: PropTypes.shape({ clear: PropTypes.func.isRequired, get: PropTypes.func.isRequired, getKey: PropTypes.func.isRequired, set: PropTypes.func.isRequired, }), - unstable_onServerSideError: PropTypes.func, } as any; interface DataGridPremiumComponent { diff --git a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx index f3412ece79c0..3a460596e4ad 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx @@ -953,11 +953,11 @@ DataGridProRaw.propTypes = { getRows: PropTypes.func.isRequired, updateRow: PropTypes.func, }), - unstable_dataSourceCache: PropTypes.shape({ + unstable_onServerSideError: PropTypes.func, + unstable_serverSideCache: PropTypes.shape({ clear: PropTypes.func.isRequired, get: PropTypes.func.isRequired, getKey: PropTypes.func.isRequired, set: PropTypes.func.isRequired, }), - unstable_onServerSideError: PropTypes.func, } as any; diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridServerSideCache.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridServerSideCache.ts index b8ee6f25913c..76fb92f78875 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridServerSideCache.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridServerSideCache.ts @@ -2,7 +2,7 @@ import * as React from 'react'; import { useGridApiMethod } from '@mui/x-data-grid'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; -import { GridGetRowsParams, GridGetRowsResponse, GridDataSourceCache } from '../../../models'; +import { GridGetRowsParams, GridGetRowsResponse, GridServerSideCache } from '../../../models'; import { GridServerSideCacheApi } from './serverSideInterfaces'; class SimpleServerSideCache { @@ -34,23 +34,25 @@ class SimpleServerSideCache { } } -const cacheInstance = new SimpleServerSideCache(); - -const defaultCache: GridDataSourceCache = { +const getDefaultCache = (cacheInstance: SimpleServerSideCache): GridServerSideCache => ({ getKey: SimpleServerSideCache.getKey, - set: (key, value) => cacheInstance.set(key as string, value as GridGetRowsResponse), - get: (key) => cacheInstance.get(key as string), + set: (key: string, value: GridGetRowsResponse) => + cacheInstance.set(key as string, value as GridGetRowsResponse), + get: (key: string) => cacheInstance.get(key as string), clear: () => cacheInstance.clear(), -}; +}); export const useGridServerSideCache = ( privateApiRef: React.MutableRefObject, props: Pick< DataGridProProcessedProps, - 'unstable_dataSource' | 'disableServerSideCache' | 'unstable_dataSourceCache' + 'unstable_dataSource' | 'disableServerSideCache' | 'unstable_serverSideCache' >, ): void => { - const cache = React.useRef(props.unstable_dataSourceCache || defaultCache); + const defaultCache = React.useRef(getDefaultCache(new SimpleServerSideCache())); + const cache = React.useRef( + props.unstable_serverSideCache || defaultCache.current, + ); const getCacheData = React.useCallback( (params: GridGetRowsParams) => { @@ -95,8 +97,8 @@ export const useGridServerSideCache = ( isFirstRender.current = false; return; } - if (props.unstable_dataSourceCache) { - cache.current = props.unstable_dataSourceCache; + if (props.unstable_serverSideCache) { + cache.current = props.unstable_serverSideCache; } - }, [props.unstable_dataSourceCache]); + }, [props.unstable_serverSideCache]); }; diff --git a/packages/x-data-grid-pro/src/models/dataGridProProps.ts b/packages/x-data-grid-pro/src/models/dataGridProProps.ts index dafe77620a9b..632432a846cc 100644 --- a/packages/x-data-grid-pro/src/models/dataGridProProps.ts +++ b/packages/x-data-grid-pro/src/models/dataGridProProps.ts @@ -17,7 +17,7 @@ import type { GridPinnedColumnFields, DataGridProSharedPropsWithDefaultValue, DataGridProSharedPropsWithoutDefaultValue, - GridDataSourceCache, + GridServerSideCache, GridGetRowsParams, } from '@mui/x-data-grid/internals'; import type { GridPinnedRowsProp } from '../hooks/features/rowPinning'; @@ -145,7 +145,7 @@ export interface DataGridProPropsWithDefaultValue void; getGroupKey?: (row: GridValidRowModel) => string; hasChildren?: (row: GridValidRowModel) => boolean; diff --git a/packages/x-data-grid-pro/src/models/index.ts b/packages/x-data-grid-pro/src/models/index.ts index 8110b6c70a91..652e6f63d6d5 100644 --- a/packages/x-data-grid-pro/src/models/index.ts +++ b/packages/x-data-grid-pro/src/models/index.ts @@ -2,7 +2,7 @@ export type { GridGetRowsParams, GridGetRowsResponse, GridDataSource, - GridDataSourceCache, + GridServerSideCache, } from '@mui/x-data-grid/internals'; export * from './gridApiPro'; export * from './gridGroupingColDefOverride'; diff --git a/packages/x-data-grid/src/models/gridDataSource.ts b/packages/x-data-grid/src/models/gridDataSource.ts index eeb96b7bafd9..e52641cf7d46 100644 --- a/packages/x-data-grid/src/models/gridDataSource.ts +++ b/packages/x-data-grid/src/models/gridDataSource.ts @@ -65,27 +65,27 @@ export interface GridDataSource { updateRow?(updatedRow: GridRowModel): Promise; } -export interface GridDataSourceCache { +export interface GridServerSideCache { /** - * Provides a key for the cache to be used in `set` and `get` + * Provide a key for the cache to be used in `set` and `get` * @param {GridGetRowsParams} params The parameters required to fetch the rows - * @returns {unknown} The key for the cache to be used in `set` and `get` + * @returns {any} The key for the cache to be used in `set` and `get` */ - getKey: (params: GridGetRowsParams) => unknown; + getKey: (params: GridGetRowsParams) => any; /** - * Sets the cache entry for the given key - * @param {unknown} key The key for the cache + * Set the cache entry for the given key + * @param {any} key The key for the cache * @param {GridGetRowsResponse} value The value to be stored in the cache */ - set: (key: unknown, value: GridGetRowsResponse) => void; + set: (key: any, value: GridGetRowsResponse) => void; /** - * Gets the cache entry for the given key - * @param {unknown} key The key for the cache + * Get the cache entry for the given key + * @param {any} key The key for the cache * @returns {GridGetRowsResponse} The value stored in the cache */ - get: (key: unknown) => GridGetRowsResponse; + get: (key: any) => GridGetRowsResponse | undefined; /** - * Clears the cache + * Clear the cache */ clear: () => void; } diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 20875d189b2f..ed71327c361a 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -245,7 +245,6 @@ { "name": "gridDataRowIdsSelector", "kind": "Variable" }, { "name": "GridDataSource", "kind": "Interface" }, { "name": "GridDataSourceApi", "kind": "Interface" }, - { "name": "GridDataSourceCache", "kind": "Interface" }, { "name": "GridDataSourcePrivateApi", "kind": "Interface" }, { "name": "gridDateComparator", "kind": "Variable" }, { "name": "gridDateFormatter", "kind": "Variable" }, @@ -542,6 +541,7 @@ { "name": "GridSearchIcon", "kind": "Variable" }, { "name": "GridSelectedRowCount", "kind": "Variable" }, { "name": "GridSeparatorIcon", "kind": "Variable" }, + { "name": "GridServerSideCache", "kind": "Interface" }, { "name": "GridServerSideCacheApi", "kind": "Interface" }, { "name": "GridServerSideGroupNode", "kind": "Interface" }, { "name": "GridSignature", "kind": "Enum" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index a8a60b1bf9ea..57c8c5590a0d 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -219,7 +219,6 @@ { "name": "gridDataRowIdsSelector", "kind": "Variable" }, { "name": "GridDataSource", "kind": "Interface" }, { "name": "GridDataSourceApi", "kind": "Interface" }, - { "name": "GridDataSourceCache", "kind": "Interface" }, { "name": "GridDataSourcePrivateApi", "kind": "Interface" }, { "name": "gridDateComparator", "kind": "Variable" }, { "name": "gridDateFormatter", "kind": "Variable" }, @@ -496,6 +495,7 @@ { "name": "GridSearchIcon", "kind": "Variable" }, { "name": "GridSelectedRowCount", "kind": "Variable" }, { "name": "GridSeparatorIcon", "kind": "Variable" }, + { "name": "GridServerSideCache", "kind": "Interface" }, { "name": "GridServerSideCacheApi", "kind": "Interface" }, { "name": "GridServerSideGroupNode", "kind": "Interface" }, { "name": "GridSignature", "kind": "Enum" }, From 909a71d675e0702b2dbbd3854e320d8221fb254b Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 13 May 2024 16:40:06 +0500 Subject: [PATCH 21/90] Docs improvements --- docs/data/data-grid/server-side-data/index.md | 11 +- .../data-grid/server-side-data/tree-data.md | 2 +- .../tree-data/TreeDataLazyLoading.js | 297 ---------------- .../tree-data/TreeDataLazyLoading.tsx | 329 ------------------ .../tree-data/TreeDataLazyLoading.tsx.preview | 10 - docs/data/data-grid/tree-data/tree-data.md | 18 +- 6 files changed, 8 insertions(+), 659 deletions(-) delete mode 100644 docs/data/data-grid/tree-data/TreeDataLazyLoading.js delete mode 100644 docs/data/data-grid/tree-data/TreeDataLazyLoading.tsx delete mode 100644 docs/data/data-grid/tree-data/TreeDataLazyLoading.tsx.preview diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index f692ee2e557f..652cb45c0af6 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -201,12 +201,11 @@ The out-of-the-box cache is a simple in-memory cache that stores the data in a p To provide a custom cache, use `unstable_serverSideCache` prop, which could be either written from scratch or based out of another cache library. This prop accepts a generic interface of type `GridServerSideCache`. ```tsx -interface GridServerSideCache { - getKey(key: GridGetRowsParams): unknown; - set(key: unknown, value: unknown): void; - get(key: unknown): unknown; - clear(): void; -} +export interface GridServerSideCache { + getKey: (params: GridGetRowsParams) => any; + set: (key: any, value: GridGetRowsResponse) => void; + get: (key: any) => GridGetRowsResponse | undefined; + clear: () => void; ``` The following demo uses cache used by a popular library [`swr`](https://github.com/vercel/swr) to cache the server-side data. 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 1aeae9915b61..d71046aaf8f2 100644 --- a/docs/data/data-grid/server-side-data/tree-data.md +++ b/docs/data/data-grid/server-side-data/tree-data.md @@ -60,7 +60,7 @@ The demo below shows a toast apart from the default error message in the groupin ## Group expansion -The idea behind the group expansion is the same as explained in the [Row grouping](/x/react-data-grid/row-grouping/#group-expansion) section. The difference is that the data is not readily available and is fetched automatically on Data Grid mount based on the props `defaultGroupingExpansionDepth` and `isGroupExpandedByDefault` in a waterfall manner. +The idea behind the group expansion is the same as explained in the [Row grouping](/x/react-data-grid/row-grouping/#group-expansion) section. The difference is that the data is not readily available and is fetched automatically after the Data Grid is mounted based on the props `defaultGroupingExpansionDepth` and `isGroupExpandedByDefault` in a waterfall manner. The following demo uses `defaultGroupingExpansionDepth='-1'` to expand all the level of the tree by default. diff --git a/docs/data/data-grid/tree-data/TreeDataLazyLoading.js b/docs/data/data-grid/tree-data/TreeDataLazyLoading.js deleted file mode 100644 index ea3f9c8fd2a4..000000000000 --- a/docs/data/data-grid/tree-data/TreeDataLazyLoading.js +++ /dev/null @@ -1,297 +0,0 @@ -import * as React from 'react'; -import { - DataGridPro, - getDataGridUtilityClass, - useGridApiContext, - useGridApiRef, - useGridRootProps, -} from '@mui/x-data-grid-pro'; -import { unstable_composeClasses as composeClasses, styled } from '@mui/material'; -import Box from '@mui/material/Box'; -import CircularProgress from '@mui/material/CircularProgress'; -import IconButton from '@mui/material/IconButton'; - -export const isNavigationKey = (key) => - key === 'Home' || - key === 'End' || - key.indexOf('Arrow') === 0 || - key.indexOf('Page') === 0 || - key === ' '; - -const ALL_ROWS = [ - { - hierarchy: ['Sarah'], - jobTitle: 'Head of Human Resources', - recruitmentDate: new Date(2020, 8, 12), - id: 0, - }, - { - hierarchy: ['Thomas'], - jobTitle: 'Head of Sales', - recruitmentDate: new Date(2017, 3, 4), - id: 1, - }, - { - hierarchy: ['Thomas', 'Robert'], - jobTitle: 'Sales Person', - recruitmentDate: new Date(2020, 11, 20), - id: 2, - }, - { - hierarchy: ['Thomas', 'Karen'], - jobTitle: 'Sales Person', - recruitmentDate: new Date(2020, 10, 14), - id: 3, - }, - { - hierarchy: ['Thomas', 'Nancy'], - jobTitle: 'Sales Person', - recruitmentDate: new Date(2017, 10, 29), - id: 4, - }, - { - hierarchy: ['Thomas', 'Daniel'], - jobTitle: 'Sales Person', - recruitmentDate: new Date(2020, 7, 21), - id: 5, - }, - { - hierarchy: ['Thomas', 'Christopher'], - jobTitle: 'Sales Person', - recruitmentDate: new Date(2020, 7, 20), - id: 6, - }, - { - hierarchy: ['Thomas', 'Donald'], - jobTitle: 'Sales Person', - recruitmentDate: new Date(2019, 6, 28), - id: 7, - }, - { - hierarchy: ['Mary'], - jobTitle: 'Head of Engineering', - recruitmentDate: new Date(2016, 3, 14), - id: 8, - }, - { - hierarchy: ['Mary', 'Jennifer'], - jobTitle: 'Tech lead front', - recruitmentDate: new Date(2016, 5, 17), - id: 9, - }, - { - hierarchy: ['Mary', 'Jennifer', 'Anna'], - jobTitle: 'Front-end developer', - recruitmentDate: new Date(2019, 11, 7), - id: 10, - }, - { - hierarchy: ['Mary', 'Michael'], - jobTitle: 'Tech lead devops', - recruitmentDate: new Date(2021, 7, 1), - id: 11, - }, - { - hierarchy: ['Mary', 'Linda'], - jobTitle: 'Tech lead back', - recruitmentDate: new Date(2017, 0, 12), - id: 12, - }, - { - hierarchy: ['Mary', 'Linda', 'Elizabeth'], - jobTitle: 'Back-end developer', - recruitmentDate: new Date(2019, 2, 22), - id: 13, - }, - { - hierarchy: ['Mary', 'Linda', 'William'], - jobTitle: 'Back-end developer', - recruitmentDate: new Date(2018, 4, 19), - id: 14, - }, -]; - -const columns = [ - { field: 'jobTitle', headerName: 'Job Title', width: 200 }, - { - field: 'recruitmentDate', - headerName: 'Recruitment Date', - type: 'date', - width: 150, - }, -]; - -const getChildren = (parentPath) => { - const parentPathStr = parentPath.join('-'); - return ALL_ROWS.filter( - (row) => row.hierarchy.slice(0, -1).join('-') === parentPathStr, - ); -}; - -/** - * This is a naive implementation with terrible performances on a real dataset. - * This fake server is only here for demonstration purposes. - */ -const fakeDataFetcher = (parentPath = []) => - new Promise((resolve) => { - setTimeout( - () => { - const rows = getChildren(parentPath).map((row) => ({ - ...row, - descendantCount: getChildren(row.hierarchy).length, - })); - resolve(rows); - }, - 500 + Math.random() * 300, - ); - }); - -const LoadingContainer = styled('div')({ - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - height: '100%', -}); - -const getTreeDataPath = (row) => row.hierarchy; - -const useUtilityClasses = (ownerState) => { - const { classes } = ownerState; - - const slots = { - root: ['treeDataGroupingCell'], - toggle: ['treeDataGroupingCellToggle'], - }; - - return composeClasses(slots, getDataGridUtilityClass, classes); -}; - -/** - * Reproduce the behavior of the `GridTreeDataGroupingCell` component in `@mui/x-data-grid-pro` - * But base the amount of children on a `row.descendantCount` property rather than on the internal lookups. - */ -function GroupingCellWithLazyLoading(props) { - const { id, rowNode, row, hideDescendantCount, formattedValue } = props; - - const rootProps = useGridRootProps(); - const apiRef = useGridApiContext(); - const classes = useUtilityClasses({ classes: rootProps.classes }); - - const isLoading = rowNode.childrenExpanded ? !row.childrenFetched : false; - - const Icon = rowNode.childrenExpanded - ? rootProps.slots.treeDataCollapseIcon - : rootProps.slots.treeDataExpandIcon; - - const handleClick = () => { - apiRef.current.setRowChildrenExpansion(id, !rowNode.childrenExpanded); - }; - - return ( - -
- {row.descendantCount > 0 && - (isLoading ? ( - - - - ) : ( - - - - ))} -
- - {formattedValue === undefined ? rowNode.groupingKey : formattedValue} - {!hideDescendantCount && row.descendantCount > 0 - ? ` (${row.descendantCount})` - : ''} - -
- ); -} - -const CUSTOM_GROUPING_COL_DEF = { - renderCell: (params) => , -}; - -// Optional -const getRowId = (row) => { - if (typeof row?.id === 'string' && row?.id.startsWith('placeholder-children-')) { - return row.id; - } - return row.id; -}; - -function updateRows(apiRef, rows) { - if (!apiRef.current) { - return; - } - const rowsToAdd = [...rows]; - rows.forEach((row) => { - if (row.descendantCount && row.descendantCount > 0) { - // Add a placeholder row to make the row expandable - rowsToAdd.push({ - id: `placeholder-children-${getRowId(row)}`, - hierarchy: [...row.hierarchy, ''], - }); - } - }); - apiRef.current.updateRows(rowsToAdd); -} - -const initialRows = []; - -export default function TreeDataLazyLoading() { - const apiRef = useGridApiRef(); - - React.useEffect(() => { - fakeDataFetcher().then((rowsData) => { - updateRows(apiRef, rowsData); - }); - - const handleRowExpansionChange = async (node) => { - const row = apiRef.current.getRow(node.id); - - if (!node.childrenExpanded || !row || row.childrenFetched) { - return; - } - - const childrenRows = await fakeDataFetcher(row.hierarchy); - updateRows(apiRef, [ - ...childrenRows, - { ...row, childrenFetched: true }, - { id: `placeholder-children-${node.id}`, _action: 'delete' }, - ]); - }; - - return apiRef.current.subscribeEvent( - 'rowExpansionChange', - handleRowExpansionChange, - ); - }, [apiRef]); - - return ( -
- -
- ); -} diff --git a/docs/data/data-grid/tree-data/TreeDataLazyLoading.tsx b/docs/data/data-grid/tree-data/TreeDataLazyLoading.tsx deleted file mode 100644 index 4eb26369ed09..000000000000 --- a/docs/data/data-grid/tree-data/TreeDataLazyLoading.tsx +++ /dev/null @@ -1,329 +0,0 @@ -import * as React from 'react'; -import { - DataGridPro, - GridApi, - getDataGridUtilityClass, - GridColDef, - DataGridProProps, - GridEventListener, - GridGroupingColDefOverride, - GridRenderCellParams, - GridRowModel, - GridRowsProp, - GridGroupNode, - useGridApiContext, - useGridApiRef, - useGridRootProps, - GridRowModelUpdate, - GridRowIdGetter, -} from '@mui/x-data-grid-pro'; -import { unstable_composeClasses as composeClasses, styled } from '@mui/material'; -import Box from '@mui/material/Box'; -import CircularProgress from '@mui/material/CircularProgress'; -import IconButton, { IconButtonProps } from '@mui/material/IconButton'; - -export const isNavigationKey = (key: string) => - key === 'Home' || - key === 'End' || - key.indexOf('Arrow') === 0 || - key.indexOf('Page') === 0 || - key === ' '; - -interface Row { - hierarchy: string[]; - jobTitle: string; - recruitmentDate: Date; - id: number; - descendantCount?: number; - childrenFetched?: boolean; -} - -const ALL_ROWS: GridRowModel[] = [ - { - hierarchy: ['Sarah'], - jobTitle: 'Head of Human Resources', - recruitmentDate: new Date(2020, 8, 12), - id: 0, - }, - { - hierarchy: ['Thomas'], - jobTitle: 'Head of Sales', - recruitmentDate: new Date(2017, 3, 4), - id: 1, - }, - { - hierarchy: ['Thomas', 'Robert'], - jobTitle: 'Sales Person', - recruitmentDate: new Date(2020, 11, 20), - id: 2, - }, - { - hierarchy: ['Thomas', 'Karen'], - jobTitle: 'Sales Person', - recruitmentDate: new Date(2020, 10, 14), - id: 3, - }, - { - hierarchy: ['Thomas', 'Nancy'], - jobTitle: 'Sales Person', - recruitmentDate: new Date(2017, 10, 29), - id: 4, - }, - { - hierarchy: ['Thomas', 'Daniel'], - jobTitle: 'Sales Person', - recruitmentDate: new Date(2020, 7, 21), - id: 5, - }, - { - hierarchy: ['Thomas', 'Christopher'], - jobTitle: 'Sales Person', - recruitmentDate: new Date(2020, 7, 20), - id: 6, - }, - { - hierarchy: ['Thomas', 'Donald'], - jobTitle: 'Sales Person', - recruitmentDate: new Date(2019, 6, 28), - id: 7, - }, - { - hierarchy: ['Mary'], - jobTitle: 'Head of Engineering', - recruitmentDate: new Date(2016, 3, 14), - id: 8, - }, - { - hierarchy: ['Mary', 'Jennifer'], - jobTitle: 'Tech lead front', - recruitmentDate: new Date(2016, 5, 17), - id: 9, - }, - { - hierarchy: ['Mary', 'Jennifer', 'Anna'], - jobTitle: 'Front-end developer', - recruitmentDate: new Date(2019, 11, 7), - id: 10, - }, - { - hierarchy: ['Mary', 'Michael'], - jobTitle: 'Tech lead devops', - recruitmentDate: new Date(2021, 7, 1), - id: 11, - }, - { - hierarchy: ['Mary', 'Linda'], - jobTitle: 'Tech lead back', - recruitmentDate: new Date(2017, 0, 12), - id: 12, - }, - { - hierarchy: ['Mary', 'Linda', 'Elizabeth'], - jobTitle: 'Back-end developer', - recruitmentDate: new Date(2019, 2, 22), - id: 13, - }, - { - hierarchy: ['Mary', 'Linda', 'William'], - jobTitle: 'Back-end developer', - recruitmentDate: new Date(2018, 4, 19), - id: 14, - }, -]; - -const columns: GridColDef[] = [ - { field: 'jobTitle', headerName: 'Job Title', width: 200 }, - { - field: 'recruitmentDate', - headerName: 'Recruitment Date', - type: 'date', - width: 150, - }, -]; - -const getChildren = (parentPath: string[]) => { - const parentPathStr = parentPath.join('-'); - return ALL_ROWS.filter( - (row) => row.hierarchy.slice(0, -1).join('-') === parentPathStr, - ); -}; - -/** - * This is a naive implementation with terrible performances on a real dataset. - * This fake server is only here for demonstration purposes. - */ -const fakeDataFetcher = (parentPath: string[] = []) => - new Promise[]>((resolve) => { - setTimeout( - () => { - const rows = getChildren(parentPath).map((row) => ({ - ...row, - descendantCount: getChildren(row.hierarchy).length, - })); - resolve(rows); - }, - 500 + Math.random() * 300, - ); - }); - -const LoadingContainer = styled('div')({ - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - height: '100%', -}); - -const getTreeDataPath: DataGridProProps['getTreeDataPath'] = (row) => row.hierarchy; - -const useUtilityClasses = (ownerState: { classes: DataGridProProps['classes'] }) => { - const { classes } = ownerState; - - const slots = { - root: ['treeDataGroupingCell'], - toggle: ['treeDataGroupingCellToggle'], - }; - - return composeClasses(slots, getDataGridUtilityClass, classes); -}; - -interface GroupingCellWithLazyLoadingProps - extends GridRenderCellParams { - hideDescendantCount?: boolean; -} - -/** - * Reproduce the behavior of the `GridTreeDataGroupingCell` component in `@mui/x-data-grid-pro` - * But base the amount of children on a `row.descendantCount` property rather than on the internal lookups. - */ -function GroupingCellWithLazyLoading(props: GroupingCellWithLazyLoadingProps) { - const { id, rowNode, row, hideDescendantCount, formattedValue } = props; - - const rootProps = useGridRootProps(); - const apiRef = useGridApiContext(); - const classes = useUtilityClasses({ classes: rootProps.classes }); - - const isLoading = rowNode.childrenExpanded ? !row.childrenFetched : false; - - const Icon = rowNode.childrenExpanded - ? rootProps.slots.treeDataCollapseIcon - : rootProps.slots.treeDataExpandIcon; - - const handleClick: IconButtonProps['onClick'] = () => { - apiRef.current.setRowChildrenExpansion(id, !rowNode.childrenExpanded); - }; - - return ( - -
- {row.descendantCount > 0 && - (isLoading ? ( - - - - ) : ( - - - - ))} -
- - {formattedValue === undefined ? rowNode.groupingKey : formattedValue} - {!hideDescendantCount && row.descendantCount > 0 - ? ` (${row.descendantCount})` - : ''} - -
- ); -} - -const CUSTOM_GROUPING_COL_DEF: GridGroupingColDefOverride = { - renderCell: (params) => ( - - ), -}; - -// Optional -const getRowId: GridRowIdGetter = (row) => { - if (typeof row?.id === 'string' && row?.id.startsWith('placeholder-children-')) { - return row.id; - } - return row.id; -}; - -function updateRows( - apiRef: React.MutableRefObject, - rows: GridRowModelUpdate[], -) { - if (!apiRef.current) { - return; - } - const rowsToAdd = [...rows]; - rows.forEach((row) => { - if (row.descendantCount && row.descendantCount > 0) { - // Add a placeholder row to make the row expandable - rowsToAdd.push({ - id: `placeholder-children-${getRowId(row)}`, - hierarchy: [...row.hierarchy, ''], - }); - } - }); - apiRef.current.updateRows(rowsToAdd); -} - -const initialRows: GridRowsProp = []; - -export default function TreeDataLazyLoading() { - const apiRef = useGridApiRef(); - - React.useEffect(() => { - fakeDataFetcher().then((rowsData) => { - updateRows(apiRef, rowsData); - }); - - const handleRowExpansionChange: GridEventListener<'rowExpansionChange'> = async ( - node, - ) => { - const row = apiRef.current.getRow(node.id) as Row | null; - - if (!node.childrenExpanded || !row || row.childrenFetched) { - return; - } - - const childrenRows = await fakeDataFetcher(row.hierarchy); - updateRows(apiRef, [ - ...childrenRows, - { ...row, childrenFetched: true }, - { id: `placeholder-children-${node.id}`, _action: 'delete' }, - ]); - }; - - return apiRef.current.subscribeEvent( - 'rowExpansionChange', - handleRowExpansionChange, - ); - }, [apiRef]); - - return ( -
- -
- ); -} diff --git a/docs/data/data-grid/tree-data/TreeDataLazyLoading.tsx.preview b/docs/data/data-grid/tree-data/TreeDataLazyLoading.tsx.preview deleted file mode 100644 index 527bdff70663..000000000000 --- a/docs/data/data-grid/tree-data/TreeDataLazyLoading.tsx.preview +++ /dev/null @@ -1,10 +0,0 @@ - \ No newline at end of file diff --git a/docs/data/data-grid/tree-data/tree-data.md b/docs/data/data-grid/tree-data/tree-data.md index 67e9e1ea6aff..66c7eeda991f 100644 --- a/docs/data/data-grid/tree-data/tree-data.md +++ b/docs/data/data-grid/tree-data/tree-data.md @@ -116,23 +116,9 @@ const invalidRows = [{ path: ['A'] }, { path: ['B'] }, { path: ['A', 'A'] }]; ::: -## Children lazy-loading 🚧 +## Children lazy-loading -:::warning -This feature isn't implemented yet. It's coming. - -👍 Upvote [issue #3377](https://github.com/mui/mui-x/issues/3377) 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 your current solution. -::: - -Alternatively, you can achieve a similar behavior by implementing this feature outside the component as shown below. -This implementation does not support every feature of the data grid but can be a good starting point for large datasets. - -The idea is to add a property `descendantCount` on the row and to use it instead of the internal grid state. -To do so, you need to override both the `renderCell` of the grouping column and to manually open the rows by listening to `rowExpansionChange` event. - -{{"demo": "TreeDataLazyLoading.js", "bg": "inline", "defaultCodeOpen": false}} +Check the [Server-side tree data](/x/react-data-grid/server-side-data/tree-data/) section for more information about lazy-loading tree data children. ## Full example From 2ae9563681758590f0f9155536ba928e1bb626f9 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Sun, 19 May 2024 18:12:00 +0500 Subject: [PATCH 22/90] Try msw as a network interceptor --- .../server-side-data/ServerSideDataGrid.js | 72 ++++- .../server-side-data/ServerSideDataGrid.tsx | 78 ++++- .../ServerSideDataGridNoCache.js | 7 +- .../ServerSideDataGridNoCache.tsx | 7 +- .../ServerSideDataGridWithSWR.js | 7 +- .../ServerSideDataGridWithSWR.tsx | 7 +- .../ServerSideErrorHandling.js | 3 +- .../ServerSideErrorHandling.tsx | 3 +- docs/data/data-grid/server-side-data/index.md | 24 +- docs/package.json | 5 + docs/public/mockServiceWorker.js | 281 ++++++++++++++++++ packages/x-data-grid-generator/package.json | 3 +- .../src/hooks/useDemoDataSource.ts | 73 ++++- .../x-data-grid/src/models/gridDataSource.ts | 2 +- pnpm-lock.yaml | 152 +++++++++- 15 files changed, 670 insertions(+), 54 deletions(-) create mode 100644 docs/public/mockServiceWorker.js diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGrid.js b/docs/data/data-grid/server-side-data/ServerSideDataGrid.js index 57c2ac091fbc..163481325a07 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGrid.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGrid.js @@ -1,28 +1,82 @@ import * as React from 'react'; import { DataGridPro } from '@mui/x-data-grid-pro'; +import CircularProgress from '@mui/material/CircularProgress'; +import { lighten, darken, alpha, styled } from '@mui/material/styles'; import { useDemoDataSource } from '@mui/x-data-grid-generator'; -function ServerSideDataGrid() { - const { getRows, columns, initialState } = useDemoDataSource( - {}, - { useCursorPagination: false }, +function getBorderColor(theme) { + if (theme.palette.mode === 'light') { + return lighten(alpha(theme.palette.divider, 1), 0.88); + } + return darken(alpha(theme.palette.divider, 1), 0.68); +} + +function getBorderRadius(theme) { + const radius = theme.shape.borderRadius; + return typeof radius === 'number' ? `${radius}px` : radius; +} + +const serverOptions = { useCursorPagination: false }; +const dataSetOptions = {}; + +const dataSource = { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent(JSON.stringify(params.paginationModel)), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), + }); + const serverResponse = await fetch( + `https://mui.com/x/api/x-grid?${urlParams.toString()}`, + ); + const getRowsResponse = await serverResponse.json(); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, +}; + +const Div = styled('div')(({ theme }) => ({ + height: 400, + width: '100%', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + border: `1px solid ${getBorderColor(theme)}`, + borderRadius: getBorderRadius(theme), +})); + +function LoadingSlate() { + return ( +
+ +
); +} + +function ServerSideDataGrid() { + const { + loading: serverConfiguring, + columns, + initialState, + } = useDemoDataSource(dataSetOptions, serverOptions); const initialStateWithPagination = React.useMemo( () => ({ ...initialState, pagination: { paginationModel: { pageSize: 10, page: 0 }, + rowCount: 0, }, }), [initialState], ); - const dataSource = React.useMemo(() => { - return { - getRows, - }; - }, [getRows]); + if (serverConfiguring) { + return ; + } return (
diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx index 57c2ac091fbc..b9802ba5f637 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx @@ -1,28 +1,86 @@ import * as React from 'react'; -import { DataGridPro } from '@mui/x-data-grid-pro'; +import { + DataGridPro, + GridDataSource, + GridGetRowsResponse, +} from '@mui/x-data-grid-pro'; +import CircularProgress from '@mui/material/CircularProgress'; +import { lighten, darken, alpha, styled, Theme } from '@mui/material/styles'; import { useDemoDataSource } from '@mui/x-data-grid-generator'; -function ServerSideDataGrid() { - const { getRows, columns, initialState } = useDemoDataSource( - {}, - { useCursorPagination: false }, +function getBorderColor(theme: Theme) { + if (theme.palette.mode === 'light') { + return lighten(alpha(theme.palette.divider, 1), 0.88); + } + return darken(alpha(theme.palette.divider, 1), 0.68); +} + +function getBorderRadius(theme: Theme) { + const radius = theme.shape.borderRadius; + return typeof radius === 'number' ? `${radius}px` : radius; +} + +const serverOptions = { useCursorPagination: false }; +const dataSetOptions = {}; + +const dataSource: GridDataSource = { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent(JSON.stringify(params.paginationModel)), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), + }); + const serverResponse = await fetch( + `https://mui.com/x/api/x-grid?${urlParams.toString()}`, + ); + const getRowsResponse = (await serverResponse.json()) as GridGetRowsResponse; + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, +}; + +const Div = styled('div')(({ theme }) => ({ + height: 400, + width: '100%', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + border: `1px solid ${getBorderColor(theme)}`, + borderRadius: getBorderRadius(theme), +})); + +function LoadingSlate() { + return ( +
+ +
); +} + +function ServerSideDataGrid() { + const { + loading: serverConfiguring, + columns, + initialState, + } = useDemoDataSource(dataSetOptions, serverOptions); const initialStateWithPagination = React.useMemo( () => ({ ...initialState, pagination: { paginationModel: { pageSize: 10, page: 0 }, + rowCount: 0, }, }), [initialState], ); - const dataSource = React.useMemo(() => { - return { - getRows, - }; - }, [getRows]); + if (serverConfiguring) { + return ; + } return (
diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js index 21331cbc7498..0f8e3186123a 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js @@ -4,10 +4,13 @@ import { useDemoDataSource } from '@mui/x-data-grid-generator'; const pageSizeOptions = [5, 10, 50]; +const serverOptions = { useCursorPagination: false }; +const dataSetOptions = {}; + export default function ServerSideDataGridNoCache() { const { getRows, columns, initialState } = useDemoDataSource( - {}, - { useCursorPagination: false }, + dataSetOptions, + serverOptions, ); const dataSource = React.useMemo(() => { diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx index 21331cbc7498..0f8e3186123a 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx @@ -4,10 +4,13 @@ import { useDemoDataSource } from '@mui/x-data-grid-generator'; const pageSizeOptions = [5, 10, 50]; +const serverOptions = { useCursorPagination: false }; +const dataSetOptions = {}; + export default function ServerSideDataGridNoCache() { const { getRows, columns, initialState } = useDemoDataSource( - {}, - { useCursorPagination: false }, + dataSetOptions, + serverOptions, ); const dataSource = React.useMemo(() => { diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js index 7e2b61d7b248..d5b59947da15 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js @@ -3,10 +3,13 @@ import { DataGridPro } from '@mui/x-data-grid-pro'; import { useDemoDataSource } from '@mui/x-data-grid-generator'; import { useSWRConfig } from 'swr'; +const serverOptions = { useCursorPagination: false }; +const dataSetOptions = {}; + function ServerSideDataGridWithSWR() { const { getRows, columns, initialState } = useDemoDataSource( - {}, - { useCursorPagination: false }, + dataSetOptions, + serverOptions, ); const { cache: swrCache } = useSWRConfig(); diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx index bc740df69cb7..bc96e5415621 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx @@ -7,10 +7,13 @@ import { import { useDemoDataSource } from '@mui/x-data-grid-generator'; import { useSWRConfig } from 'swr'; +const serverOptions = { useCursorPagination: false }; +const dataSetOptions = {}; + function ServerSideDataGridWithSWR() { const { getRows, columns, initialState } = useDemoDataSource( - {}, - { useCursorPagination: false }, + dataSetOptions, + serverOptions, ); const { cache: swrCache } = useSWRConfig(); diff --git a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js index 811a745f3091..2bb1025d5676 100644 --- a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js +++ b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js @@ -8,6 +8,7 @@ import { useDemoDataSource } from '@mui/x-data-grid-generator'; const pageSizeOptions = [5, 10, 50]; const serverOptions = { useCursorPagination: false }; +const datasetOptions = {}; function getBorderColor(theme) { if (theme.palette.mode === 'light') { @@ -44,7 +45,7 @@ export default function ServerSideErrorHandling() { const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); const { getRows, ...props } = useDemoDataSource( - {}, + datasetOptions, serverOptions, shouldRequestsFail, ); diff --git a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx index 1217f9ec400f..a351088c2621 100644 --- a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx @@ -13,6 +13,7 @@ import { useDemoDataSource } from '@mui/x-data-grid-generator'; const pageSizeOptions = [5, 10, 50]; const serverOptions = { useCursorPagination: false }; +const datasetOptions = {}; function getBorderColor(theme: Theme) { if (theme.palette.mode === 'light') { @@ -49,7 +50,7 @@ export default function ServerSideErrorHandling() { const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); const { getRows, ...props } = useDemoDataSource( - {}, + datasetOptions, serverOptions, shouldRequestsFail, ); diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index 652cb45c0af6..050cc53176d1 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -120,22 +120,32 @@ const customDataSource: GridDataSource = { The code has been significantly reduced, the need for managing the controlled states is removed, and data fetching logic is centralized. -The following demo uses a public testing API `dummyjson.com` to fetch products data with server side pagination. - -{{"demo": "ServerSideDataGridDummyJson.js", "bg": "inline"}} - :::info -For the following examples, a utility `useDemoDataSource` is used to simulate the server-side data fetching based on the package `@mui/x-data-grid-generator`. It returns a function `getRows` apart from other props that could be used to create a custom data source. +For the following examples, a utility `useDemoDataSource` is used to simulate the server-side data fetching based on the package `@mui/x-data-grid-generator`. It creates a dummy server based on the mock service worker. You can replace this with your actual server-side data fetching logic. ```tsx -const { getRows, columns, initialState } = useDemoDataSource( +const { loading, columns, initialState } = useDemoDataSource( dataSetOptions, serverOptions, ); const customDataSource: GridDataSource = { - getRows, + getRows: async (params: GridGetRowsParams): GetRowsResponse => { + const requestParams = new URLSearchParams({ + page: params.page.toString(), + pageSize: params.pageSize.toString(), + sortModel: JSON.stringify(params.sortModel), + filterModel: JSON.stringify(params.filterModel), + }); + const response = await fetch(`https://api-url?${requestParams.toString()}`); + const data = await response.json(); + + return { + rows: data.rows, + rowCount: data.totalCount, + }; + }, }; ; diff --git a/docs/package.json b/docs/package.json index 09d75e390dcd..5ebc1f3571a1 100644 --- a/docs/package.json +++ b/docs/package.json @@ -116,5 +116,10 @@ "@types/webpack-bundle-analyzer": "^4.7.0", "gm": "^1.25.0", "serve": "^14.2.3" + }, + "msw": { + "workerDirectory": [ + "public" + ] } } diff --git a/docs/public/mockServiceWorker.js b/docs/public/mockServiceWorker.js new file mode 100644 index 000000000000..55e469b28b71 --- /dev/null +++ b/docs/public/mockServiceWorker.js @@ -0,0 +1,281 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker. + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + * - Please do NOT serve this file on production. + */ + +const PACKAGE_VERSION = '2.3.0'; +const INTEGRITY_CHECKSUM = '26357c79639bfa20d64c0efca2a87423'; +const IS_MOCKED_RESPONSE = Symbol('isMockedResponse'); +const activeClientIds = new Set(); + +self.addEventListener('install', function () { + self.skipWaiting(); +}); + +self.addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()); +}); + +self.addEventListener('message', async function (event) { + const clientId = event.source.id; + + if (!clientId || !self.clients) { + return; + } + + const client = await self.clients.get(clientId); + + if (!client) { + return; + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }); + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }); + break; + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: { + packageVersion: PACKAGE_VERSION, + checksum: INTEGRITY_CHECKSUM, + }, + }); + break; + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId); + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: true, + }); + break; + } + + case 'MOCK_DEACTIVATE': { + activeClientIds.delete(clientId); + break; + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId); + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId; + }); + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister(); + } + + break; + } + } +}); + +self.addEventListener('fetch', function (event) { + const { request } = event; + + // Bypass navigation requests. + if (request.mode === 'navigate') { + return; + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + return; + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been deleted (still remains active until the next reload). + if (activeClientIds.size === 0) { + return; + } + + // Generate unique request ID. + const requestId = crypto.randomUUID(); + event.respondWith(handleRequest(event, requestId)); +}); + +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event); + const response = await getResponse(event, client, requestId); + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + (async function () { + const responseClone = response.clone(); + + sendToClient( + client, + { + type: 'RESPONSE', + payload: { + requestId, + isMockedResponse: IS_MOCKED_RESPONSE in response, + type: responseClone.type, + status: responseClone.status, + statusText: responseClone.statusText, + body: responseClone.body, + headers: Object.fromEntries(responseClone.headers.entries()), + }, + }, + [responseClone.body], + ); + })(); + } + + return response; +} + +// Resolve the main client for the given event. +// Client that issues a request doesn't necessarily equal the client +// that registered the worker. It's with the latter the worker should +// communicate with during the response resolving phase. +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId); + + if (client?.frameType === 'top-level') { + return client; + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }); + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible'; + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id); + }); +} + +async function getResponse(event, client, requestId) { + const { request } = event; + + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const requestClone = request.clone(); + + function passthrough() { + const headers = Object.fromEntries(requestClone.headers.entries()); + + // Remove internal MSW request header so the passthrough request + // complies with any potential CORS preflight checks on the server. + // Some servers forbid unknown request headers. + delete headers['x-msw-intention']; + + return fetch(requestClone, { headers }); + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough(); + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough(); + } + + // Notify the client that a request has been intercepted. + const requestBuffer = await request.arrayBuffer(); + const clientMessage = await sendToClient( + client, + { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + mode: request.mode, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: requestBuffer, + keepalive: request.keepalive, + }, + }, + [requestBuffer], + ); + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data); + } + + case 'PASSTHROUGH': { + return passthrough(); + } + } + + return passthrough(); +} + +function sendToClient(client, message, transferrables = []) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel(); + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error); + } + + resolve(event.data); + }; + + client.postMessage(message, [channel.port2].concat(transferrables.filter(Boolean))); + }); +} + +async function respondWithMock(response) { + // Setting response status code to 0 is a no-op. + // However, when responding with a "Response.error()", the produced Response + // instance will have status code set to 0. Since it's not possible to create + // a Response instance with status code 0, handle that use-case separately. + if (response.status === 0) { + return Response.error(); + } + + const mockedResponse = new Response(response.body, response); + + Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { + value: true, + enumerable: true, + }); + + return mockedResponse; +} diff --git a/packages/x-data-grid-generator/package.json b/packages/x-data-grid-generator/package.json index 5c6b26b2b943..4b2d5ffb266e 100644 --- a/packages/x-data-grid-generator/package.json +++ b/packages/x-data-grid-generator/package.json @@ -38,7 +38,8 @@ "@mui/x-data-grid-premium": "workspace:*", "chance": "^1.1.11", "clsx": "^2.1.1", - "lru-cache": "^7.18.3" + "lru-cache": "^7.18.3", + "msw": "^2.3.0" }, "devDependencies": { "@types/chance": "^1.1.6", diff --git a/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts b/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts index 4b491b5dcfac..416f7d117d1a 100644 --- a/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts +++ b/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts @@ -1,5 +1,7 @@ import * as React from 'react'; import LRUCache from 'lru-cache'; +import { http, HttpResponse } from 'msw'; +import { SetupWorkerApi } from 'msw/browser'; import { getGridDefaultColumnTypes, GridRowModel, @@ -26,13 +28,15 @@ import { DEFAULT_SERVER_OPTIONS, } from './serverUtils'; import type { ServerOptions } from './serverUtils'; -import { randomInt, random } from '../services'; +import { randomInt } from '../services'; const dataCache = new LRUCache({ max: 10, ttl: 60 * 5 * 1e3, // 5 minutes }); +export const API_URL = 'https://mui.com/x/api/x-grid'; + type UseDemoDataSourceResponse = { columns: GridColDef[]; initialState: GridInitialState; @@ -40,9 +44,25 @@ type UseDemoDataSourceResponse = { hasChildren?: (row: GridRowModel) => boolean; getChildrenCount?: (row: GridRowModel) => number; getRows: GridDataSource['getRows']; + loading: boolean; loadNewData: () => void; }; +function decodeParams(url: string): GridGetRowsParams { + const params = new URL(url).searchParams; + const decodedParams = {} as any; + const array = Array.from(params.entries()); + for (const [key, value] of array) { + try { + decodedParams[key] = JSON.parse(decodeURIComponent(value)); + } catch (e) { + decodedParams[key] = value; + } + } + + return decodedParams as GridGetRowsParams; +} + const getInitialState = (columns: GridColDefGenerator[], groupingField?: string) => { const columnVisibilityModel: GridColumnVisibilityModel = {}; columns.forEach((col) => { @@ -65,6 +85,7 @@ export const useDemoDataSource = ( serverOptions?: ServerOptions, shouldRequestsFail?: boolean, ): UseDemoDataSourceResponse => { + const [worker, setWorker] = React.useState(); const [data, setData] = React.useState(); const [index, setIndex] = React.useState(0); const shouldRequestsFailRef = React.useRef(shouldRequestsFail ?? false); @@ -230,6 +251,55 @@ export const useDemoDataSource = ( [data, columnsWithDefaultColDef, isTreeData, serverOptions], ); + const handlers = React.useMemo(() => { + if (!data) { + return []; + } + return [ + http.get(API_URL, async ({ request }) => { + if (!request.url) { + return HttpResponse.json({ error: 'Bad request.' }, { status: 400 }); + } + const params = decodeParams(request.url); + try { + const response = await getRows(params); + return HttpResponse.json(response); + } catch (error) { + return HttpResponse.json({ error }, { status: 500 }); + } + }), + ]; + }, [getRows, data]); + + React.useEffect(() => { + async function startServer() { + if (typeof window !== 'undefined') { + const { setupWorker } = require('msw/browser'); + if (!setupWorker) { + return; + } + const w = setupWorker(...handlers); + try { + await w.start({ quiet: true }); + setWorker(w); + } catch (e) { + console.error(e); + } + } + } + if (handlers.length > 0) { + startServer(); + } + return () => { + if (worker) { + setWorker((prev) => { + prev?.stop(); + return undefined; + }); + } + }; + }, [handlers]); + return { columns: columnsWithDefaultColDef, initialState, @@ -237,6 +307,7 @@ export const useDemoDataSource = ( hasChildren, getChildrenCount, getRows, + loading: !data || !worker, loadNewData: () => { setIndex((oldIndex) => oldIndex + 1); }, diff --git a/packages/x-data-grid/src/models/gridDataSource.ts b/packages/x-data-grid/src/models/gridDataSource.ts index e52641cf7d46..9f49692fbf88 100644 --- a/packages/x-data-grid/src/models/gridDataSource.ts +++ b/packages/x-data-grid/src/models/gridDataSource.ts @@ -24,7 +24,7 @@ export interface GridGetRowsParams { /** * List of grouped columns (only applicable with `rowGrouping`). */ - groupFields: GridColDef['field'][]; + groupFields?: GridColDef['field'][]; /** * Array of keys returned by `getGroupKey` of all the parent rows until the row for which the data is requested * `getGroupKey` prop must be implemented to use this. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7a6169e496c3..9401995909b7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -885,6 +885,9 @@ importers: lru-cache: specifier: ^7.18.3 version: 7.18.3 + msw: + specifier: ^2.3.0 + version: 2.3.0(typescript@5.4.5) react: specifier: ^17.0.0 || ^18.0.0 version: 18.2.0 @@ -2858,6 +2861,18 @@ packages: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: false + /@bundled-es-modules/cookie@2.0.0: + resolution: {integrity: sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==} + dependencies: + cookie: 0.5.0 + dev: false + + /@bundled-es-modules/statuses@1.0.1: + resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==} + dependencies: + statuses: 2.0.1 + dev: false + /@colors/colors@1.5.0: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} @@ -3395,6 +3410,43 @@ packages: engines: {node: '>=6.9.0'} dev: true + /@inquirer/confirm@3.1.7: + resolution: {integrity: sha512-BZjjj19W8gnh5UGFTdP5ZxpgMNRjy03Dzq3k28sB2MDlEUFrcyTkMEoGgvBmGpUw0vNBoCJkTcbHZ3e9tb+d+w==} + engines: {node: '>=18'} + dependencies: + '@inquirer/core': 8.2.0 + '@inquirer/type': 1.3.1 + dev: false + + /@inquirer/core@8.2.0: + resolution: {integrity: sha512-pexNF9j2orvMMTgoQ/uKOw8V6/R7x/sIDwRwXRhl4i0pPSh6paRzFehpFKpfMbqix1/+gzCekhYTmVbQpWkVjQ==} + engines: {node: '>=18'} + dependencies: + '@inquirer/figures': 1.0.1 + '@inquirer/type': 1.3.1 + '@types/mute-stream': 0.0.4 + '@types/node': 18.19.33 + '@types/wrap-ansi': 3.0.0 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + cli-spinners: 2.9.2 + cli-width: 4.1.0 + mute-stream: 1.0.0 + signal-exit: 4.1.0 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + dev: false + + /@inquirer/figures@1.0.1: + resolution: {integrity: sha512-mtup3wVKia3ZwULPHcbs4Mor8Voi+iIXEWD7wCNbIO6lYR62oPCTQyrddi5OMYVXHzeCSoneZwJuS8sBvlEwDw==} + engines: {node: '>=18'} + dev: false + + /@inquirer/type@1.3.1: + resolution: {integrity: sha512-Pe3PFccjPVJV1vtlfVvm9OnlbxqdnP5QcscFEFEnK5quChf1ufZtM0r8mR5ToWHMxZOh0s8o/qp9ANGRTo/DAw==} + engines: {node: '>=18'} + dev: false + /@isaacs/cliui@8.0.2: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -3560,6 +3612,11 @@ packages: semver: 5.7.2 dev: true + /@mswjs/cookies@1.1.0: + resolution: {integrity: sha512-0ZcCVQxifZmhwNBoQIrystCb+2sWBY2Zw8lpfJBPCHGCA/HWqehITeCRVIv4VMy8MPlaHo2w2pTHFV2pFfqKPw==} + engines: {node: '>=18'} + dev: false + /@mswjs/interceptors@0.27.2: resolution: {integrity: sha512-mE6PhwcoW70EX8+h+Y/4dLfHk33GFt/y5PzDJz56ktMyaVGFXMJ5BYLbUjdmGEABfE0x5GgAGyKbrbkYww2s3A==} engines: {node: '>=18'} @@ -3572,6 +3629,18 @@ packages: strict-event-emitter: 0.5.1 dev: true + /@mswjs/interceptors@0.29.1: + resolution: {integrity: sha512-3rDakgJZ77+RiQUuSK69t1F0m8BQKA8Vh5DCS5V0DWvNY67zob2JhhQrhCO0AKLGINTRSFd1tBaHcJTkhefoSw==} + engines: {node: '>=18'} + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.2 + strict-event-emitter: 0.5.1 + dev: false + /@mui/base@5.0.0-beta.40(@types/react@18.2.60)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==} engines: {node: '>=12.0.0'} @@ -4621,18 +4690,15 @@ packages: /@open-draft/deferred-promise@2.2.0: resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} - dev: true /@open-draft/logger@0.3.0: resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} dependencies: is-node-process: 1.2.0 outvariant: 1.4.2 - dev: true /@open-draft/until@2.1.0: resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} - dev: true /@opentelemetry/api-logs@0.50.0: resolution: {integrity: sha512-JdZuKrhOYggqOpUljAq4WWNi5nB10PmgoF0y2CvedLGXd0kSawb/UBnWT8gg1ND3bHCNHStAIVT0ELlxJJRqrA==} @@ -5220,6 +5286,10 @@ packages: resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} dev: true + /@types/cookie@0.6.0: + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + dev: false + /@types/cors@2.8.17: resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==} dependencies: @@ -5430,6 +5500,12 @@ packages: moment: 2.30.1 dev: true + /@types/mute-stream@0.0.4: + resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==} + dependencies: + '@types/node': 18.19.33 + dev: false + /@types/node@18.19.33: resolution: {integrity: sha512-NR9+KrpSajr2qBVp/Yt5TU/rp+b5Mayi3+OlMlcg2cVCfRmcG5PWZ7S4+MG9PZ5gWBoc9Pd0BKSRViuBCRPu0A==} dependencies: @@ -5550,6 +5626,10 @@ packages: resolution: {integrity: sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==} dev: true + /@types/statuses@2.0.5: + resolution: {integrity: sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==} + dev: false + /@types/stylis@4.2.0: resolution: {integrity: sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw==} dev: false @@ -5579,6 +5659,10 @@ packages: - webpack-cli dev: true + /@types/wrap-ansi@3.0.0: + resolution: {integrity: sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==} + dev: false + /@types/ws@7.4.7: resolution: {integrity: sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==} dependencies: @@ -6114,7 +6198,6 @@ packages: engines: {node: '>=8'} dependencies: type-fest: 0.21.3 - dev: true /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} @@ -7285,13 +7368,17 @@ packages: /cli-spinners@2.9.2: resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} engines: {node: '>=6'} - dev: true /cli-width@3.0.0: resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} engines: {node: '>= 10'} dev: true + /cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + dev: false + /client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} dev: false @@ -7684,7 +7771,6 @@ packages: /cookie@0.5.0: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} engines: {node: '>= 0.6'} - dev: true /copy-descriptor@0.1.1: resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==} @@ -10263,6 +10349,11 @@ packages: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} dev: true + /graphql@16.8.1: + resolution: {integrity: sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + dev: false + /gtoken@7.0.1: resolution: {integrity: sha512-KcFVtoP1CVFtQu0aSk3AyAt2og66PFhZAlkUOuWKwzMLoulHXG5W5wE5xAnHb+yl3/wEFoqGW7/cDGMU8igDZQ==} engines: {node: '>=14.0.0'} @@ -10402,6 +10493,10 @@ packages: hasBin: true dev: true + /headers-polyfill@4.0.3: + resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} + dev: false + /hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} dependencies: @@ -10997,7 +11092,6 @@ packages: /is-node-process@1.2.0: resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} - dev: true /is-number-object@1.0.7: resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} @@ -12868,6 +12962,37 @@ packages: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} dev: true + /msw@2.3.0(typescript@5.4.5): + resolution: {integrity: sha512-cDr1q/QTMzaWhY8n9lpGhceY209k29UZtdTgJ3P8Bzne3TSMchX2EM/ldvn4ATLOktpCefCU2gcEgzHc31GTPw==} + engines: {node: '>=18'} + hasBin: true + requiresBuild: true + peerDependencies: + typescript: '>= 4.7.x' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@bundled-es-modules/cookie': 2.0.0 + '@bundled-es-modules/statuses': 1.0.1 + '@inquirer/confirm': 3.1.7 + '@mswjs/cookies': 1.1.0 + '@mswjs/interceptors': 0.29.1 + '@open-draft/until': 2.1.0 + '@types/cookie': 0.6.0 + '@types/statuses': 2.0.5 + chalk: 4.1.2 + graphql: 16.8.1 + headers-polyfill: 4.0.3 + is-node-process: 1.2.0 + outvariant: 1.4.2 + path-to-regexp: 6.2.1 + strict-event-emitter: 0.5.1 + type-fest: 4.18.2 + typescript: 5.4.5 + yargs: 17.7.2 + dev: false + /multimatch@5.0.0: resolution: {integrity: sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==} engines: {node: '>=10'} @@ -12893,7 +13018,6 @@ packages: /mute-stream@1.0.0: resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - dev: true /mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -13651,7 +13775,6 @@ packages: /outvariant@1.4.2: resolution: {integrity: sha512-Ou3dJ6bA/UJ5GVHxah4LnqDwZRwAmWxrG3wtrHrbGnP4RnLCtA64A4F+ae7Y8ww660JaddSoArUR5HjipWSHAQ==} - dev: true /override-require@1.1.1: resolution: {integrity: sha512-eoJ9YWxFcXbrn2U8FKT6RV+/Kj7fiGAB1VvHzbYKt8xM5ZuKZgCGvnHzDxmreEjcBH28ejg5MiOH4iyY1mQnkg==} @@ -14039,7 +14162,6 @@ packages: /path-to-regexp@6.2.1: resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} - dev: true /path-type@3.0.0: resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} @@ -15741,7 +15863,6 @@ packages: /statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} - dev: true /stop-iteration-iterator@1.0.0: resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} @@ -15782,7 +15903,6 @@ packages: /strict-event-emitter@0.5.1: resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} - dev: true /strict-uri-encode@2.0.0: resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} @@ -16434,7 +16554,6 @@ packages: /type-fest@0.21.3: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} - dev: true /type-fest@0.4.1: resolution: {integrity: sha512-IwzA/LSfD2vC1/YDYMv/zHP4rDF1usCwllsDpbolT3D4fUepIO7f9K70jjmUewU/LmGUKJcwcVtDCpnKk4BPMw==} @@ -16456,6 +16575,11 @@ packages: engines: {node: '>=12.20'} dev: true + /type-fest@4.18.2: + resolution: {integrity: sha512-+suCYpfJLAe4OXS6+PPXjW3urOS4IoP9waSiLuXfLgqZODKw/aWwASvzqE886wA0kQgGy0mIWyhd87VpqIy6Xg==} + engines: {node: '>=16'} + dev: false + /type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -16522,7 +16646,6 @@ packages: resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} engines: {node: '>=14.17'} hasBin: true - dev: true /ua-parser-js@0.7.37: resolution: {integrity: sha512-xV8kqRKM+jhMvcHWUKthV9fNebIzrNy//2O9ZwWcfiBFR5f25XVZPLlEajk/sf3Ra15V92isyQqnIEXRDaZWEA==} @@ -17127,7 +17250,6 @@ packages: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - dev: true /wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} From 5867429f063d38d5abae98410ba38db495a309c7 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Sun, 19 May 2024 18:49:03 +0500 Subject: [PATCH 23/90] Update API url --- docs/data/data-grid/server-side-data/ServerSideDataGrid.js | 2 +- docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx | 2 +- packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGrid.js b/docs/data/data-grid/server-side-data/ServerSideDataGrid.js index 163481325a07..9a8c95570633 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGrid.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGrid.js @@ -28,7 +28,7 @@ const dataSource = { groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), }); const serverResponse = await fetch( - `https://mui.com/x/api/x-grid?${urlParams.toString()}`, + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, ); const getRowsResponse = await serverResponse.json(); return { diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx index b9802ba5f637..f7fbfa103cd1 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx @@ -32,7 +32,7 @@ const dataSource: GridDataSource = { groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), }); const serverResponse = await fetch( - `https://mui.com/x/api/x-grid?${urlParams.toString()}`, + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, ); const getRowsResponse = (await serverResponse.json()) as GridGetRowsResponse; return { diff --git a/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts b/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts index 416f7d117d1a..cf6340da2424 100644 --- a/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts +++ b/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts @@ -35,7 +35,7 @@ const dataCache = new LRUCache({ ttl: 60 * 5 * 1e3, // 5 minutes }); -export const API_URL = 'https://mui.com/x/api/x-grid'; +export const API_URL = 'https://mui.com/x/api/data-grid'; type UseDemoDataSourceResponse = { columns: GridColDef[]; From c698b09351dae6530798927e2109c8f7c0226a9b Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Sun, 19 May 2024 20:24:31 +0500 Subject: [PATCH 24/90] Add the mock server to other demos on overview --- .../server-side-data/LoadingSlate.js | 32 ++++++++ .../server-side-data/LoadingSlate.tsx | 32 ++++++++ .../server-side-data/LoadingSlate.tsx.preview | 3 + .../server-side-data/ServerSideDataGrid.js | 66 ++++------------ .../server-side-data/ServerSideDataGrid.tsx | 74 +++++------------- .../ServerSideDataGridDummyJson.js | 71 ----------------- .../ServerSideDataGridDummyJson.tsx | 77 ------------------- .../ServerSideDataGridDummyJson.tsx.preview | 15 ---- .../ServerSideDataGridNoCache.js | 48 ++++++++---- .../ServerSideDataGridNoCache.tsx | 50 ++++++++---- .../ServerSideDataGridNoCache.tsx.preview | 20 +++-- .../ServerSideDataGridWithSWR.js | 48 ++++++++---- .../ServerSideDataGridWithSWR.tsx | 49 ++++++++---- .../ServerSideErrorHandling.js | 61 ++++++++++----- .../ServerSideErrorHandling.tsx | 61 ++++++++++----- .../src/hooks/useDemoDataSource.ts | 19 ++++- 16 files changed, 349 insertions(+), 377 deletions(-) create mode 100644 docs/data/data-grid/server-side-data/LoadingSlate.js create mode 100644 docs/data/data-grid/server-side-data/LoadingSlate.tsx create mode 100644 docs/data/data-grid/server-side-data/LoadingSlate.tsx.preview delete mode 100644 docs/data/data-grid/server-side-data/ServerSideDataGridDummyJson.js delete mode 100644 docs/data/data-grid/server-side-data/ServerSideDataGridDummyJson.tsx delete mode 100644 docs/data/data-grid/server-side-data/ServerSideDataGridDummyJson.tsx.preview diff --git a/docs/data/data-grid/server-side-data/LoadingSlate.js b/docs/data/data-grid/server-side-data/LoadingSlate.js new file mode 100644 index 000000000000..ddfe4582dfe2 --- /dev/null +++ b/docs/data/data-grid/server-side-data/LoadingSlate.js @@ -0,0 +1,32 @@ +import CircularProgress from '@mui/material/CircularProgress'; +import { lighten, darken, alpha, styled } from '@mui/material/styles'; + +function getBorderColor(theme) { + if (theme.palette.mode === 'light') { + return lighten(alpha(theme.palette.divider, 1), 0.88); + } + return darken(alpha(theme.palette.divider, 1), 0.68); +} + +function getBorderRadius(theme) { + const radius = theme.shape.borderRadius; + return typeof radius === 'number' ? `${radius}px` : radius; +} + +const StyledDiv = styled('div')(({ theme }) => ({ + height: '100%', + width: '100%', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + border: `1px solid ${getBorderColor(theme)}`, + borderRadius: getBorderRadius(theme), +})); + +export default function LoadingSlate() { + return ( + + + + ); +} diff --git a/docs/data/data-grid/server-side-data/LoadingSlate.tsx b/docs/data/data-grid/server-side-data/LoadingSlate.tsx new file mode 100644 index 000000000000..120c6553033d --- /dev/null +++ b/docs/data/data-grid/server-side-data/LoadingSlate.tsx @@ -0,0 +1,32 @@ +import CircularProgress from '@mui/material/CircularProgress'; +import { lighten, darken, alpha, styled, Theme } from '@mui/material/styles'; + +function getBorderColor(theme: Theme) { + if (theme.palette.mode === 'light') { + return lighten(alpha(theme.palette.divider, 1), 0.88); + } + return darken(alpha(theme.palette.divider, 1), 0.68); +} + +function getBorderRadius(theme: Theme) { + const radius = theme.shape.borderRadius; + return typeof radius === 'number' ? `${radius}px` : radius; +} + +const StyledDiv = styled('div')(({ theme }) => ({ + height: '100%', + width: '100%', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + border: `1px solid ${getBorderColor(theme)}`, + borderRadius: getBorderRadius(theme), +})); + +export default function LoadingSlate() { + return ( + + + + ); +} diff --git a/docs/data/data-grid/server-side-data/LoadingSlate.tsx.preview b/docs/data/data-grid/server-side-data/LoadingSlate.tsx.preview new file mode 100644 index 000000000000..73a2fae54ba3 --- /dev/null +++ b/docs/data/data-grid/server-side-data/LoadingSlate.tsx.preview @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGrid.js b/docs/data/data-grid/server-side-data/ServerSideDataGrid.js index 9a8c95570633..c8360d7baef7 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGrid.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGrid.js @@ -1,22 +1,9 @@ import * as React from 'react'; import { DataGridPro } from '@mui/x-data-grid-pro'; -import CircularProgress from '@mui/material/CircularProgress'; -import { lighten, darken, alpha, styled } from '@mui/material/styles'; import { useDemoDataSource } from '@mui/x-data-grid-generator'; +import LoadingSlate from './LoadingSlate'; -function getBorderColor(theme) { - if (theme.palette.mode === 'light') { - return lighten(alpha(theme.palette.divider, 1), 0.88); - } - return darken(alpha(theme.palette.divider, 1), 0.68); -} - -function getBorderRadius(theme) { - const radius = theme.shape.borderRadius; - return typeof radius === 'number' ? `${radius}px` : radius; -} - -const serverOptions = { useCursorPagination: false }; +const serverOptions = { useCursorPagination: false, minDelay: 1000, maxDelay: 3000 }; const dataSetOptions = {}; const dataSource = { @@ -38,30 +25,11 @@ const dataSource = { }, }; -const Div = styled('div')(({ theme }) => ({ - height: 400, - width: '100%', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - border: `1px solid ${getBorderColor(theme)}`, - borderRadius: getBorderRadius(theme), -})); - -function LoadingSlate() { - return ( -
- -
- ); -} - function ServerSideDataGrid() { - const { - loading: serverConfiguring, - columns, - initialState, - } = useDemoDataSource(dataSetOptions, serverOptions); + const { isInitialized, columns, initialState } = useDemoDataSource( + dataSetOptions, + serverOptions, + ); const initialStateWithPagination = React.useMemo( () => ({ @@ -74,19 +42,19 @@ function ServerSideDataGrid() { [initialState], ); - if (serverConfiguring) { - return ; - } - return (
- + {isInitialized ? ( + + ) : ( + + )}
); } diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx index f7fbfa103cd1..3e8377a70086 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx @@ -1,26 +1,9 @@ import * as React from 'react'; -import { - DataGridPro, - GridDataSource, - GridGetRowsResponse, -} from '@mui/x-data-grid-pro'; -import CircularProgress from '@mui/material/CircularProgress'; -import { lighten, darken, alpha, styled, Theme } from '@mui/material/styles'; +import { DataGridPro, GridDataSource } from '@mui/x-data-grid-pro'; import { useDemoDataSource } from '@mui/x-data-grid-generator'; +import LoadingSlate from './LoadingSlate'; -function getBorderColor(theme: Theme) { - if (theme.palette.mode === 'light') { - return lighten(alpha(theme.palette.divider, 1), 0.88); - } - return darken(alpha(theme.palette.divider, 1), 0.68); -} - -function getBorderRadius(theme: Theme) { - const radius = theme.shape.borderRadius; - return typeof radius === 'number' ? `${radius}px` : radius; -} - -const serverOptions = { useCursorPagination: false }; +const serverOptions = { useCursorPagination: false, minDelay: 1000, maxDelay: 3000 }; const dataSetOptions = {}; const dataSource: GridDataSource = { @@ -34,7 +17,7 @@ const dataSource: GridDataSource = { const serverResponse = await fetch( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, ); - const getRowsResponse = (await serverResponse.json()) as GridGetRowsResponse; + const getRowsResponse = await serverResponse.json(); return { rows: getRowsResponse.rows, rowCount: getRowsResponse.rowCount, @@ -42,30 +25,11 @@ const dataSource: GridDataSource = { }, }; -const Div = styled('div')(({ theme }) => ({ - height: 400, - width: '100%', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - border: `1px solid ${getBorderColor(theme)}`, - borderRadius: getBorderRadius(theme), -})); - -function LoadingSlate() { - return ( -
- -
- ); -} - function ServerSideDataGrid() { - const { - loading: serverConfiguring, - columns, - initialState, - } = useDemoDataSource(dataSetOptions, serverOptions); + const { isInitialized, columns, initialState } = useDemoDataSource( + dataSetOptions, + serverOptions, + ); const initialStateWithPagination = React.useMemo( () => ({ @@ -78,19 +42,19 @@ function ServerSideDataGrid() { [initialState], ); - if (serverConfiguring) { - return ; - } - return (
- + {isInitialized ? ( + + ) : ( + + )}
); } diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridDummyJson.js b/docs/data/data-grid/server-side-data/ServerSideDataGridDummyJson.js deleted file mode 100644 index 3ff558cdb400..000000000000 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridDummyJson.js +++ /dev/null @@ -1,71 +0,0 @@ -import * as React from 'react'; -import { DataGridPro } from '@mui/x-data-grid-pro'; - -function ThumbnailCell(props) { - if (!props.value) { - return null; - } - const url = props.value; - return ( - {props.row.product} - ); -} - -const columns = [ - { - field: 'thumbnail', - headerName: 'Preview', - width: 120, - renderCell: ThumbnailCell, - }, - { field: 'title', headerName: 'Product', width: 200 }, - { field: 'description', headerName: 'Description', width: 200 }, - { field: 'brand', headerName: 'Brand', width: 150 }, - { - field: 'price', - type: 'number', - headerName: 'Price', - width: 80, - valueFormatter: (value) => `$${value}`, - }, -]; - -const dataSource = { - getRows: async (params) => { - const { pageSize, page } = params.paginationModel; - const response = await fetch( - `https://dummyjson.com/products?limit=${pageSize}&skip=${pageSize * page}`, - ); - const data = await response.json(); - return { - rows: data.products, - rowCount: data.total, - }; - }, -}; - -export default function ServerSideDataGridDummyJson() { - return ( -
- 100} - pinnedColumns={{ left: ['thumbnail'], right: ['price'] }} - disableColumnSorting - disableColumnFilter - /> -
- ); -} diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridDummyJson.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridDummyJson.tsx deleted file mode 100644 index 89174796b8a5..000000000000 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridDummyJson.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import * as React from 'react'; -import { - DataGridPro, - GridCellParams, - GridColDef, - GridDataSource, - GridGetRowsParams, -} from '@mui/x-data-grid-pro'; - -function ThumbnailCell(props: GridCellParams) { - if (!props.value) { - return null; - } - const url = props.value as string; - return ( - {props.row.product} - ); -} - -const columns: GridColDef[] = [ - { - field: 'thumbnail', - headerName: 'Preview', - width: 120, - renderCell: ThumbnailCell, - }, - { field: 'title', headerName: 'Product', width: 200 }, - { field: 'description', headerName: 'Description', width: 200 }, - { field: 'brand', headerName: 'Brand', width: 150 }, - { - field: 'price', - type: 'number', - headerName: 'Price', - width: 80, - valueFormatter: (value) => `$${value}`, - }, -]; - -const dataSource: GridDataSource = { - getRows: async (params: GridGetRowsParams) => { - const { pageSize, page } = params.paginationModel; - const response = await fetch( - `https://dummyjson.com/products?limit=${pageSize}&skip=${pageSize * page}`, - ); - const data = await response.json(); - return { - rows: data.products, - rowCount: data.total, - }; - }, -}; - -export default function ServerSideDataGridDummyJson() { - return ( -
- 100} - pinnedColumns={{ left: ['thumbnail'], right: ['price'] }} - disableColumnSorting - disableColumnFilter - /> -
- ); -} diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridDummyJson.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideDataGridDummyJson.tsx.preview deleted file mode 100644 index 71c2c1343d34..000000000000 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridDummyJson.tsx.preview +++ /dev/null @@ -1,15 +0,0 @@ - 100} - pinnedColumns={{ left: ['thumbnail'], right: ['price'] }} - disableColumnSorting - disableColumnFilter -/> \ No newline at end of file diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js index 0f8e3186123a..b815aa143d08 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js @@ -1,24 +1,38 @@ import * as React from 'react'; import { DataGridPro } from '@mui/x-data-grid-pro'; import { useDemoDataSource } from '@mui/x-data-grid-generator'; +import LoadingSlate from './LoadingSlate'; const pageSizeOptions = [5, 10, 50]; const serverOptions = { useCursorPagination: false }; const dataSetOptions = {}; +const dataSource = { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent(JSON.stringify(params.paginationModel)), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), + }); + const serverResponse = await fetch( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + const getRowsResponse = await serverResponse.json(); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, +}; + export default function ServerSideDataGridNoCache() { - const { getRows, columns, initialState } = useDemoDataSource( + const { isInitialized, columns, initialState } = useDemoDataSource( dataSetOptions, serverOptions, ); - const dataSource = React.useMemo(() => { - return { - getRows, - }; - }, [getRows]); - const initialStateWithPagination = React.useMemo( () => ({ ...initialState, @@ -31,14 +45,18 @@ export default function ServerSideDataGridNoCache() { return (
- + {isInitialized ? ( + + ) : ( + + )}
); } diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx index 0f8e3186123a..0e128cbdcbd1 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx @@ -1,24 +1,38 @@ import * as React from 'react'; -import { DataGridPro } from '@mui/x-data-grid-pro'; +import { DataGridPro, GridDataSource } from '@mui/x-data-grid-pro'; import { useDemoDataSource } from '@mui/x-data-grid-generator'; +import LoadingSlate from './LoadingSlate'; const pageSizeOptions = [5, 10, 50]; const serverOptions = { useCursorPagination: false }; const dataSetOptions = {}; +const dataSource: GridDataSource = { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent(JSON.stringify(params.paginationModel)), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), + }); + const serverResponse = await fetch( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + const getRowsResponse = await serverResponse.json(); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, +}; + export default function ServerSideDataGridNoCache() { - const { getRows, columns, initialState } = useDemoDataSource( + const { isInitialized, columns, initialState } = useDemoDataSource( dataSetOptions, serverOptions, ); - const dataSource = React.useMemo(() => { - return { - getRows, - }; - }, [getRows]); - const initialStateWithPagination = React.useMemo( () => ({ ...initialState, @@ -31,14 +45,18 @@ export default function ServerSideDataGridNoCache() { return (
- + {isInitialized ? ( + + ) : ( + + )}
); } diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx.preview index 2dfc61d6c612..19ffc24ca7c9 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx.preview +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx.preview @@ -1,8 +1,12 @@ - \ No newline at end of file +{isInitialized ? ( + +) : ( + +)} \ No newline at end of file diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js index d5b59947da15..6a9cdeec524d 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js @@ -2,12 +2,32 @@ import * as React from 'react'; import { DataGridPro } from '@mui/x-data-grid-pro'; import { useDemoDataSource } from '@mui/x-data-grid-generator'; import { useSWRConfig } from 'swr'; +import LoadingSlate from './LoadingSlate'; const serverOptions = { useCursorPagination: false }; const dataSetOptions = {}; +const dataSource = { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent(JSON.stringify(params.paginationModel)), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), + }); + const serverResponse = await fetch( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + const getRowsResponse = await serverResponse.json(); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, +}; + function ServerSideDataGridWithSWR() { - const { getRows, columns, initialState } = useDemoDataSource( + const { isInitialized, columns, initialState } = useDemoDataSource( dataSetOptions, serverOptions, ); @@ -42,22 +62,20 @@ function ServerSideDataGridWithSWR() { [initialState], ); - const dataSource = React.useMemo(() => { - return { - getRows, - }; - }, [getRows]); - return (
- + {isInitialized ? ( + + ) : ( + + )}
); } diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx index bc96e5415621..bd5805c8e49e 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx @@ -3,15 +3,36 @@ import { DataGridPro, GridGetRowsParams, GridGetRowsResponse, + GridDataSource, } from '@mui/x-data-grid-pro'; import { useDemoDataSource } from '@mui/x-data-grid-generator'; import { useSWRConfig } from 'swr'; +import LoadingSlate from './LoadingSlate'; const serverOptions = { useCursorPagination: false }; const dataSetOptions = {}; +const dataSource: GridDataSource = { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent(JSON.stringify(params.paginationModel)), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), + }); + const serverResponse = await fetch( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + const getRowsResponse = await serverResponse.json(); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, +}; + function ServerSideDataGridWithSWR() { - const { getRows, columns, initialState } = useDemoDataSource( + const { isInitialized, columns, initialState } = useDemoDataSource( dataSetOptions, serverOptions, ); @@ -46,22 +67,20 @@ function ServerSideDataGridWithSWR() { [initialState], ); - const dataSource = React.useMemo(() => { - return { - getRows, - }; - }, [getRows]); - return (
- + {isInitialized ? ( + + ) : ( + + )}
); } diff --git a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js index 2bb1025d5676..22426cff7d16 100644 --- a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js +++ b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js @@ -5,11 +5,35 @@ import Checkbox from '@mui/material/Checkbox'; import FormControlLabel from '@mui/material/FormControlLabel'; import { alpha, styled, darken, lighten } from '@mui/material/styles'; import { useDemoDataSource } from '@mui/x-data-grid-generator'; +import LoadingSlate from './LoadingSlate'; const pageSizeOptions = [5, 10, 50]; const serverOptions = { useCursorPagination: false }; const datasetOptions = {}; +const dataSource = { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent(JSON.stringify(params.paginationModel)), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), + }); + const serverResponse = await fetch( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + if (!serverResponse.ok) { + const body = await serverResponse.json(); + throw new Error(body.error ?? 'An error occurred while fetching the data'); + } + const getRowsResponse = await serverResponse.json(); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, +}; + function getBorderColor(theme) { if (theme.palette.mode === 'light') { return lighten(alpha(theme.palette.divider, 1), 0.88); @@ -44,18 +68,12 @@ export default function ServerSideErrorHandling() { const [error, setError] = React.useState(); const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); - const { getRows, ...props } = useDemoDataSource( + const { isInitialized, ...props } = useDemoDataSource( datasetOptions, serverOptions, shouldRequestsFail, ); - const dataSource = React.useMemo(() => { - return { - getRows, - }; - }, [getRows]); - const initialState = React.useMemo( () => ({ ...props.initialState, @@ -75,7 +93,7 @@ export default function ServerSideErrorHandling() {
- setError(e.message)} - disableServerSideCache - apiRef={apiRef} - pagination - pageSizeOptions={pageSizeOptions} - initialState={initialState} - slots={{ toolbar: GridToolbar }} - /> + {isInitialized ? ( + setError(e.message)} + disableServerSideCache + apiRef={apiRef} + pagination + pageSizeOptions={pageSizeOptions} + initialState={initialState} + slots={{ toolbar: GridToolbar }} + /> + ) : ( + + )} + {error && }
diff --git a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx index a351088c2621..973b4099e5b4 100644 --- a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx @@ -4,17 +4,42 @@ import { useGridApiRef, GridInitialState, GridToolbar, + GridDataSource, } from '@mui/x-data-grid-pro'; import Button from '@mui/material/Button'; import Checkbox from '@mui/material/Checkbox'; import FormControlLabel from '@mui/material/FormControlLabel'; import { alpha, styled, darken, lighten, Theme } from '@mui/material/styles'; import { useDemoDataSource } from '@mui/x-data-grid-generator'; +import LoadingSlate from './LoadingSlate'; const pageSizeOptions = [5, 10, 50]; const serverOptions = { useCursorPagination: false }; const datasetOptions = {}; +const dataSource: GridDataSource = { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent(JSON.stringify(params.paginationModel)), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), + }); + const serverResponse = await fetch( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + if (!serverResponse.ok) { + const body = await serverResponse.json(); + throw new Error(body.error ?? 'An error occurred while fetching the data'); + } + const getRowsResponse = await serverResponse.json(); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, +}; + function getBorderColor(theme: Theme) { if (theme.palette.mode === 'light') { return lighten(alpha(theme.palette.divider, 1), 0.88); @@ -49,18 +74,12 @@ export default function ServerSideErrorHandling() { const [error, setError] = React.useState(); const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); - const { getRows, ...props } = useDemoDataSource( + const { isInitialized, ...props } = useDemoDataSource( datasetOptions, serverOptions, shouldRequestsFail, ); - const dataSource = React.useMemo(() => { - return { - getRows, - }; - }, [getRows]); - const initialState: GridInitialState = React.useMemo( () => ({ ...props.initialState, @@ -80,7 +99,7 @@ export default function ServerSideErrorHandling() {
- setError(e.message)} - disableServerSideCache - apiRef={apiRef} - pagination - pageSizeOptions={pageSizeOptions} - initialState={initialState} - slots={{ toolbar: GridToolbar }} - /> + {isInitialized ? ( + setError(e.message)} + disableServerSideCache + apiRef={apiRef} + pagination + pageSizeOptions={pageSizeOptions} + initialState={initialState} + slots={{ toolbar: GridToolbar }} + /> + ) : ( + + )} {error && }
diff --git a/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts b/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts index cf6340da2424..6790b879f933 100644 --- a/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts +++ b/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts @@ -44,7 +44,7 @@ type UseDemoDataSourceResponse = { hasChildren?: (row: GridRowModel) => boolean; getChildrenCount?: (row: GridRowModel) => number; getRows: GridDataSource['getRows']; - loading: boolean; + isInitialized: boolean; loadNewData: () => void; }; @@ -85,6 +85,7 @@ export const useDemoDataSource = ( serverOptions?: ServerOptions, shouldRequestsFail?: boolean, ): UseDemoDataSourceResponse => { + const [isInitialized, setIsInitialized] = React.useState(false); const [worker, setWorker] = React.useState(); const [data, setData] = React.useState(); const [index, setIndex] = React.useState(0); @@ -262,6 +263,12 @@ export const useDemoDataSource = ( } const params = decodeParams(request.url); try { + if (shouldRequestsFail) { + const serverOptionsWithDefault = { ...DEFAULT_SERVER_OPTIONS, ...serverOptions }; + const { minDelay, maxDelay } = serverOptionsWithDefault; + const delay = randomInt(minDelay, maxDelay); + return HttpResponse.json({ error: 'Could not fetch the data' }, { status: 500 }); + } const response = await getRows(params); return HttpResponse.json(response); } catch (error) { @@ -269,7 +276,7 @@ export const useDemoDataSource = ( } }), ]; - }, [getRows, data]); + }, [getRows, data, shouldRequestsFail]); React.useEffect(() => { async function startServer() { @@ -300,6 +307,12 @@ export const useDemoDataSource = ( }; }, [handlers]); + React.useEffect(() => { + if (data && worker && !isInitialized) { + setIsInitialized(true); + } + }, [data, worker, isInitialized]); + return { columns: columnsWithDefaultColDef, initialState, @@ -307,7 +320,7 @@ export const useDemoDataSource = ( hasChildren, getChildrenCount, getRows, - loading: !data || !worker, + isInitialized, loadNewData: () => { setIndex((oldIndex) => oldIndex + 1); }, From 12134166b8958141ec5eb4fed2075fbe65404fe9 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Sun, 19 May 2024 20:24:49 +0500 Subject: [PATCH 25/90] Add the mock server to the tree data example --- .../server-side-data/ServerSideTreeData.js | 56 +++++++++++------- .../server-side-data/ServerSideTreeData.tsx | 57 ++++++++++++------- .../ServerSideTreeData.tsx.preview | 14 ----- 3 files changed, 75 insertions(+), 52 deletions(-) delete mode 100644 docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.js b/docs/data/data-grid/server-side-data/ServerSideTreeData.js index ca6b4eaf5da2..13ddb1a6379f 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.js @@ -2,24 +2,38 @@ import * as React from 'react'; import { DataGridPro, useGridApiRef, GridToolbar } from '@mui/x-data-grid-pro'; import Button from '@mui/material/Button'; import { useDemoDataSource } from '@mui/x-data-grid-generator'; +import LoadingSlate from './LoadingSlate'; const pageSizeOptions = [5, 10, 50]; +const dataSource = { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent(JSON.stringify(params.paginationModel)), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), + }); + const serverResponse = await fetch( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + const getRowsResponse = await serverResponse.json(); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, +}; + export default function ServerSideTreeData() { const apiRef = useGridApiRef(); - const { getRows, ...props } = useDemoDataSource({ + const { isInitialized, ...props } = useDemoDataSource({ dataSet: 'Employee', rowLength: 1000, treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, }); - const dataSource = React.useMemo(() => { - return { - getRows, - }; - }, [getRows]); - const initialState = React.useMemo( () => ({ ...props.initialState, @@ -35,19 +49,23 @@ export default function ServerSideTreeData() { return (
- +
- + {isInitialized ? ( + + ) : ( + + )}
); diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx index 3ed8cf1c3da7..3ef6bee77bb1 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx @@ -4,27 +4,42 @@ import { useGridApiRef, GridInitialState, GridToolbar, + GridDataSource, } from '@mui/x-data-grid-pro'; import Button from '@mui/material/Button'; import { useDemoDataSource } from '@mui/x-data-grid-generator'; +import LoadingSlate from './LoadingSlate'; const pageSizeOptions = [5, 10, 50]; +const dataSource: GridDataSource = { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent(JSON.stringify(params.paginationModel)), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), + }); + const serverResponse = await fetch( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + const getRowsResponse = await serverResponse.json(); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, +}; + export default function ServerSideTreeData() { const apiRef = useGridApiRef(); - const { getRows, ...props } = useDemoDataSource({ + const { isInitialized, ...props } = useDemoDataSource({ dataSet: 'Employee', rowLength: 1000, treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, }); - const dataSource = React.useMemo(() => { - return { - getRows, - }; - }, [getRows]); - const initialState: GridInitialState = React.useMemo( () => ({ ...props.initialState, @@ -40,19 +55,23 @@ export default function ServerSideTreeData() { return (
- +
- + {isInitialized ? ( + + ) : ( + + )}
); diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview deleted file mode 100644 index c5dca894d53f..000000000000 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview +++ /dev/null @@ -1,14 +0,0 @@ - -
- -
\ No newline at end of file From acc47b42ebc61cf8dd74725f11cdad7901466b74 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 20 May 2024 14:02:05 +0500 Subject: [PATCH 26/90] Remove msw from many demos due to limitation + some refactors --- .../server-side-data/ServerSideDataGrid.js | 6 +- .../server-side-data/ServerSideDataGrid.tsx | 7 +- .../ServerSideDataGridNoCache.js | 45 ++--- .../ServerSideDataGridNoCache.tsx | 45 ++--- .../ServerSideDataGridWithSWR.js | 46 +++--- .../ServerSideDataGridWithSWR.tsx | 46 +++--- .../ServerSideErrorHandling.js | 49 +++--- .../ServerSideErrorHandling.tsx | 49 +++--- .../server-side-data/ServerSideTreeData.js | 17 +- .../server-side-data/ServerSideTreeData.tsx | 17 +- .../ServerSideTreeDataCustomCache.js | 60 ++++--- .../ServerSideTreeDataCustomCache.tsx | 61 ++++--- .../ServerSideTreeDataCustomCache.tsx.preview | 15 -- .../ServerSideTreeDataErrorHandling.js | 148 ++++++++++------- .../ServerSideTreeDataErrorHandling.tsx | 155 +++++++++++------- .../ServerSideTreeDataGroupExpansion.js | 51 ++++-- .../ServerSideTreeDataGroupExpansion.tsx | 52 ++++-- ...rverSideTreeDataGroupExpansion.tsx.preview | 15 -- docs/data/data-grid/server-side-data/index.md | 37 +---- .../data-grid/server-side-data/tree-data.md | 2 +- .../x-data-grid-generator/src/hooks/index.ts | 2 +- .../src/hooks/serverUtils.ts | 2 - ...{useDemoDataSource.ts => useMockServer.ts} | 98 ++++++----- 23 files changed, 574 insertions(+), 451 deletions(-) delete mode 100644 docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx.preview delete mode 100644 docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx.preview rename packages/x-data-grid-generator/src/hooks/{useDemoDataSource.ts => useMockServer.ts} (79%) diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGrid.js b/docs/data/data-grid/server-side-data/ServerSideDataGrid.js index c8360d7baef7..d8f937a0ab9d 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGrid.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGrid.js @@ -1,9 +1,9 @@ import * as React from 'react'; import { DataGridPro } from '@mui/x-data-grid-pro'; -import { useDemoDataSource } from '@mui/x-data-grid-generator'; +import { useMockServer } from '@mui/x-data-grid-generator'; import LoadingSlate from './LoadingSlate'; -const serverOptions = { useCursorPagination: false, minDelay: 1000, maxDelay: 3000 }; +const serverOptions = { useCursorPagination: false, startServer: true }; const dataSetOptions = {}; const dataSource = { @@ -26,7 +26,7 @@ const dataSource = { }; function ServerSideDataGrid() { - const { isInitialized, columns, initialState } = useDemoDataSource( + const { isInitialized, columns, initialState } = useMockServer( dataSetOptions, serverOptions, ); diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx index 3e8377a70086..5c01f22d792e 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; import { DataGridPro, GridDataSource } from '@mui/x-data-grid-pro'; -import { useDemoDataSource } from '@mui/x-data-grid-generator'; +import { useMockServer } from '@mui/x-data-grid-generator'; import LoadingSlate from './LoadingSlate'; -const serverOptions = { useCursorPagination: false, minDelay: 1000, maxDelay: 3000 }; +const serverOptions = { useCursorPagination: false, startServer: true }; const dataSetOptions = {}; const dataSource: GridDataSource = { @@ -12,7 +12,6 @@ const dataSource: GridDataSource = { paginationModel: encodeURIComponent(JSON.stringify(params.paginationModel)), filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), - groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), }); const serverResponse = await fetch( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, @@ -26,7 +25,7 @@ const dataSource: GridDataSource = { }; function ServerSideDataGrid() { - const { isInitialized, columns, initialState } = useDemoDataSource( + const { isInitialized, columns, initialState } = useMockServer( dataSetOptions, serverOptions, ); diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js index b815aa143d08..f4dd8ced6f69 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { DataGridPro } from '@mui/x-data-grid-pro'; -import { useDemoDataSource } from '@mui/x-data-grid-generator'; +import { useMockServer } from '@mui/x-data-grid-generator'; import LoadingSlate from './LoadingSlate'; const pageSizeOptions = [5, 10, 50]; @@ -8,31 +8,34 @@ const pageSizeOptions = [5, 10, 50]; const serverOptions = { useCursorPagination: false }; const dataSetOptions = {}; -const dataSource = { - getRows: async (params) => { - const urlParams = new URLSearchParams({ - paginationModel: encodeURIComponent(JSON.stringify(params.paginationModel)), - filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), - sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), - groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), - }); - const serverResponse = await fetch( - `https://mui.com/x/api/data-grid?${urlParams.toString()}`, - ); - const getRowsResponse = await serverResponse.json(); - return { - rows: getRowsResponse.rows, - rowCount: getRowsResponse.rowCount, - }; - }, -}; - export default function ServerSideDataGridNoCache() { - const { isInitialized, columns, initialState } = useDemoDataSource( + const { isInitialized, fetchRows, columns, initialState } = useMockServer( dataSetOptions, serverOptions, ); + const dataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent( + JSON.stringify(params.paginationModel), + ), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + }), + [fetchRows], + ); + const initialStateWithPagination = React.useMemo( () => ({ ...initialState, diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx index 0e128cbdcbd1..7db1605e6d2b 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { DataGridPro, GridDataSource } from '@mui/x-data-grid-pro'; -import { useDemoDataSource } from '@mui/x-data-grid-generator'; +import { useMockServer } from '@mui/x-data-grid-generator'; import LoadingSlate from './LoadingSlate'; const pageSizeOptions = [5, 10, 50]; @@ -8,31 +8,34 @@ const pageSizeOptions = [5, 10, 50]; const serverOptions = { useCursorPagination: false }; const dataSetOptions = {}; -const dataSource: GridDataSource = { - getRows: async (params) => { - const urlParams = new URLSearchParams({ - paginationModel: encodeURIComponent(JSON.stringify(params.paginationModel)), - filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), - sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), - groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), - }); - const serverResponse = await fetch( - `https://mui.com/x/api/data-grid?${urlParams.toString()}`, - ); - const getRowsResponse = await serverResponse.json(); - return { - rows: getRowsResponse.rows, - rowCount: getRowsResponse.rowCount, - }; - }, -}; - export default function ServerSideDataGridNoCache() { - const { isInitialized, columns, initialState } = useDemoDataSource( + const { isInitialized, fetchRows, columns, initialState } = useMockServer( dataSetOptions, serverOptions, ); + const dataSource: GridDataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent( + JSON.stringify(params.paginationModel), + ), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + }), + [fetchRows], + ); + const initialStateWithPagination = React.useMemo( () => ({ ...initialState, diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js index 6a9cdeec524d..d0075d0ea6c3 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js @@ -1,36 +1,40 @@ import * as React from 'react'; import { DataGridPro } from '@mui/x-data-grid-pro'; -import { useDemoDataSource } from '@mui/x-data-grid-generator'; +import { useMockServer } from '@mui/x-data-grid-generator'; import { useSWRConfig } from 'swr'; import LoadingSlate from './LoadingSlate'; const serverOptions = { useCursorPagination: false }; const dataSetOptions = {}; -const dataSource = { - getRows: async (params) => { - const urlParams = new URLSearchParams({ - paginationModel: encodeURIComponent(JSON.stringify(params.paginationModel)), - filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), - sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), - groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), - }); - const serverResponse = await fetch( - `https://mui.com/x/api/data-grid?${urlParams.toString()}`, - ); - const getRowsResponse = await serverResponse.json(); - return { - rows: getRowsResponse.rows, - rowCount: getRowsResponse.rowCount, - }; - }, -}; - function ServerSideDataGridWithSWR() { - const { isInitialized, columns, initialState } = useDemoDataSource( + const { isInitialized, fetchRows, columns, initialState } = useMockServer( dataSetOptions, serverOptions, ); + + const dataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent( + JSON.stringify(params.paginationModel), + ), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + }), + [fetchRows], + ); + const { cache: swrCache } = useSWRConfig(); const cache = React.useMemo( diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx index bd5805c8e49e..18d272c08c21 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx @@ -5,37 +5,41 @@ import { GridGetRowsResponse, GridDataSource, } from '@mui/x-data-grid-pro'; -import { useDemoDataSource } from '@mui/x-data-grid-generator'; +import { useMockServer } from '@mui/x-data-grid-generator'; import { useSWRConfig } from 'swr'; import LoadingSlate from './LoadingSlate'; const serverOptions = { useCursorPagination: false }; const dataSetOptions = {}; -const dataSource: GridDataSource = { - getRows: async (params) => { - const urlParams = new URLSearchParams({ - paginationModel: encodeURIComponent(JSON.stringify(params.paginationModel)), - filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), - sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), - groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), - }); - const serverResponse = await fetch( - `https://mui.com/x/api/data-grid?${urlParams.toString()}`, - ); - const getRowsResponse = await serverResponse.json(); - return { - rows: getRowsResponse.rows, - rowCount: getRowsResponse.rowCount, - }; - }, -}; - function ServerSideDataGridWithSWR() { - const { isInitialized, columns, initialState } = useDemoDataSource( + const { isInitialized, fetchRows, columns, initialState } = useMockServer( dataSetOptions, serverOptions, ); + + const dataSource: GridDataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent( + JSON.stringify(params.paginationModel), + ), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + }), + [fetchRows], + ); + const { cache: swrCache } = useSWRConfig(); const cache = React.useMemo( diff --git a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js index 22426cff7d16..da78974d631b 100644 --- a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js +++ b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js @@ -4,36 +4,13 @@ import Button from '@mui/material/Button'; import Checkbox from '@mui/material/Checkbox'; import FormControlLabel from '@mui/material/FormControlLabel'; import { alpha, styled, darken, lighten } from '@mui/material/styles'; -import { useDemoDataSource } from '@mui/x-data-grid-generator'; +import { useMockServer } from '@mui/x-data-grid-generator'; import LoadingSlate from './LoadingSlate'; const pageSizeOptions = [5, 10, 50]; const serverOptions = { useCursorPagination: false }; const datasetOptions = {}; -const dataSource = { - getRows: async (params) => { - const urlParams = new URLSearchParams({ - paginationModel: encodeURIComponent(JSON.stringify(params.paginationModel)), - filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), - sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), - groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), - }); - const serverResponse = await fetch( - `https://mui.com/x/api/data-grid?${urlParams.toString()}`, - ); - if (!serverResponse.ok) { - const body = await serverResponse.json(); - throw new Error(body.error ?? 'An error occurred while fetching the data'); - } - const getRowsResponse = await serverResponse.json(); - return { - rows: getRowsResponse.rows, - rowCount: getRowsResponse.rowCount, - }; - }, -}; - function getBorderColor(theme) { if (theme.palette.mode === 'light') { return lighten(alpha(theme.palette.divider, 1), 0.88); @@ -68,12 +45,34 @@ export default function ServerSideErrorHandling() { const [error, setError] = React.useState(); const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); - const { isInitialized, ...props } = useDemoDataSource( + const { isInitialized, fetchRows, ...props } = useMockServer( datasetOptions, serverOptions, shouldRequestsFail, ); + const dataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent( + JSON.stringify(params.paginationModel), + ), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + }), + [fetchRows], + ); + const initialState = React.useMemo( () => ({ ...props.initialState, diff --git a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx index 973b4099e5b4..6e637ada7e5a 100644 --- a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx @@ -10,36 +10,13 @@ import Button from '@mui/material/Button'; import Checkbox from '@mui/material/Checkbox'; import FormControlLabel from '@mui/material/FormControlLabel'; import { alpha, styled, darken, lighten, Theme } from '@mui/material/styles'; -import { useDemoDataSource } from '@mui/x-data-grid-generator'; +import { useMockServer } from '@mui/x-data-grid-generator'; import LoadingSlate from './LoadingSlate'; const pageSizeOptions = [5, 10, 50]; const serverOptions = { useCursorPagination: false }; const datasetOptions = {}; -const dataSource: GridDataSource = { - getRows: async (params) => { - const urlParams = new URLSearchParams({ - paginationModel: encodeURIComponent(JSON.stringify(params.paginationModel)), - filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), - sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), - groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), - }); - const serverResponse = await fetch( - `https://mui.com/x/api/data-grid?${urlParams.toString()}`, - ); - if (!serverResponse.ok) { - const body = await serverResponse.json(); - throw new Error(body.error ?? 'An error occurred while fetching the data'); - } - const getRowsResponse = await serverResponse.json(); - return { - rows: getRowsResponse.rows, - rowCount: getRowsResponse.rowCount, - }; - }, -}; - function getBorderColor(theme: Theme) { if (theme.palette.mode === 'light') { return lighten(alpha(theme.palette.divider, 1), 0.88); @@ -74,12 +51,34 @@ export default function ServerSideErrorHandling() { const [error, setError] = React.useState(); const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); - const { isInitialized, ...props } = useDemoDataSource( + const { isInitialized, fetchRows, ...props } = useMockServer( datasetOptions, serverOptions, shouldRequestsFail, ); + const dataSource: GridDataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent( + JSON.stringify(params.paginationModel), + ), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + }), + [fetchRows], + ); + const initialState: GridInitialState = React.useMemo( () => ({ ...props.initialState, diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.js b/docs/data/data-grid/server-side-data/ServerSideTreeData.js index 13ddb1a6379f..656ca6fd5d8d 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.js @@ -1,7 +1,7 @@ import * as React from 'react'; import { DataGridPro, useGridApiRef, GridToolbar } from '@mui/x-data-grid-pro'; import Button from '@mui/material/Button'; -import { useDemoDataSource } from '@mui/x-data-grid-generator'; +import { useMockServer } from '@mui/x-data-grid-generator'; import LoadingSlate from './LoadingSlate'; const pageSizeOptions = [5, 10, 50]; @@ -28,11 +28,16 @@ const dataSource = { export default function ServerSideTreeData() { const apiRef = useGridApiRef(); - const { isInitialized, ...props } = useDemoDataSource({ - dataSet: 'Employee', - rowLength: 1000, - treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, - }); + const { isInitialized, ...props } = useMockServer( + { + dataSet: 'Employee', + rowLength: 1000, + treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, + }, + { + startServer: true, + }, + ); const initialState = React.useMemo( () => ({ diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx index 3ef6bee77bb1..1d07676505c6 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx @@ -7,7 +7,7 @@ import { GridDataSource, } from '@mui/x-data-grid-pro'; import Button from '@mui/material/Button'; -import { useDemoDataSource } from '@mui/x-data-grid-generator'; +import { useMockServer } from '@mui/x-data-grid-generator'; import LoadingSlate from './LoadingSlate'; const pageSizeOptions = [5, 10, 50]; @@ -34,11 +34,16 @@ const dataSource: GridDataSource = { export default function ServerSideTreeData() { const apiRef = useGridApiRef(); - const { isInitialized, ...props } = useDemoDataSource({ - dataSet: 'Employee', - rowLength: 1000, - treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, - }); + const { isInitialized, ...props } = useMockServer( + { + dataSet: 'Employee', + rowLength: 1000, + treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, + }, + { + startServer: true, + }, + ); const initialState: GridInitialState = React.useMemo( () => ({ diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js index b0230e1c25a3..84768b8597e4 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js @@ -1,8 +1,9 @@ import * as React from 'react'; import { DataGridPro, useGridApiRef, GridToolbar } from '@mui/x-data-grid-pro'; import Button from '@mui/material/Button'; -import { useDemoDataSource } from '@mui/x-data-grid-generator'; +import { useMockServer } from '@mui/x-data-grid-generator'; import { QueryClient } from '@tanstack/query-core'; +import LoadingSlate from './LoadingSlate'; const queryClient = new QueryClient({ defaultOptions: { @@ -37,17 +38,34 @@ const pageSizeOptions = [5, 10, 50]; export default function ServerSideTreeDataCustomCache() { const apiRef = useGridApiRef(); - const { getRows, ...props } = useDemoDataSource({ + const { isInitialized, fetchRows, ...props } = useMockServer({ dataSet: 'Employee', rowLength: 1000, treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, }); - const dataSource = React.useMemo(() => { - return { - getRows, - }; - }, [getRows]); + const dataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent( + JSON.stringify(params.paginationModel), + ), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + }), + [fetchRows], + ); const initialState = React.useMemo( () => ({ @@ -66,18 +84,22 @@ export default function ServerSideTreeDataCustomCache() {
- + {isInitialized ? ( + + ) : ( + + )}
); diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx index 77276f45fcaf..0c9b863e3f16 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx @@ -5,10 +5,12 @@ import { GridInitialState, GridToolbar, GridServerSideCache, + GridDataSource, } from '@mui/x-data-grid-pro'; import Button from '@mui/material/Button'; -import { useDemoDataSource } from '@mui/x-data-grid-generator'; +import { useMockServer } from '@mui/x-data-grid-generator'; import { QueryClient } from '@tanstack/query-core'; +import LoadingSlate from './LoadingSlate'; const queryClient = new QueryClient({ defaultOptions: { @@ -43,17 +45,34 @@ const pageSizeOptions = [5, 10, 50]; export default function ServerSideTreeDataCustomCache() { const apiRef = useGridApiRef(); - const { getRows, ...props } = useDemoDataSource({ + const { isInitialized, fetchRows, ...props } = useMockServer({ dataSet: 'Employee', rowLength: 1000, treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, }); - const dataSource = React.useMemo(() => { - return { - getRows, - }; - }, [getRows]); + const dataSource: GridDataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent( + JSON.stringify(params.paginationModel), + ), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + }), + [fetchRows], + ); const initialState: GridInitialState = React.useMemo( () => ({ @@ -72,18 +91,22 @@ export default function ServerSideTreeDataCustomCache() {
- + {isInitialized ? ( + + ) : ( + + )}
); diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx.preview deleted file mode 100644 index ef89b01d59e2..000000000000 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx.preview +++ /dev/null @@ -1,15 +0,0 @@ - -
- -
\ No newline at end of file diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js index 70675287b5e1..2bfccd431428 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js @@ -5,47 +5,19 @@ import Button from '@mui/material/Button'; import Checkbox from '@mui/material/Checkbox'; import FormControlLabel from '@mui/material/FormControlLabel'; import { alpha, styled, darken, lighten } from '@mui/material/styles'; -import { useDemoDataSource } from '@mui/x-data-grid-generator'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import LoadingSlate from './LoadingSlate'; const pageSizeOptions = [5, 10, 50]; const serverOptions = { useCursorPagination: false }; -function getBorderColor(theme) { - if (theme.palette.mode === 'light') { - return lighten(alpha(theme.palette.divider, 1), 0.88); - } - return darken(alpha(theme.palette.divider, 1), 0.68); -} - -const StyledDiv = styled('div')(({ theme: t }) => ({ - position: 'absolute', - zIndex: 10, - fontSize: '0.875em', - top: 0, - height: '100%', - width: '100%', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - borderRadius: '4px', - border: `1px solid ${getBorderColor(t)}`, - backgroundColor: t.palette.background.default, -})); - -function ErrorOverlay({ error }) { - if (!error) { - return null; - } - return {error}; -} - export default function ServerSideTreeDataErrorHandling() { const apiRef = useGridApiRef(); const [rootError, setRootError] = React.useState(); const [childrenError, setChildrenError] = React.useState(); const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); - const { getRows, ...props } = useDemoDataSource( + const { isInitialized, fetchRows, ...props } = useMockServer( { dataSet: 'Employee', rowLength: 1000, @@ -55,11 +27,28 @@ export default function ServerSideTreeDataErrorHandling() { shouldRequestsFail, ); - const dataSource = React.useMemo(() => { - return { - getRows, - }; - }, [getRows]); + const dataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent( + JSON.stringify(params.paginationModel), + ), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + }), + [fetchRows], + ); const initialState = React.useMemo( () => ({ @@ -96,33 +85,68 @@ export default function ServerSideTreeDataErrorHandling() { />
- { - if (!params.groupKeys || params.groupKeys.length === 0) { - setRootError(e.message); - } else { - setChildrenError( - `${e.message} (Requested level: ${params.groupKeys.join(', ')})`, - ); - } - }} - disableServerSideCache - apiRef={apiRef} - pagination - pageSizeOptions={pageSizeOptions} - initialState={initialState} - /> - {rootError && } - setChildrenError('')} - message={childrenError} - /> + {isInitialized ? ( + + { + if (!params.groupKeys || params.groupKeys.length === 0) { + setRootError(e.message); + } else { + setChildrenError( + `${e.message} (Requested level: ${params.groupKeys.join(', ')})`, + ); + } + }} + disableServerSideCache + apiRef={apiRef} + pagination + pageSizeOptions={pageSizeOptions} + initialState={initialState} + /> + {rootError && } + setChildrenError('')} + message={childrenError} + /> + + ) : ( + + )}
); } + +function getBorderColor(theme) { + if (theme.palette.mode === 'light') { + return lighten(alpha(theme.palette.divider, 1), 0.88); + } + return darken(alpha(theme.palette.divider, 1), 0.68); +} + +const StyledDiv = styled('div')(({ theme: t }) => ({ + position: 'absolute', + zIndex: 10, + fontSize: '0.875em', + top: 0, + height: '100%', + width: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + borderRadius: '4px', + border: `1px solid ${getBorderColor(t)}`, + backgroundColor: t.palette.background.default, +})); + +function ErrorOverlay({ error }) { + if (!error) { + return null; + } + return {error}; +} diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx index b8c6b9905f40..29ed918dc960 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx @@ -1,51 +1,28 @@ import * as React from 'react'; -import { DataGridPro, useGridApiRef, GridInitialState } from '@mui/x-data-grid-pro'; +import { + DataGridPro, + useGridApiRef, + GridInitialState, + GridDataSource, +} from '@mui/x-data-grid-pro'; import Snackbar from '@mui/material/Snackbar'; import Button from '@mui/material/Button'; import Checkbox from '@mui/material/Checkbox'; import FormControlLabel from '@mui/material/FormControlLabel'; import { alpha, styled, darken, lighten, Theme } from '@mui/material/styles'; -import { useDemoDataSource } from '@mui/x-data-grid-generator'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import LoadingSlate from './LoadingSlate'; const pageSizeOptions = [5, 10, 50]; const serverOptions = { useCursorPagination: false }; -function getBorderColor(theme: Theme) { - if (theme.palette.mode === 'light') { - return lighten(alpha(theme.palette.divider, 1), 0.88); - } - return darken(alpha(theme.palette.divider, 1), 0.68); -} - -const StyledDiv = styled('div')(({ theme: t }) => ({ - position: 'absolute', - zIndex: 10, - fontSize: '0.875em', - top: 0, - height: '100%', - width: '100%', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - borderRadius: '4px', - border: `1px solid ${getBorderColor(t)}`, - backgroundColor: t.palette.background.default, -})); - -function ErrorOverlay({ error }: { error: string }) { - if (!error) { - return null; - } - return {error}; -} - export default function ServerSideTreeDataErrorHandling() { const apiRef = useGridApiRef(); const [rootError, setRootError] = React.useState(); const [childrenError, setChildrenError] = React.useState(); const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); - const { getRows, ...props } = useDemoDataSource( + const { isInitialized, fetchRows, ...props } = useMockServer( { dataSet: 'Employee', rowLength: 1000, @@ -55,11 +32,28 @@ export default function ServerSideTreeDataErrorHandling() { shouldRequestsFail, ); - const dataSource = React.useMemo(() => { - return { - getRows, - }; - }, [getRows]); + const dataSource: GridDataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent( + JSON.stringify(params.paginationModel), + ), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + }), + [fetchRows], + ); const initialState: GridInitialState = React.useMemo( () => ({ @@ -96,33 +90,68 @@ export default function ServerSideTreeDataErrorHandling() { />
- { - if (!params.groupKeys || params.groupKeys.length === 0) { - setRootError(e.message); - } else { - setChildrenError( - `${e.message} (Requested level: ${params.groupKeys.join(', ')})`, - ); - } - }} - disableServerSideCache - apiRef={apiRef} - pagination - pageSizeOptions={pageSizeOptions} - initialState={initialState} - /> - {rootError && } - setChildrenError('')} - message={childrenError} - /> + {isInitialized ? ( + + { + if (!params.groupKeys || params.groupKeys.length === 0) { + setRootError(e.message); + } else { + setChildrenError( + `${e.message} (Requested level: ${params.groupKeys.join(', ')})`, + ); + } + }} + disableServerSideCache + apiRef={apiRef} + pagination + pageSizeOptions={pageSizeOptions} + initialState={initialState} + /> + {rootError && } + setChildrenError('')} + message={childrenError} + /> + + ) : ( + + )}
); } + +function getBorderColor(theme: Theme) { + if (theme.palette.mode === 'light') { + return lighten(alpha(theme.palette.divider, 1), 0.88); + } + return darken(alpha(theme.palette.divider, 1), 0.68); +} + +const StyledDiv = styled('div')(({ theme: t }) => ({ + position: 'absolute', + zIndex: 10, + fontSize: '0.875em', + top: 0, + height: '100%', + width: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + borderRadius: '4px', + border: `1px solid ${getBorderColor(t)}`, + backgroundColor: t.palette.background.default, +})); + +function ErrorOverlay({ error }: { error: string }) { + if (!error) { + return null; + } + return {error}; +} diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js index 2cced6425928..048d72cdd0b0 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js @@ -1,28 +1,52 @@ import * as React from 'react'; import { DataGridPro, useGridApiRef, GridToolbar } from '@mui/x-data-grid-pro'; import Button from '@mui/material/Button'; -import { useDemoDataSource } from '@mui/x-data-grid-generator'; +import { useMockServer } from '@mui/x-data-grid-generator'; const pageSizeOptions = [5, 10, 50]; export default function ServerSideTreeDataGroupExpansion() { const apiRef = useGridApiRef(); - const { getRows, ...props } = useDemoDataSource({ + const { + fetchRows, + columns, + initialState, + getGroupKey, + getChildrenCount, + hasChildren, + } = useMockServer({ dataSet: 'Employee', rowLength: 1000, treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, }); - const dataSource = React.useMemo(() => { - return { - getRows, - }; - }, [getRows]); + const dataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent( + JSON.stringify(params.paginationModel), + ), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + }), + [fetchRows], + ); - const initialState = React.useMemo( + const initialStateWithPagination = React.useMemo( () => ({ - ...props.initialState, + ...initialState, pagination: { paginationModel: { pageSize: 5, @@ -30,7 +54,7 @@ export default function ServerSideTreeDataGroupExpansion() { rowCount: 0, }, }), - [props.initialState], + [initialState], ); return ( @@ -38,13 +62,16 @@ export default function ServerSideTreeDataGroupExpansion() {
{ - return { - getRows, - }; - }, [getRows]); + const dataSource: GridDataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent( + JSON.stringify(params.paginationModel), + ), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + }), + [fetchRows], + ); - const initialState: GridInitialState = React.useMemo( + const initialStateWithPagination: GridInitialState = React.useMemo( () => ({ - ...props.initialState, + ...initialState, pagination: { paginationModel: { pageSize: 5, @@ -35,7 +60,7 @@ export default function ServerSideTreeDataGroupExpansion() { rowCount: 0, }, }), - [props.initialState], + [initialState], ); return ( @@ -43,13 +68,16 @@ export default function ServerSideTreeDataGroupExpansion() {
apiRef.current.clearCache()}>Reset cache -
- -
\ No newline at end of file diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index 050cc53176d1..f7ab6baeb0c2 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -120,39 +120,6 @@ const customDataSource: GridDataSource = { The code has been significantly reduced, the need for managing the controlled states is removed, and data fetching logic is centralized. -:::info - -For the following examples, a utility `useDemoDataSource` is used to simulate the server-side data fetching based on the package `@mui/x-data-grid-generator`. It creates a dummy server based on the mock service worker. You can replace this with your actual server-side data fetching logic. - -```tsx -const { loading, columns, initialState } = useDemoDataSource( - dataSetOptions, - serverOptions, -); - -const customDataSource: GridDataSource = { - getRows: async (params: GridGetRowsParams): GetRowsResponse => { - const requestParams = new URLSearchParams({ - page: params.page.toString(), - pageSize: params.pageSize.toString(), - sortModel: JSON.stringify(params.sortModel), - filterModel: JSON.stringify(params.filterModel), - }); - const response = await fetch(`https://api-url?${requestParams.toString()}`); - const data = await response.json(); - - return { - rows: data.rows, - rowCount: data.totalCount, - }; - }, -}; - -; -``` - -::: - ## Server-side filtering, sorting, and pagination The data source changes how the existing server-side features like `filtering`, `sorting`, and `pagination` work. @@ -198,6 +165,10 @@ The following demo showcases this behavior. {{"demo": "ServerSideDataGrid.js", "bg": "inline"}} +:::info +The demo above uses a utility `msw` to create a mock server that intercepts the actual Network calls and provide data. Open Network tab in the browser's developer tools to see the requests and corresponding responses. +::: + ## Data caching The Data source supports caching the data it receives from the server by default. This means that if the user navigates to a page or expands a node that has already been fetched, the grid will not call the `getRows` function again to avoid unnecessary calls to the server. 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 d71046aaf8f2..266d1d1be4de 100644 --- a/docs/data/data-grid/server-side-data/tree-data.md +++ b/docs/data/data-grid/server-side-data/tree-data.md @@ -47,7 +47,7 @@ Following is a demo of the server-side tree data with the data source which supp {{"demo": "ServerSideTreeData.js", "bg": "inline"}} :::info -The demo above uses a utility `useDemoDataSource` which uses a data generator service to generate data for testing of the application. Apart from providing the additional props, it exposes a function called `getRows` which could be used directly as `GridDataSource.getRows`. +The demo above uses a utility `msw` to create a mock server that intercepts the actual Network calls and provide data. Open Network tab in the browser's developer tools to see the requests and corresponding responses. ::: ## Error handling diff --git a/packages/x-data-grid-generator/src/hooks/index.ts b/packages/x-data-grid-generator/src/hooks/index.ts index 33d9e5ebd232..4d18ce1e90b9 100644 --- a/packages/x-data-grid-generator/src/hooks/index.ts +++ b/packages/x-data-grid-generator/src/hooks/index.ts @@ -2,5 +2,5 @@ export * from './useDemoData'; export * from './useBasicDemoData'; export * from './useMovieData'; export * from './useQuery'; -export * from './useDemoDataSource'; +export * from './useMockServer'; export { loadServerRows } from './serverUtils'; diff --git a/packages/x-data-grid-generator/src/hooks/serverUtils.ts b/packages/x-data-grid-generator/src/hooks/serverUtils.ts index 34852a79e5c1..03288b5c8886 100644 --- a/packages/x-data-grid-generator/src/hooks/serverUtils.ts +++ b/packages/x-data-grid-generator/src/hooks/serverUtils.ts @@ -39,7 +39,6 @@ export interface QueryOptions { cursor?: GridRowId; page?: number; pageSize?: number; - // TODO: implement the behavior liked to following models filterModel?: GridFilterModel; sortModel?: GridSortModel; firstRowToRender?: number; @@ -50,7 +49,6 @@ export interface ServerSideQueryOptions { cursor?: GridRowId; paginationModel?: GridPaginationModel; groupKeys?: string[]; - // TODO: implement the behavior liked to following models filterModel?: GridFilterModel; sortModel?: GridSortModel; firstRowToRender?: number; diff --git a/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts b/packages/x-data-grid-generator/src/hooks/useMockServer.ts similarity index 79% rename from packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts rename to packages/x-data-grid-generator/src/hooks/useMockServer.ts index 6790b879f933..c621390f7150 100644 --- a/packages/x-data-grid-generator/src/hooks/useDemoDataSource.ts +++ b/packages/x-data-grid-generator/src/hooks/useMockServer.ts @@ -10,7 +10,6 @@ import { GridColDef, GridInitialState, GridColumnVisibilityModel, - GridDataSource, } from '@mui/x-data-grid-pro'; import { UseDemoDataOptions, @@ -35,15 +34,15 @@ const dataCache = new LRUCache({ ttl: 60 * 5 * 1e3, // 5 minutes }); -export const API_URL = 'https://mui.com/x/api/data-grid'; +export const BASE_URL = 'https://mui.com/x/api/data-grid'; -type UseDemoDataSourceResponse = { +type UseMockServerResponse = { columns: GridColDef[]; initialState: GridInitialState; getGroupKey?: (row: GridRowModel) => string; hasChildren?: (row: GridRowModel) => boolean; getChildrenCount?: (row: GridRowModel) => number; - getRows: GridDataSource['getRows']; + fetchRows: (url: string) => Promise; isInitialized: boolean; loadNewData: () => void; }; @@ -80,11 +79,11 @@ const getInitialState = (columns: GridColDefGenerator[], groupingField?: string) const defaultColDef = getGridDefaultColumnTypes(); -export const useDemoDataSource = ( +export const useMockServer = ( dataSetOptions?: Partial, - serverOptions?: ServerOptions, + serverOptions?: ServerOptions & { startServer?: boolean }, shouldRequestsFail?: boolean, -): UseDemoDataSourceResponse => { +): UseMockServerResponse => { const [isInitialized, setIsInitialized] = React.useState(false); const [worker, setWorker] = React.useState(); const [data, setData] = React.useState(); @@ -204,15 +203,21 @@ export const useDemoDataSource = ( index, ]); - const getRows = React.useCallback( - async (params: GridGetRowsParams): Promise => { - if (!data) { + const fetchRows = React.useCallback( + async (requestUrl: string): Promise => { + if (!data || !requestUrl) { return new Promise((resolve) => { resolve({ rows: [], rowCount: 0 }); }); } + const params = decodeParams(requestUrl); let getRowsResponse: GridGetRowsResponse; - const serverOptionsWithDefault = { ...DEFAULT_SERVER_OPTIONS, ...serverOptions }; + const serverOptionsWithDefault = { + minDelay: serverOptions?.minDelay ?? DEFAULT_SERVER_OPTIONS.minDelay, + maxDelay: serverOptions?.maxDelay ?? DEFAULT_SERVER_OPTIONS.maxDelay, + useCursorPagination: + serverOptions?.useCursorPagination ?? DEFAULT_SERVER_OPTIONS.useCursorPagination, + }; if (shouldRequestsFailRef.current) { const { minDelay, maxDelay } = serverOptionsWithDefault; @@ -223,6 +228,8 @@ export const useDemoDataSource = ( } if (isTreeData /* || TODO: `isRowGrouping` */) { + console.log('requested tree data for groupKeys', params.groupKeys); + const { rows, rootRowCount } = await processTreeDataRows( data.rows, params, @@ -230,6 +237,11 @@ export const useDemoDataSource = ( columnsWithDefaultColDef, ); + console.log('processed tree data for groupKeys', { + rows: rows.slice().map((row) => ({ ...row, path: undefined })), + rowCount: rootRowCount, + }); + getRowsResponse = { rows: rows.slice().map((row) => ({ ...row, path: undefined })), rowCount: rootRowCount, @@ -249,42 +261,42 @@ export const useDemoDataSource = ( resolve(getRowsResponse); }); }, - [data, columnsWithDefaultColDef, isTreeData, serverOptions], + [ + data, + columnsWithDefaultColDef, + isTreeData, + serverOptions?.minDelay, + serverOptions?.maxDelay, + serverOptions?.useCursorPagination, + ], ); - const handlers = React.useMemo(() => { - if (!data) { - return []; - } - return [ - http.get(API_URL, async ({ request }) => { - if (!request.url) { - return HttpResponse.json({ error: 'Bad request.' }, { status: 400 }); - } - const params = decodeParams(request.url); - try { - if (shouldRequestsFail) { - const serverOptionsWithDefault = { ...DEFAULT_SERVER_OPTIONS, ...serverOptions }; - const { minDelay, maxDelay } = serverOptionsWithDefault; - const delay = randomInt(minDelay, maxDelay); - return HttpResponse.json({ error: 'Could not fetch the data' }, { status: 500 }); - } - const response = await getRows(params); - return HttpResponse.json(response); - } catch (error) { - return HttpResponse.json({ error }, { status: 500 }); - } - }), - ]; - }, [getRows, data, shouldRequestsFail]); - React.useEffect(() => { + if (!data || !serverOptions?.startServer) { + return; + } async function startServer() { if (typeof window !== 'undefined') { const { setupWorker } = require('msw/browser'); if (!setupWorker) { return; } + const handlers = [ + http.get(BASE_URL, async ({ request }) => { + if (!request.url) { + return HttpResponse.json({ error: 'Bad request.' }, { status: 400 }); + } + try { + if (shouldRequestsFail) { + return HttpResponse.json({ error: 'Could not fetch the data' }, { status: 500 }); + } + const response = await fetchRows(request.url); + return HttpResponse.json(response); + } catch (error) { + return HttpResponse.json({ error }, { status: 500 }); + } + }), + ]; const w = setupWorker(...handlers); try { await w.start({ quiet: true }); @@ -294,9 +306,7 @@ export const useDemoDataSource = ( } } } - if (handlers.length > 0) { - startServer(); - } + startServer(); return () => { if (worker) { setWorker((prev) => { @@ -305,10 +315,10 @@ export const useDemoDataSource = ( }); } }; - }, [handlers]); + }, [fetchRows, data, shouldRequestsFail]); React.useEffect(() => { - if (data && worker && !isInitialized) { + if (data && (!serverOptions?.startServer || worker) && !isInitialized) { setIsInitialized(true); } }, [data, worker, isInitialized]); @@ -319,7 +329,7 @@ export const useDemoDataSource = ( getGroupKey, hasChildren, getChildrenCount, - getRows, + fetchRows, isInitialized, loadNewData: () => { setIndex((oldIndex) => oldIndex + 1); From 1986f741e0472fc1fcb44893211b0098f437b35c Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 20 May 2024 14:17:45 +0500 Subject: [PATCH 27/90] Rename loading slate file --- .../server-side-data/LoadingSlate.tsx | 32 ------------------- .../server-side-data/LoadingSlate.tsx.preview | 3 -- ...{LoadingSlate.js => LoadingSlateNoSnap.js} | 0 .../server-side-data/ServerSideDataGrid.js | 3 +- .../server-side-data/ServerSideDataGrid.tsx | 2 +- .../ServerSideDataGridNoCache.js | 2 +- .../ServerSideDataGridNoCache.tsx | 2 +- .../ServerSideDataGridWithSWR.js | 2 +- .../ServerSideDataGridWithSWR.tsx | 2 +- .../ServerSideErrorHandling.js | 2 +- .../ServerSideErrorHandling.tsx | 2 +- .../server-side-data/ServerSideTreeData.js | 2 +- .../server-side-data/ServerSideTreeData.tsx | 2 +- .../ServerSideTreeDataCustomCache.js | 2 +- .../ServerSideTreeDataCustomCache.tsx | 2 +- .../ServerSideTreeDataErrorHandling.js | 2 +- .../ServerSideTreeDataErrorHandling.tsx | 2 +- 17 files changed, 14 insertions(+), 50 deletions(-) delete mode 100644 docs/data/data-grid/server-side-data/LoadingSlate.tsx delete mode 100644 docs/data/data-grid/server-side-data/LoadingSlate.tsx.preview rename docs/data/data-grid/server-side-data/{LoadingSlate.js => LoadingSlateNoSnap.js} (100%) diff --git a/docs/data/data-grid/server-side-data/LoadingSlate.tsx b/docs/data/data-grid/server-side-data/LoadingSlate.tsx deleted file mode 100644 index 120c6553033d..000000000000 --- a/docs/data/data-grid/server-side-data/LoadingSlate.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import CircularProgress from '@mui/material/CircularProgress'; -import { lighten, darken, alpha, styled, Theme } from '@mui/material/styles'; - -function getBorderColor(theme: Theme) { - if (theme.palette.mode === 'light') { - return lighten(alpha(theme.palette.divider, 1), 0.88); - } - return darken(alpha(theme.palette.divider, 1), 0.68); -} - -function getBorderRadius(theme: Theme) { - const radius = theme.shape.borderRadius; - return typeof radius === 'number' ? `${radius}px` : radius; -} - -const StyledDiv = styled('div')(({ theme }) => ({ - height: '100%', - width: '100%', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - border: `1px solid ${getBorderColor(theme)}`, - borderRadius: getBorderRadius(theme), -})); - -export default function LoadingSlate() { - return ( - - - - ); -} diff --git a/docs/data/data-grid/server-side-data/LoadingSlate.tsx.preview b/docs/data/data-grid/server-side-data/LoadingSlate.tsx.preview deleted file mode 100644 index 73a2fae54ba3..000000000000 --- a/docs/data/data-grid/server-side-data/LoadingSlate.tsx.preview +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/docs/data/data-grid/server-side-data/LoadingSlate.js b/docs/data/data-grid/server-side-data/LoadingSlateNoSnap.js similarity index 100% rename from docs/data/data-grid/server-side-data/LoadingSlate.js rename to docs/data/data-grid/server-side-data/LoadingSlateNoSnap.js diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGrid.js b/docs/data/data-grid/server-side-data/ServerSideDataGrid.js index d8f937a0ab9d..848aa80af500 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGrid.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGrid.js @@ -1,7 +1,7 @@ import * as React from 'react'; import { DataGridPro } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; -import LoadingSlate from './LoadingSlate'; +import LoadingSlate from './LoadingSlateNoSnap'; const serverOptions = { useCursorPagination: false, startServer: true }; const dataSetOptions = {}; @@ -12,7 +12,6 @@ const dataSource = { paginationModel: encodeURIComponent(JSON.stringify(params.paginationModel)), filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), - groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), }); const serverResponse = await fetch( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx index 5c01f22d792e..e4f92cba2795 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { DataGridPro, GridDataSource } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; -import LoadingSlate from './LoadingSlate'; +import LoadingSlate from './LoadingSlateNoSnap'; const serverOptions = { useCursorPagination: false, startServer: true }; const dataSetOptions = {}; diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js index f4dd8ced6f69..6093f7bab276 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js @@ -1,7 +1,7 @@ import * as React from 'react'; import { DataGridPro } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; -import LoadingSlate from './LoadingSlate'; +import LoadingSlate from './LoadingSlateNoSnap'; const pageSizeOptions = [5, 10, 50]; diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx index 7db1605e6d2b..c7ad15f1979a 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { DataGridPro, GridDataSource } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; -import LoadingSlate from './LoadingSlate'; +import LoadingSlate from './LoadingSlateNoSnap'; const pageSizeOptions = [5, 10, 50]; diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js index d0075d0ea6c3..2ecda51613b6 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js @@ -2,7 +2,7 @@ import * as React from 'react'; import { DataGridPro } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; import { useSWRConfig } from 'swr'; -import LoadingSlate from './LoadingSlate'; +import LoadingSlate from './LoadingSlateNoSnap'; const serverOptions = { useCursorPagination: false }; const dataSetOptions = {}; diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx index 18d272c08c21..504050395d66 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx @@ -7,7 +7,7 @@ import { } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; import { useSWRConfig } from 'swr'; -import LoadingSlate from './LoadingSlate'; +import LoadingSlate from './LoadingSlateNoSnap'; const serverOptions = { useCursorPagination: false }; const dataSetOptions = {}; diff --git a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js index da78974d631b..20cab96ce96d 100644 --- a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js +++ b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js @@ -5,7 +5,7 @@ import Checkbox from '@mui/material/Checkbox'; import FormControlLabel from '@mui/material/FormControlLabel'; import { alpha, styled, darken, lighten } from '@mui/material/styles'; import { useMockServer } from '@mui/x-data-grid-generator'; -import LoadingSlate from './LoadingSlate'; +import LoadingSlate from './LoadingSlateNoSnap'; const pageSizeOptions = [5, 10, 50]; const serverOptions = { useCursorPagination: false }; diff --git a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx index 6e637ada7e5a..7e8d16471313 100644 --- a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx @@ -11,7 +11,7 @@ import Checkbox from '@mui/material/Checkbox'; import FormControlLabel from '@mui/material/FormControlLabel'; import { alpha, styled, darken, lighten, Theme } from '@mui/material/styles'; import { useMockServer } from '@mui/x-data-grid-generator'; -import LoadingSlate from './LoadingSlate'; +import LoadingSlate from './LoadingSlateNoSnap'; const pageSizeOptions = [5, 10, 50]; const serverOptions = { useCursorPagination: false }; diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.js b/docs/data/data-grid/server-side-data/ServerSideTreeData.js index 656ca6fd5d8d..8a5aa525ac23 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.js @@ -2,7 +2,7 @@ import * as React from 'react'; import { DataGridPro, useGridApiRef, GridToolbar } from '@mui/x-data-grid-pro'; import Button from '@mui/material/Button'; import { useMockServer } from '@mui/x-data-grid-generator'; -import LoadingSlate from './LoadingSlate'; +import LoadingSlate from './LoadingSlateNoSnap'; const pageSizeOptions = [5, 10, 50]; diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx index 1d07676505c6..142013bde8bb 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx @@ -8,7 +8,7 @@ import { } from '@mui/x-data-grid-pro'; import Button from '@mui/material/Button'; import { useMockServer } from '@mui/x-data-grid-generator'; -import LoadingSlate from './LoadingSlate'; +import LoadingSlate from './LoadingSlateNoSnap'; const pageSizeOptions = [5, 10, 50]; diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js index 84768b8597e4..6fbf545ef889 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js @@ -3,7 +3,7 @@ import { DataGridPro, useGridApiRef, GridToolbar } from '@mui/x-data-grid-pro'; import Button from '@mui/material/Button'; import { useMockServer } from '@mui/x-data-grid-generator'; import { QueryClient } from '@tanstack/query-core'; -import LoadingSlate from './LoadingSlate'; +import LoadingSlate from './LoadingSlateNoSnap'; const queryClient = new QueryClient({ defaultOptions: { diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx index 0c9b863e3f16..cc0f5a971335 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx @@ -10,7 +10,7 @@ import { import Button from '@mui/material/Button'; import { useMockServer } from '@mui/x-data-grid-generator'; import { QueryClient } from '@tanstack/query-core'; -import LoadingSlate from './LoadingSlate'; +import LoadingSlate from './LoadingSlateNoSnap'; const queryClient = new QueryClient({ defaultOptions: { diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js index 2bfccd431428..fd6da299632c 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js @@ -6,7 +6,7 @@ import Checkbox from '@mui/material/Checkbox'; import FormControlLabel from '@mui/material/FormControlLabel'; import { alpha, styled, darken, lighten } from '@mui/material/styles'; import { useMockServer } from '@mui/x-data-grid-generator'; -import LoadingSlate from './LoadingSlate'; +import LoadingSlate from './LoadingSlateNoSnap'; const pageSizeOptions = [5, 10, 50]; const serverOptions = { useCursorPagination: false }; diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx index 29ed918dc960..95e8a5e87fc9 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx @@ -11,7 +11,7 @@ import Checkbox from '@mui/material/Checkbox'; import FormControlLabel from '@mui/material/FormControlLabel'; import { alpha, styled, darken, lighten, Theme } from '@mui/material/styles'; import { useMockServer } from '@mui/x-data-grid-generator'; -import LoadingSlate from './LoadingSlate'; +import LoadingSlate from './LoadingSlateNoSnap'; const pageSizeOptions = [5, 10, 50]; const serverOptions = { useCursorPagination: false }; From a99815267b033a1e140cb64f6849eecdeb91e683 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 20 May 2024 15:40:07 +0500 Subject: [PATCH 28/90] Disable client-side features with data source --- .../src/DataGridPremium/index.ts | 2 +- .../useDataGridPremiumProps.ts | 34 +++++++++++++++---- .../x-data-grid-pro/src/DataGridPro/index.ts | 2 +- .../src/DataGridPro/useDataGridProProps.ts | 33 +++++++++++++----- scripts/x-data-grid-generator.exports.json | 3 +- scripts/x-data-grid-premium.exports.json | 2 +- scripts/x-data-grid-pro.exports.json | 2 +- 7 files changed, 59 insertions(+), 19 deletions(-) diff --git a/packages/x-data-grid-premium/src/DataGridPremium/index.ts b/packages/x-data-grid-premium/src/DataGridPremium/index.ts index 2de98cc82798..283b4bc437bb 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/index.ts +++ b/packages/x-data-grid-premium/src/DataGridPremium/index.ts @@ -1,3 +1,3 @@ export * from './DataGrid'; export * from './DataGridPremium'; -export { GET_DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES } from './useDataGridPremiumProps'; +export { DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES } from './useDataGridPremiumProps'; diff --git a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts index 6fa61af148c8..57b22b3b696f 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts +++ b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { useThemeProps } from '@mui/material/styles'; import { - GET_DATA_GRID_PRO_PROPS_DEFAULT_VALUES, + DATA_GRID_PRO_PROPS_DEFAULT_VALUES, GRID_DEFAULT_LOCALE_TEXT, DataGridProProps, } from '@mui/x-data-grid-pro'; @@ -17,13 +17,26 @@ import { DATA_GRID_PREMIUM_DEFAULT_SLOTS_COMPONENTS } from '../constants/dataGri interface GetDataGridPremiumPropsDefaultValues extends DataGridPremiumProps {} +type DataGridProForcedProps = { [key in keyof DataGridProProps]?: DataGridPremiumProcessedProps[key] }; +type GetDataGridProForcedProps = ( + themedProps: GetDataGridPremiumPropsDefaultValues, +) => DataGridProForcedProps; + +const GET_DATA_GRID_PREMIUM_FORCED_PROPS: GetDataGridProForcedProps = (themedProps) => ({ + signature: 'DataGridPremium', + ...(themedProps.unstable_dataSource + ? { + filterMode: 'server', + sortingMode: 'server', + paginationMode: 'server', + } + : {}), +}); /** * The default values of `DataGridPremiumPropsWithDefaultValue` to inject in the props of DataGridPremium. */ -export const GET_DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES: ( - themedProps: GetDataGridPremiumPropsDefaultValues, -) => DataGridPremiumPropsWithDefaultValue = (themedProps) => ({ - ...GET_DATA_GRID_PRO_PROPS_DEFAULT_VALUES(themedProps as DataGridProProps), +export const DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES: DataGridPremiumPropsWithDefaultValue = { + ...DATA_GRID_PRO_PROPS_DEFAULT_VALUES, cellSelection: false, disableAggregation: false, disableRowGrouping: false, @@ -38,6 +51,15 @@ export const GET_DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES: ( const text = pastedText.replace(/\r?\n$/, ''); return text.split(/\r\n|\n|\r/).map((row) => row.split('\t')); }, +}; + +const GET_DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES: ( + themedProps: GetDataGridPremiumPropsDefaultValues, +) => DataGridPremiumPropsWithDefaultValue = (themedProps) => ({ + ...DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES, + filterDebounceMs: themedProps.unstable_dataSource + ? 1000 + : DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES.filterDebounceMs, }); const defaultSlots = DATA_GRID_PREMIUM_DEFAULT_SLOTS_COMPONENTS; @@ -71,7 +93,7 @@ export const useDataGridPremiumProps = (inProps: DataGridPremiumProps) => { ...themedProps, localeText, slots, - signature: 'DataGridPremium', + ...GET_DATA_GRID_PREMIUM_FORCED_PROPS(themedProps), }), [themedProps, localeText, slots], ); diff --git a/packages/x-data-grid-pro/src/DataGridPro/index.ts b/packages/x-data-grid-pro/src/DataGridPro/index.ts index fadddecfe7fc..9a2f9455f863 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/index.ts +++ b/packages/x-data-grid-pro/src/DataGridPro/index.ts @@ -1,3 +1,3 @@ export * from './DataGrid'; export * from './DataGridPro'; -export { GET_DATA_GRID_PRO_PROPS_DEFAULT_VALUES } from './useDataGridProProps'; +export { DATA_GRID_PRO_PROPS_DEFAULT_VALUES } from './useDataGridProProps'; diff --git a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts index 5f4fee03d7f6..a65dde7e58cc 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts +++ b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts @@ -16,12 +16,26 @@ import { DATA_GRID_PRO_DEFAULT_SLOTS_COMPONENTS } from '../constants/dataGridPro interface GetDataGridProPropsDefaultValues extends DataGridProProps {} +type DataGridProForcedProps = { [key in keyof DataGridProProps]?: DataGridProProcessedProps[key] }; +type GetDataGridProForcedProps = ( + themedProps: GetDataGridProPropsDefaultValues, +) => DataGridProForcedProps; + +const GET_DATA_GRID_PRO_FORCED_PROPS: GetDataGridProForcedProps = (themedProps) => ({ + signature: 'DataGridPro', + ...(themedProps.unstable_dataSource + ? { + filterMode: 'server', + sortingMode: 'server', + paginationMode: 'server', + } + : {}), +}); + /** * The default values of `DataGridProPropsWithDefaultValue` to inject in the props of DataGridPro. */ -export const GET_DATA_GRID_PRO_PROPS_DEFAULT_VALUES: ( - themedProps: GetDataGridProPropsDefaultValues, -) => DataGridProPropsWithDefaultValue = (themedProps) => ({ +export const DATA_GRID_PRO_PROPS_DEFAULT_VALUES: DataGridProPropsWithDefaultValue = { ...DATA_GRID_PROPS_DEFAULT_VALUES, disableServerSideCache: false, scrollEndThreshold: 80, @@ -37,10 +51,13 @@ export const GET_DATA_GRID_PRO_PROPS_DEFAULT_VALUES: ( rowsLoadingMode: 'client', getDetailPanelHeight: () => 500, headerFilters: false, - filterMode: themedProps.unstable_dataSource?.getRows ? 'server' : 'client', - sortingMode: themedProps.unstable_dataSource?.getRows ? 'server' : 'client', - paginationMode: themedProps.unstable_dataSource?.getRows ? 'server' : 'client', - filterDebounceMs: themedProps.unstable_dataSource?.getRows +}; + +const GET_DATA_GRID_PRO_PROPS_DEFAULT_VALUES: ( + themedProps: GetDataGridProPropsDefaultValues, +) => DataGridProPropsWithDefaultValue = (themedProps) => ({ + ...DATA_GRID_PRO_PROPS_DEFAULT_VALUES, + filterDebounceMs: themedProps.unstable_dataSource ? 1000 : DATA_GRID_PROPS_DEFAULT_VALUES.filterDebounceMs, }); @@ -76,7 +93,7 @@ export const useDataGridProProps = (inProps: DataGr ...themedProps, localeText, slots, - signature: 'DataGridPro', + ...GET_DATA_GRID_PRO_FORCED_PROPS(themedProps), }), [themedProps, localeText, slots], ); diff --git a/scripts/x-data-grid-generator.exports.json b/scripts/x-data-grid-generator.exports.json index 189f25f961a1..c2fc12a036aa 100644 --- a/scripts/x-data-grid-generator.exports.json +++ b/scripts/x-data-grid-generator.exports.json @@ -1,4 +1,5 @@ [ + { "name": "BASE_URL", "kind": "Variable" }, { "name": "ColumnsOptions", "kind": "Interface" }, { "name": "createFakeServer", "kind": "Variable" }, { "name": "currencyPairs", "kind": "Variable" }, @@ -78,6 +79,6 @@ { "name": "useBasicDemoData", "kind": "Variable" }, { "name": "useDemoData", "kind": "Variable" }, { "name": "UseDemoDataOptions", "kind": "Interface" }, - { "name": "useDemoDataSource", "kind": "Variable" }, + { "name": "useMockServer", "kind": "Variable" }, { "name": "useMovieData", "kind": "Variable" } ] diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index ed71327c361a..6f6bf5d5c799 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -24,6 +24,7 @@ { "name": "COMPACT_DENSITY_FACTOR", "kind": "Variable" }, { "name": "createUseGridApiEventHandler", "kind": "Function" }, { "name": "CursorCoordinates", "kind": "Interface" }, + { "name": "DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES", "kind": "Variable" }, { "name": "DataGrid", "kind": "Function" }, { "name": "DataGridPremium", "kind": "Variable" }, { "name": "DataGridPremiumProps", "kind": "Interface" }, @@ -39,7 +40,6 @@ { "name": "FocusElement", "kind": "Interface" }, { "name": "FooterPropsOverrides", "kind": "Interface" }, { "name": "FooterRowCountOverrides", "kind": "Interface" }, - { "name": "GET_DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES", "kind": "Variable" }, { "name": "getAggregationFooterRowIdFromGroupId", "kind": "Variable" }, { "name": "GetApplyFilterFn", "kind": "TypeAlias" }, { "name": "GetApplyQuickFilterFn", "kind": "TypeAlias" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index 57c8c5590a0d..363b90ea5b77 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -23,6 +23,7 @@ { "name": "COMPACT_DENSITY_FACTOR", "kind": "Variable" }, { "name": "createUseGridApiEventHandler", "kind": "Function" }, { "name": "CursorCoordinates", "kind": "Interface" }, + { "name": "DATA_GRID_PRO_PROPS_DEFAULT_VALUES", "kind": "Variable" }, { "name": "DataGrid", "kind": "Function" }, { "name": "DataGridPremium", "kind": "Function" }, { "name": "DataGridPro", "kind": "Variable" }, @@ -38,7 +39,6 @@ { "name": "FocusElement", "kind": "Interface" }, { "name": "FooterPropsOverrides", "kind": "Interface" }, { "name": "FooterRowCountOverrides", "kind": "Interface" }, - { "name": "GET_DATA_GRID_PRO_PROPS_DEFAULT_VALUES", "kind": "Variable" }, { "name": "GetApplyFilterFn", "kind": "TypeAlias" }, { "name": "GetApplyQuickFilterFn", "kind": "TypeAlias" }, { "name": "GetColumnForNewFilterArgs", "kind": "Interface" }, From 9d25100e83735e0d6a4594632c428fee90e56c45 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 20 May 2024 15:48:02 +0500 Subject: [PATCH 29/90] Fix broken reference --- .../src/tests/filtering.DataGridPro.test.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx index 283be6d47e95..8c919672114a 100644 --- a/packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx @@ -6,7 +6,7 @@ import { GridLogicOperator, GridPreferencePanelsValue, GridRowModel, - GET_DATA_GRID_PRO_PROPS_DEFAULT_VALUES, + DATA_GRID_PRO_PROPS_DEFAULT_VALUES, useGridApiRef, DataGridPro, GetColumnForNewFilterArgs, @@ -22,9 +22,7 @@ import * as React from 'react'; import { spy } from 'sinon'; import { getColumnHeaderCell, getColumnValues, getSelectInput, grid } from 'test/utils/helperFn'; -const SUBMIT_FILTER_STROKE_TIME = GET_DATA_GRID_PRO_PROPS_DEFAULT_VALUES( - {} as any, -).filterDebounceMs; +const SUBMIT_FILTER_STROKE_TIME = DATA_GRID_PRO_PROPS_DEFAULT_VALUES.filterDebounceMs; const isJSDOM = /jsdom/.test(window.navigator.userAgent); From af771c676eff10c1b4cc09088aa8575d6e35b42e Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 20 May 2024 16:04:43 +0500 Subject: [PATCH 30/90] Minor improvement --- .../useGridServerSideTreeDataPreProcessors.tsx | 15 ++++----------- .../hooks/features/serverSideTreeData/utils.ts | 12 ++++++++---- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx index ef15376943a8..c1f49d2032bc 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx @@ -19,7 +19,7 @@ import { GRID_TREE_DATA_GROUPING_COL_DEF_FORCED_PROPERTIES, } from '../treeData/gridTreeDataGroupColDef'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; -import { skipFiltering } from './utils'; +import { skipFiltering, skipSorting } from './utils'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { GridGroupingColDefOverride, @@ -199,21 +199,14 @@ export const useGridServerSideTreeDataPreProcessors = ( const filterRows = React.useCallback>(() => { const rowTree = gridRowTreeSelector(privateApiRef); - return skipFiltering({ - rowTree, - }); + return skipFiltering(rowTree); }, [privateApiRef]); const sortRows = React.useCallback>( - (params) => { + () => { const rowTree = gridRowTreeSelector(privateApiRef); - return sortRowTree({ - rowTree, - sortRowList: params.sortRowList, - disableChildrenSorting: props.disableChildrenSorting, - shouldRenderGroupBelowLeaves: false, - }); + return skipSorting(rowTree); }, [privateApiRef, props.disableChildrenSorting], ); diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/utils.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/utils.ts index 7423cd731673..e1314f6aef1d 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/utils.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/utils.ts @@ -1,7 +1,7 @@ -import { GridRowId, GridRowTreeConfig } from '@mui/x-data-grid'; +import { GridRowId, GridRowTreeConfig, GRID_ROOT_GROUP_ID } from '@mui/x-data-grid'; +import { getTreeNodeDescendants } from '@mui/x-data-grid/internals'; -export const skipFiltering = (params: { rowTree: GridRowTreeConfig }) => { - const { rowTree } = params; +export function skipFiltering(rowTree: GridRowTreeConfig) { const filteredRowsLookup: Record = {}; const filteredDescendantCountLookup: Record = {}; @@ -15,4 +15,8 @@ export const skipFiltering = (params: { rowTree: GridRowTreeConfig }) => { filteredRowsLookup, filteredDescendantCountLookup, }; -}; +} + +export function skipSorting(rowTree: GridRowTreeConfig) { + return getTreeNodeDescendants(rowTree, GRID_ROOT_GROUP_ID, false); +} From 19a4952d6412f1efc0172399e4540871b646fca5 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 27 May 2024 12:16:20 +0500 Subject: [PATCH 31/90] Minor updates --- .../src/DataGridPremium/useDataGridPremiumProps.ts | 4 +++- .../features/serverSideData/useGridDataSource.ts | 2 +- .../useGridServerSideTreeDataPreProcessors.tsx | 11 ++++------- .../features/pagination/gridPaginationInterfaces.ts | 2 -- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts index 57b22b3b696f..ced59206751d 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts +++ b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts @@ -17,7 +17,9 @@ import { DATA_GRID_PREMIUM_DEFAULT_SLOTS_COMPONENTS } from '../constants/dataGri interface GetDataGridPremiumPropsDefaultValues extends DataGridPremiumProps {} -type DataGridProForcedProps = { [key in keyof DataGridProProps]?: DataGridPremiumProcessedProps[key] }; +type DataGridProForcedProps = { + [key in keyof DataGridProProps]?: DataGridPremiumProcessedProps[key]; +}; type GetDataGridProForcedProps = ( themedProps: GetDataGridPremiumPropsDefaultValues, ) => DataGridProForcedProps; diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts index 2890992cc7e4..6aa472735f0b 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts @@ -185,7 +185,7 @@ export const useGridDataSource = ( nestedDataManager.setRequestSettled(id); privateApiRef.current.setChildrenLoading(id, false); privateApiRef.current.setChildrenFetchError(id, e); - onError?.(e as Error, fetchParams); + onError?.(e, fetchParams); } }, [nestedDataManager, onError, privateApiRef, props.treeData, props.unstable_dataSource?.getRows], diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx index c1f49d2032bc..0909d9d2fd6b 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx @@ -202,14 +202,11 @@ export const useGridServerSideTreeDataPreProcessors = ( return skipFiltering(rowTree); }, [privateApiRef]); - const sortRows = React.useCallback>( - () => { - const rowTree = gridRowTreeSelector(privateApiRef); + const sortRows = React.useCallback>(() => { + const rowTree = gridRowTreeSelector(privateApiRef); - return skipSorting(rowTree); - }, - [privateApiRef, props.disableChildrenSorting], - ); + return skipSorting(rowTree); + }, [privateApiRef, props.disableChildrenSorting]); useGridRegisterPipeProcessor(privateApiRef, 'hydrateColumns', updateGroupingColumn); useGridRegisterStrategyProcessor( diff --git a/packages/x-data-grid/src/hooks/features/pagination/gridPaginationInterfaces.ts b/packages/x-data-grid/src/hooks/features/pagination/gridPaginationInterfaces.ts index 675c540f79a8..a47c14ea4454 100644 --- a/packages/x-data-grid/src/hooks/features/pagination/gridPaginationInterfaces.ts +++ b/packages/x-data-grid/src/hooks/features/pagination/gridPaginationInterfaces.ts @@ -19,13 +19,11 @@ export interface GridPaginationModelApi { /** * Sets the displayed page to the value given by `page`. * @param {number} page The new page number. - * @deprecated Use `setPaginationModel` instead. */ setPage: (page: number) => void; /** * Sets the number of displayed rows to the value given by `pageSize`. * @param {number} pageSize The new number of displayed rows. - * @deprecated Use `setPaginationModel` instead. */ setPageSize: (pageSize: number) => void; /** From 2593dc8c5909cec6b2b44e1134f97f0baee38dcf Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 27 May 2024 12:54:33 +0500 Subject: [PATCH 32/90] lint --- .../server-side-data/LoadingSlateNoSnap.js | 3 ++- .../src/hooks/useMockServer.ts | 15 ++++++--------- .../useGridServerSideTreeDataPreProcessors.tsx | 1 - 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/docs/data/data-grid/server-side-data/LoadingSlateNoSnap.js b/docs/data/data-grid/server-side-data/LoadingSlateNoSnap.js index ddfe4582dfe2..73ad4517bad6 100644 --- a/docs/data/data-grid/server-side-data/LoadingSlateNoSnap.js +++ b/docs/data/data-grid/server-side-data/LoadingSlateNoSnap.js @@ -1,3 +1,4 @@ +import * as React from 'react'; import CircularProgress from '@mui/material/CircularProgress'; import { lighten, darken, alpha, styled } from '@mui/material/styles'; @@ -23,7 +24,7 @@ const StyledDiv = styled('div')(({ theme }) => ({ borderRadius: getBorderRadius(theme), })); -export default function LoadingSlate() { +export default function LoadingSlateNoSnap() { return ( diff --git a/packages/x-data-grid-generator/src/hooks/useMockServer.ts b/packages/x-data-grid-generator/src/hooks/useMockServer.ts index c621390f7150..59cf7073b7a8 100644 --- a/packages/x-data-grid-generator/src/hooks/useMockServer.ts +++ b/packages/x-data-grid-generator/src/hooks/useMockServer.ts @@ -51,6 +51,7 @@ function decodeParams(url: string): GridGetRowsParams { const params = new URL(url).searchParams; const decodedParams = {} as any; const array = Array.from(params.entries()); + // eslint-disable-next-line no-restricted-syntax for (const [key, value] of array) { try { decodedParams[key] = JSON.parse(decodeURIComponent(value)); @@ -228,8 +229,6 @@ export const useMockServer = ( } if (isTreeData /* || TODO: `isRowGrouping` */) { - console.log('requested tree data for groupKeys', params.groupKeys); - const { rows, rootRowCount } = await processTreeDataRows( data.rows, params, @@ -237,11 +236,6 @@ export const useMockServer = ( columnsWithDefaultColDef, ); - console.log('processed tree data for groupKeys', { - rows: rows.slice().map((row) => ({ ...row, path: undefined })), - rowCount: rootRowCount, - }); - getRowsResponse = { rows: rows.slice().map((row) => ({ ...row, path: undefined })), rowCount: rootRowCount, @@ -277,6 +271,7 @@ export const useMockServer = ( } async function startServer() { if (typeof window !== 'undefined') { + // eslint-disable-next-line global-require const { setupWorker } = require('msw/browser'); if (!setupWorker) { return; @@ -307,6 +302,7 @@ export const useMockServer = ( } } startServer(); + // eslint-disable-next-line consistent-return return () => { if (worker) { setWorker((prev) => { @@ -315,13 +311,14 @@ export const useMockServer = ( }); } }; - }, [fetchRows, data, shouldRequestsFail]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [fetchRows, data, shouldRequestsFail, serverOptions?.startServer]); React.useEffect(() => { if (data && (!serverOptions?.startServer || worker) && !isInitialized) { setIsInitialized(true); } - }, [data, worker, isInitialized]); + }, [data, worker, isInitialized, serverOptions?.startServer]); return { columns: columnsWithDefaultColDef, diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx index 0909d9d2fd6b..c93ea0fa3309 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx @@ -31,7 +31,6 @@ import { GridTreePathDuplicateHandler, RowTreeBuilderGroupingCriterion, } from '../../../utils/tree/models'; -import { sortRowTree } from '../../../utils/tree/sortRowTree'; import { updateRowTree } from '../../../utils/tree/updateRowTree'; import { getVisibleRowsLookup } from '../../../utils/tree/utils'; From 444f96082d8a5f5632c3ddea5e560fe5f5072d85 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 27 May 2024 13:19:41 +0500 Subject: [PATCH 33/90] lint --- .../useGridServerSideTreeDataPreProcessors.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx index c93ea0fa3309..959ce41d653b 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx @@ -205,7 +205,7 @@ export const useGridServerSideTreeDataPreProcessors = ( const rowTree = gridRowTreeSelector(privateApiRef); return skipSorting(rowTree); - }, [privateApiRef, props.disableChildrenSorting]); + }, [privateApiRef]); useGridRegisterPipeProcessor(privateApiRef, 'hydrateColumns', updateGroupingColumn); useGridRegisterStrategyProcessor( From ab621a66c3cd4033d9f20100b470425c171b6525 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 27 May 2024 16:45:04 +0500 Subject: [PATCH 34/90] Add verbose mode to 'useMockServer' --- .../server-side-data/ServerSideDataGrid.js | 86 +++++++++++-------- .../server-side-data/ServerSideDataGrid.tsx | 86 +++++++++++-------- docs/data/data-grid/server-side-data/index.md | 4 +- .../src/hooks/useMockServer.ts | 19 +++- 4 files changed, 123 insertions(+), 72 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGrid.js b/docs/data/data-grid/server-side-data/ServerSideDataGrid.js index 848aa80af500..0b75401cf703 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGrid.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGrid.js @@ -1,33 +1,38 @@ import * as React from 'react'; import { DataGridPro } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; +import Checkbox from '@mui/material/Checkbox'; +import FormControlLabel from '@mui/material/FormControlLabel'; import LoadingSlate from './LoadingSlateNoSnap'; -const serverOptions = { useCursorPagination: false, startServer: true }; -const dataSetOptions = {}; +function ServerSideDataGrid() { + const [verbose, setVerbose] = React.useState(false); -const dataSource = { - getRows: async (params) => { - const urlParams = new URLSearchParams({ - paginationModel: encodeURIComponent(JSON.stringify(params.paginationModel)), - filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), - sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), - }); - const serverResponse = await fetch( - `https://mui.com/x/api/data-grid?${urlParams.toString()}`, - ); - const getRowsResponse = await serverResponse.json(); - return { - rows: getRowsResponse.rows, - rowCount: getRowsResponse.rowCount, - }; - }, -}; + const { isInitialized, columns, initialState, fetchRows } = useMockServer( + {}, + { useCursorPagination: false, verbose }, + ); -function ServerSideDataGrid() { - const { isInitialized, columns, initialState } = useMockServer( - dataSetOptions, - serverOptions, + const dataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent( + JSON.stringify(params.paginationModel), + ), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + }), + [fetchRows], ); const initialStateWithPagination = React.useMemo( @@ -42,18 +47,31 @@ function ServerSideDataGrid() { ); return ( -
- {isInitialized ? ( - +
+ setVerbose(e.target.checked)} + /> + } + label="Verbose" /> - ) : ( - - )} +
+
+ {isInitialized ? ( + + ) : ( + + )} +
); } diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx index e4f92cba2795..9a0bee05ed9c 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx @@ -1,33 +1,38 @@ import * as React from 'react'; import { DataGridPro, GridDataSource } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; +import Checkbox from '@mui/material/Checkbox'; +import FormControlLabel from '@mui/material/FormControlLabel'; import LoadingSlate from './LoadingSlateNoSnap'; -const serverOptions = { useCursorPagination: false, startServer: true }; -const dataSetOptions = {}; +function ServerSideDataGrid() { + const [verbose, setVerbose] = React.useState(false); -const dataSource: GridDataSource = { - getRows: async (params) => { - const urlParams = new URLSearchParams({ - paginationModel: encodeURIComponent(JSON.stringify(params.paginationModel)), - filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), - sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), - }); - const serverResponse = await fetch( - `https://mui.com/x/api/data-grid?${urlParams.toString()}`, - ); - const getRowsResponse = await serverResponse.json(); - return { - rows: getRowsResponse.rows, - rowCount: getRowsResponse.rowCount, - }; - }, -}; + const { isInitialized, columns, initialState, fetchRows } = useMockServer( + {}, + { useCursorPagination: false, verbose }, + ); -function ServerSideDataGrid() { - const { isInitialized, columns, initialState } = useMockServer( - dataSetOptions, - serverOptions, + const dataSource: GridDataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent( + JSON.stringify(params.paginationModel), + ), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + }), + [fetchRows], ); const initialStateWithPagination = React.useMemo( @@ -42,18 +47,31 @@ function ServerSideDataGrid() { ); return ( -
- {isInitialized ? ( - +
+ setVerbose(e.target.checked)} + /> + } + label="Verbose" /> - ) : ( - - )} +
+
+ {isInitialized ? ( + + ) : ( + + )} +
); } diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index f7ab6baeb0c2..eea3741b3df9 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -166,7 +166,9 @@ The following demo showcases this behavior. {{"demo": "ServerSideDataGrid.js", "bg": "inline"}} :::info -The demo above uses a utility `msw` to create a mock server that intercepts the actual Network calls and provide data. Open Network tab in the browser's developer tools to see the requests and corresponding responses. +The demos used with server-side data use a utility `useMockServer` coming from the `@mui/x-data-grid-generator` package to simulate the server-side data fetching. + +It supports a property called `verbose`, in the demos below, you can set it using a checkbox. Set it checked to observe the request parameters and the response data coming from the `useMockServer` in the info section of the browser console. ::: ## Data caching diff --git a/packages/x-data-grid-generator/src/hooks/useMockServer.ts b/packages/x-data-grid-generator/src/hooks/useMockServer.ts index 59cf7073b7a8..ce370fd4fab9 100644 --- a/packages/x-data-grid-generator/src/hooks/useMockServer.ts +++ b/packages/x-data-grid-generator/src/hooks/useMockServer.ts @@ -82,7 +82,7 @@ const defaultColDef = getGridDefaultColumnTypes(); export const useMockServer = ( dataSetOptions?: Partial, - serverOptions?: ServerOptions & { startServer?: boolean }, + serverOptions?: ServerOptions & { startServer?: boolean; verbose?: boolean }, shouldRequestsFail?: boolean, ): UseMockServerResponse => { const [isInitialized, setIsInitialized] = React.useState(false); @@ -212,6 +212,12 @@ export const useMockServer = ( }); } const params = decodeParams(requestUrl); + const verbose = serverOptions?.verbose ?? false; + // eslint-disable-next-line no-console + const print = console.info; + if (verbose) { + print('MUI X: SERVER REQUEST RECIEVED WITH PARAMS', params); + } let getRowsResponse: GridGetRowsResponse; const serverOptionsWithDefault = { minDelay: serverOptions?.minDelay ?? DEFAULT_SERVER_OPTIONS.minDelay, @@ -224,6 +230,9 @@ export const useMockServer = ( const { minDelay, maxDelay } = serverOptionsWithDefault; const delay = randomInt(minDelay, maxDelay); return new Promise((_, reject) => { + if (verbose) { + print('MUI X: SERVER REQUEST FAILURE WITH PARAMS', params); + } setTimeout(() => reject(new Error('Could not fetch the data')), delay); }); } @@ -252,16 +261,20 @@ export const useMockServer = ( } return new Promise((resolve) => { + if (verbose) { + print('MUI X: SERVER RESPONSE WITH PARAMS', params, getRowsResponse); + } resolve(getRowsResponse); }); }, [ data, - columnsWithDefaultColDef, - isTreeData, + serverOptions?.verbose, serverOptions?.minDelay, serverOptions?.maxDelay, serverOptions?.useCursorPagination, + isTreeData, + columnsWithDefaultColDef, ], ); From 9c2602b0b7b7fd4bbe70b01160c6ca188088c286 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Tue, 28 May 2024 16:28:00 +0500 Subject: [PATCH 35/90] Rename isServerSide -> hasServerChildren --- .../src/components/GridServerSideTreeDataGroupingCell.tsx | 4 ++-- .../x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts | 2 +- packages/x-data-grid/src/models/gridRows.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx b/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx index fa5b9c804667..9b4bf0dce13c 100644 --- a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx +++ b/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx @@ -68,7 +68,7 @@ function GridTreeDataGroupingCellIcon(props: GridTreeDataGroupingCellIconProps) const isDataLoading = useGridSelector(apiRef, loadingSelector); const error = useGridSelector(apiRef, errorSelector); - const isServerSideNode = rowNode.isServerSide; + const hasServerChildren = rowNode.hasServerChildren; const handleClick = (event: React.MouseEvent) => { if (!rowNode.childrenExpanded) { @@ -92,7 +92,7 @@ function GridTreeDataGroupingCellIcon(props: GridTreeDataGroupingCellIconProps) ); } - return descendantCount > 0 || isServerSideNode ? ( + return descendantCount > 0 || hasServerChildren ? ( Date: Tue, 28 May 2024 16:32:16 +0500 Subject: [PATCH 36/90] Improve NestedDataManager --- .../src/hooks/features/serverSideData/utils.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/utils.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/utils.ts index 1ccea95d7542..939eb6fec2ab 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/utils.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/utils.ts @@ -57,9 +57,11 @@ export class NestedDataManager { return; } const fetchQueue = Array.from(this.queuedRequests); - for (let i = 0; i < this.maxConcurrentRequests; i += 1) { + + const availableSlots = this.maxConcurrentRequests - this.pendingRequests.size; + for (let i = 0; i < availableSlots; i += 1) { const nextId = fetchQueue[i]; - if (!nextId) { + if (typeof nextId === 'undefined') { clearInterval(this.timer); return; } From c072c105a5806c7501f6afdc57a1085c14f5b00d Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Tue, 28 May 2024 16:32:33 +0500 Subject: [PATCH 37/90] Clean proptypes --- .../GridServerSideTreeDataGroupingCell.tsx | 80 +------------------ 1 file changed, 1 insertion(+), 79 deletions(-) diff --git a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx b/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx index 9b4bf0dce13c..fe34e2abfec4 100644 --- a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx +++ b/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import PropTypes from 'prop-types'; import { unstable_composeClasses as composeClasses } from '@mui/utils'; import { styled } from '@mui/system'; import Box from '@mui/material/Box'; @@ -113,7 +112,7 @@ function GridTreeDataGroupingCellIcon(props: GridTreeDataGroupingCellIconProps) ) : null; } -function GridServerSideTreeDataGroupingCell(props: GridTreeDataGroupingCellProps) { +export function GridServerSideTreeDataGroupingCell(props: GridTreeDataGroupingCellProps) { const { id, field, formattedValue, rowNode, hideDescendantCount, offsetMultiplier = 2 } = props; const rootProps = useGridRootProps(); @@ -141,80 +140,3 @@ function GridServerSideTreeDataGroupingCell(props: GridTreeDataGroupingCellProps ); } - -GridServerSideTreeDataGroupingCell.propTypes = { - // ----------------------------- Warning -------------------------------- - // | These PropTypes are generated from the TypeScript type definitions | - // | To update them edit the TypeScript types and run "yarn proptypes" | - // ---------------------------------------------------------------------- - /** - * GridApi that let you manipulate the grid. - */ - api: PropTypes.object.isRequired, - /** - * The mode of the cell. - */ - cellMode: PropTypes.oneOf(['edit', 'view']).isRequired, - /** - * The column of the row that the current cell belongs to. - */ - colDef: PropTypes.object.isRequired, - /** - * The column field of the cell that triggered the event. - */ - field: PropTypes.string.isRequired, - /** - * A ref allowing to set imperative focus. - * It can be passed to the element that should receive focus. - * @ignore - do not document. - */ - focusElementRef: PropTypes.oneOfType([ - PropTypes.func, - PropTypes.shape({ - current: PropTypes.shape({ - focus: PropTypes.func.isRequired, - }), - }), - ]), - /** - * The cell value formatted with the column valueFormatter. - */ - formattedValue: PropTypes.any, - /** - * If true, the cell is the active element. - */ - hasFocus: PropTypes.bool.isRequired, - hideDescendantCount: PropTypes.bool, - /** - * The grid row id. - */ - id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, - /** - * If true, the cell is editable. - */ - isEditable: PropTypes.bool, - /** - * The cell offset multiplier used for calculating cell offset (`rowNode.depth * offsetMultiplier` px). - * @default 2 - */ - offsetMultiplier: PropTypes.number, - /** - * The row model of the row that the current cell belongs to. - */ - row: PropTypes.any.isRequired, - /** - * The node of the row that the current cell belongs to. - */ - rowNode: PropTypes.object.isRequired, - /** - * the tabIndex value. - */ - tabIndex: PropTypes.oneOf([-1, 0]).isRequired, - /** - * The cell value. - * If the column has `valueGetter`, use `params.row` to directly access the fields. - */ - value: PropTypes.any, -} as any; - -export { GridServerSideTreeDataGroupingCell }; From 3b2bb68af4166a53303c86355b463e06ab9ee262 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Tue, 28 May 2024 16:37:41 +0500 Subject: [PATCH 38/90] Add enqueue to handleCellKeyDown --- .../DataGridPremium/useDataGridPremiumComponent.tsx | 2 +- .../src/DataGridPro/useDataGridProComponent.tsx | 2 +- .../src/hooks/features/treeData/useGridTreeData.tsx | 13 +++++++++++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx index 0e46c65113e2..9cbc43d5f9b1 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx @@ -144,7 +144,7 @@ export const useDataGridPremiumComponent = ( useGridRowGrouping(apiRef, props); useGridHeaderFiltering(apiRef, props); - useGridTreeData(apiRef); + useGridTreeData(apiRef, props); useGridAggregation(apiRef, props); useGridKeyboardNavigation(apiRef, props); useGridRowSelection(apiRef, props); diff --git a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx index f29f4e0c4212..b7509be9c97e 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx @@ -132,7 +132,7 @@ export const useDataGridProComponent = ( useGridInitializeState(dataSourceStateInitializer, apiRef, props); useGridHeaderFiltering(apiRef, props); - useGridTreeData(apiRef); + useGridTreeData(apiRef, props); useGridKeyboardNavigation(apiRef, props); useGridRowSelection(apiRef, props); useGridColumnPinning(apiRef, props); 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 f2410669db0f..176c16235cd0 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 @@ -1,9 +1,14 @@ import * as React from 'react'; import { useGridApiEventHandler, GridEventListener } from '@mui/x-data-grid'; import { GridApiPro } from '../../../models/gridApiPro'; +import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; + import { GRID_TREE_DATA_GROUPING_FIELD } from './gridTreeDataGroupColDef'; -export const useGridTreeData = (apiRef: React.MutableRefObject) => { +export const useGridTreeData = ( + apiRef: React.MutableRefObject, + props: Pick, +) => { /** * EVENTS */ @@ -18,11 +23,15 @@ export const useGridTreeData = (apiRef: React.MutableRefObject) => { if (params.rowNode.type !== 'group') { return; } + if (props.unstable_dataSource) { + apiRef.current.enqueueChildrenFetch(params.id); + return; + } apiRef.current.setRowChildrenExpansion(params.id, !params.rowNode.childrenExpanded); } }, - [apiRef], + [apiRef, props.unstable_dataSource], ); useGridApiEventHandler(apiRef, 'cellKeyDown', handleCellKeyDown); From d7ca5805a21669a50a9c0dacaaa01a47e158f829 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 3 Jun 2024 11:42:47 +0500 Subject: [PATCH 39/90] Fix collapsing of nodes using keyboard --- .../src/hooks/features/treeData/useGridTreeData.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 176c16235cd0..a6bf53029a74 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 @@ -23,7 +23,8 @@ export const useGridTreeData = ( if (params.rowNode.type !== 'group') { return; } - if (props.unstable_dataSource) { + + if (props.unstable_dataSource && !params.rowNode.childrenExpanded) { apiRef.current.enqueueChildrenFetch(params.id); return; } From 9ac088f18c54121c2d80e635d96b6433d3eb5fe0 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 3 Jun 2024 11:50:41 +0500 Subject: [PATCH 40/90] Rename enqueueChildrenFetch -> queueChildrenFetch --- .../components/GridServerSideTreeDataGroupingCell.tsx | 2 +- .../features/serverSideData/serverSideInterfaces.ts | 4 ++-- .../hooks/features/serverSideData/useGridDataSource.ts | 10 ++++------ .../src/hooks/features/treeData/useGridTreeData.tsx | 2 +- .../x-data-grid/src/hooks/features/rows/useGridRows.ts | 1 - 5 files changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx b/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx index fe34e2abfec4..ceeba9d7dcc0 100644 --- a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx +++ b/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx @@ -72,7 +72,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.enqueueChildrenFetch(id); + apiRef.current.queueChildrenFetch(id); } else { apiRef.current.setRowChildrenExpansion(id, !rowNode.childrenExpanded); } diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts index 5209a2c9c2c3..c1add56bef6d 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts @@ -22,10 +22,10 @@ export interface GridDataSourceApi { */ fetchTopLevelRows: () => void; /** - * Enqueues the fetch of the children of a row. + * Adds the fetch of the children of a row to queue. * @param {GridRowId} id The id of the rowNode belonging to the group to be fetched. */ - enqueueChildrenFetch: (id: GridRowId) => void; + queueChildrenFetch: (id: GridRowId) => void; } export interface GridDataSourcePrivateApi { diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts index 6aa472735f0b..8f65c80f6cb7 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts @@ -60,10 +60,8 @@ export const useGridDataSource = ( return; } - // Reset the nested data variables - if (nestedDataManager.getActiveRequestsCount() > 0) { - nestedDataManager.clearPendingRequests(); - } + nestedDataManager.clearPendingRequests(); + scheduledGroups.current = 0; const serverSideState = privateApiRef.current.state.serverSideData; if (serverSideState !== INITIAL_STATE) { @@ -107,7 +105,7 @@ export const useGridDataSource = ( } }, [nestedDataManager, privateApiRef, props.unstable_dataSource?.getRows, onError]); - const enqueueChildrenFetch = React.useCallback( + const queueChildrenFetch = React.useCallback( (id: GridRowId) => { nestedDataManager.enqueue([id]); }, @@ -231,7 +229,7 @@ export const useGridDataSource = ( }, [privateApiRef]); const dataSourceApi: GridDataSourceApi = { - enqueueChildrenFetch, + queueChildrenFetch, setChildrenLoading, setChildrenFetchError, fetchTopLevelRows, 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 a6bf53029a74..c56bc146a015 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.enqueueChildrenFetch(params.id); + apiRef.current.queueChildrenFetch(params.id); return; } 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 52a3900ba2e2..286a26cdd408 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts @@ -253,7 +253,6 @@ export const useGridRows = ( rows: { ...state.rows, loading }, })); apiRef.current.caches.rows.loadingPropBeforePartialUpdates = loading; - apiRef.current.forceUpdate(); }, [props.loading, apiRef, logger], ); From 9054f4eee00e014f01e4b6b9861e23e749b70ddd Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 3 Jun 2024 12:09:46 +0500 Subject: [PATCH 41/90] Rename serverSide in related props to dataSource --- .../ServerSideDataGridNoCache.js | 2 +- .../ServerSideDataGridNoCache.tsx | 2 +- .../ServerSideDataGridNoCache.tsx.preview | 2 +- .../ServerSideDataGridWithSWR.js | 2 +- .../ServerSideDataGridWithSWR.tsx | 2 +- .../ServerSideErrorHandling.js | 4 ++-- .../ServerSideErrorHandling.tsx | 4 ++-- .../ServerSideTreeDataCustomCache.js | 2 +- .../ServerSideTreeDataCustomCache.tsx | 2 +- .../ServerSideTreeDataErrorHandling.js | 4 ++-- .../ServerSideTreeDataErrorHandling.tsx | 4 ++-- docs/data/data-grid/server-side-data/index.md | 10 ++++----- .../data-grid/server-side-data/tree-data.md | 4 ++-- .../x/api/data-grid/data-grid-premium.json | 2 +- docs/pages/x/api/data-grid/data-grid-pro.json | 2 +- docs/pages/x/api/data-grid/grid-api.json | 10 ++++----- .../data-grid-premium/data-grid-premium.json | 6 ++--- .../data-grid-pro/data-grid-pro.json | 6 ++--- .../api-docs/data-grid/grid-api.json | 2 +- .../src/DataGridPremium/DataGridPremium.tsx | 14 ++++++------ .../src/DataGridPro/DataGridPro.tsx | 14 ++++++------ .../src/DataGridPro/useDataGridProProps.ts | 2 +- .../serverSideData/useGridDataSource.ts | 6 ++--- .../serverSideData/useGridServerSideCache.ts | 22 +++++++++---------- .../src/models/dataGridProProps.ts | 8 +++---- .../x-data-grid/src/models/gridDataSource.ts | 2 +- 26 files changed, 70 insertions(+), 70 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js index 6093f7bab276..6f4aabe1d1c9 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js @@ -53,7 +53,7 @@ export default function ServerSideDataGridNoCache() { initialState={initialStateWithPagination} columns={columns} unstable_dataSource={dataSource} - disableServerSideCache + disableDataSourceCache pagination pageSizeOptions={pageSizeOptions} /> diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx index c7ad15f1979a..6a841afe44fe 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx @@ -53,7 +53,7 @@ export default function ServerSideDataGridNoCache() { initialState={initialStateWithPagination} columns={columns} unstable_dataSource={dataSource} - disableServerSideCache + disableDataSourceCache pagination pageSizeOptions={pageSizeOptions} /> diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx.preview index 19ffc24ca7c9..3e1802301caf 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx.preview +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx.preview @@ -3,7 +3,7 @@ initialState={initialStateWithPagination} columns={columns} unstable_dataSource={dataSource} - disableServerSideCache + disableDataSourceCache pagination pageSizeOptions={pageSizeOptions} /> diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js index 2ecda51613b6..8122d981383f 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js @@ -72,7 +72,7 @@ function ServerSideDataGridWithSWR() { setError(e.message)} - disableServerSideCache + unstable_onDataSourceError={(e) => setError(e.message)} + disableDataSourceCache apiRef={apiRef} pagination pageSizeOptions={pageSizeOptions} diff --git a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx index 7e8d16471313..b12c01341d2a 100644 --- a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx @@ -118,8 +118,8 @@ export default function ServerSideErrorHandling() { setError(e.message)} - disableServerSideCache + unstable_onDataSourceError={(e) => setError(e.message)} + disableDataSourceCache apiRef={apiRef} pagination pageSizeOptions={pageSizeOptions} diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js index 6fbf545ef889..ee5d715aae47 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js @@ -88,7 +88,7 @@ export default function ServerSideTreeDataCustomCache() { { + unstable_onDataSourceError={(e, params) => { if (!params.groupKeys || params.groupKeys.length === 0) { setRootError(e.message); } else { @@ -100,7 +100,7 @@ export default function ServerSideTreeDataErrorHandling() { ); } }} - disableServerSideCache + disableDataSourceCache apiRef={apiRef} pagination pageSizeOptions={pageSizeOptions} diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx index 95e8a5e87fc9..fb24d484a838 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx @@ -96,7 +96,7 @@ export default function ServerSideTreeDataErrorHandling() { {...props} treeData unstable_dataSource={dataSource} - unstable_onServerSideError={(e, params) => { + unstable_onDataSourceError={(e, params) => { if (!params.groupKeys || params.groupKeys.length === 0) { setRootError(e.message); } else { @@ -105,7 +105,7 @@ export default function ServerSideTreeDataErrorHandling() { ); } }} - disableServerSideCache + disableDataSourceCache apiRef={apiRef} pagination pageSizeOptions={pageSizeOptions} diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index eea3741b3df9..7c5daaf9a718 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -181,10 +181,10 @@ The out-of-the-box cache is a simple in-memory cache that stores the data in a p ### Custom cache -To provide a custom cache, use `unstable_serverSideCache` prop, which could be either written from scratch or based out of another cache library. This prop accepts a generic interface of type `GridServerSideCache`. +To provide a custom cache, use `unstable_dataSourceCache` prop, which could be either written from scratch or based out of another cache library. This prop accepts a generic interface of type `GridDataSourceCache`. ```tsx -export interface GridServerSideCache { +export interface GridDataSourceCache { getKey: (params: GridGetRowsParams) => any; set: (key: any, value: GridGetRowsResponse) => void; get: (key: any) => GridGetRowsResponse | undefined; @@ -197,13 +197,13 @@ The following demo uses cache used by a popular library [`swr`](https://github.c ### Disable caching -To disable the caching on the server-side data, pass the `disableServerSideCache` prop. +To disable the caching on the server-side data, pass the `disableDataSourceCache` prop. ```tsx ``` @@ -211,7 +211,7 @@ To disable the caching on the server-side data, pass the `disableServerSideCache ## Error handling -You could handle the errors with the data source by providing an error handler function using the `unstable_onServerSideError`. It will be called whenever there's an error in fetching the data. +You could handle the errors with the data source by providing an error handler function using the `unstable_onDataSourceError`. It will be called whenever there's an error in fetching the data. The first argument of this function is the error object, and the second argument is the fetch parameters of type `GridGetRowsParams`. 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 266d1d1be4de..8457b6cf96cc 100644 --- a/docs/data/data-grid/server-side-data/tree-data.md +++ b/docs/data/data-grid/server-side-data/tree-data.md @@ -52,7 +52,7 @@ The demo above uses a utility `msw` to create a mock server that intercepts the ## Error handling -For each row group expansion, the data source is called to fetch the children. If an error occurs during the fetch, the grid will display an error message in the row group cell. `unstable_onServerSideError` is also triggered with the error and the fetch params. +For each row group expansion, the data source is called to fetch the children. If an error occurs during the fetch, the grid will display an error message in the row group cell. `unstable_onDataSourceError` is also triggered with the error and the fetch params. The demo below shows a toast apart from the default error message in the grouping cell. Cache has been disabled in this demo for simplicity. @@ -68,7 +68,7 @@ The following demo uses `defaultGroupingExpansionDepth='-1'` to expand all the l ## Custom cache -The data source uses a cache by default to store the fetched data. Use `unstable_serverSideCache` to provide a custom cache to the data source to manage the cache as per your requirements. See more about caching in the [overview section](/x/react-data-grid/server-side-data/#data-caching). +The data source uses a cache by default to store the fetched data. Use `unstable_dataSourceCache` to provide a custom cache to the data source to manage the cache as per your requirements. See more about caching in the [overview section](/x/react-data-grid/server-side-data/#data-caching). The following demo uses `QueryClient` from `@tanstack/react-core` to provide a custom cache to the Grid which could be manipulated on the userland. 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 3979c24db303..5e79dd24c4c2 100644 --- a/docs/pages/x/api/data-grid/data-grid-premium.json +++ b/docs/pages/x/api/data-grid/data-grid-premium.json @@ -58,6 +58,7 @@ "disableColumnResize": { "type": { "name": "bool" }, "default": "false" }, "disableColumnSelector": { "type": { "name": "bool" }, "default": "false" }, "disableColumnSorting": { "type": { "name": "bool" }, "default": "false" }, + "disableDataSourceCache": { "type": { "name": "bool" }, "default": "false" }, "disableDensitySelector": { "type": { "name": "bool" }, "default": "false" }, "disableEval": { "type": { "name": "bool" }, "default": "false" }, "disableMultipleColumnsFiltering": { "type": { "name": "bool" }, "default": "false" }, @@ -68,7 +69,6 @@ }, "disableRowGrouping": { "type": { "name": "bool" }, "default": "false" }, "disableRowSelectionOnClick": { "type": { "name": "bool" }, "default": "false" }, - "disableServerSideCache": { "type": { "name": "bool" }, "default": "false" }, "disableVirtualization": { "type": { "name": "bool" }, "default": "false" }, "editMode": { "type": { "name": "enum", "description": "'cell'
| 'row'" }, 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 0da5c2f1b7cf..672ac1dfe1bb 100644 --- a/docs/pages/x/api/data-grid/data-grid-pro.json +++ b/docs/pages/x/api/data-grid/data-grid-pro.json @@ -45,6 +45,7 @@ "disableColumnResize": { "type": { "name": "bool" }, "default": "false" }, "disableColumnSelector": { "type": { "name": "bool" }, "default": "false" }, "disableColumnSorting": { "type": { "name": "bool" }, "default": "false" }, + "disableDataSourceCache": { "type": { "name": "bool" }, "default": "false" }, "disableDensitySelector": { "type": { "name": "bool" }, "default": "false" }, "disableEval": { "type": { "name": "bool" }, "default": "false" }, "disableMultipleColumnsFiltering": { "type": { "name": "bool" }, "default": "false" }, @@ -54,7 +55,6 @@ "default": "false (`!props.checkboxSelection` for MIT Data Grid)" }, "disableRowSelectionOnClick": { "type": { "name": "bool" }, "default": "false" }, - "disableServerSideCache": { "type": { "name": "bool" }, "default": "false" }, "disableVirtualization": { "type": { "name": "bool" }, "default": "false" }, "editMode": { "type": { "name": "enum", "description": "'cell'
| 'row'" }, diff --git a/docs/pages/x/api/data-grid/grid-api.json b/docs/pages/x/api/data-grid/grid-api.json index 94ef9d20ded6..dc73eff38a0d 100644 --- a/docs/pages/x/api/data-grid/grid-api.json +++ b/docs/pages/x/api/data-grid/grid-api.json @@ -28,11 +28,6 @@ "type": { "description": "(item: GridFilterItem) => void" }, "required": true }, - "enqueueChildrenFetch": { - "type": { "description": "(id: GridRowId) => void" }, - "required": true, - "isProPlan": true - }, "exportDataAsCsv": { "type": { "description": "(options?: GridCsvExportOptions) => void" }, "required": true @@ -256,6 +251,11 @@ "isProPlan": true }, "publishEvent": { "type": { "description": "GridEventPublisher" }, "required": true }, + "queueChildrenFetch": { + "type": { "description": "(id: GridRowId) => void" }, + "required": true, + "isProPlan": true + }, "removeRowGroupingCriteria": { "type": { "description": "(groupingCriteriaField: string) => void" }, "required": true, 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 d9f9eb7ed595..500b5274e14c 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 @@ -81,6 +81,9 @@ "disableColumnSorting": { "description": "If true, the column sorting feature will be disabled." }, + "disableDataSourceCache": { + "description": "If true, the server-side cache will be disabled." + }, "disableDensitySelector": { "description": "If true, the density selector is disabled." }, @@ -100,9 +103,6 @@ "disableRowSelectionOnClick": { "description": "If true, the selection on click on a row or cell is disabled." }, - "disableServerSideCache": { - "description": "If true, the server-side cache will be disabled." - }, "disableVirtualization": { "description": "If true, the virtualization is disabled." }, 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 9866f3e5d288..f8be967124d6 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 @@ -70,6 +70,9 @@ "disableColumnSorting": { "description": "If true, the column sorting feature will be disabled." }, + "disableDataSourceCache": { + "description": "If true, the server-side cache will be disabled." + }, "disableDensitySelector": { "description": "If true, the density selector is disabled." }, @@ -88,9 +91,6 @@ "disableRowSelectionOnClick": { "description": "If true, the selection on click on a row or cell is disabled." }, - "disableServerSideCache": { - "description": "If true, the server-side cache will be disabled." - }, "disableVirtualization": { "description": "If true, the virtualization is disabled." }, diff --git a/docs/translations/api-docs/data-grid/grid-api.json b/docs/translations/api-docs/data-grid/grid-api.json index 3c76cef2f7f8..2767dcf7cfee 100644 --- a/docs/translations/api-docs/data-grid/grid-api.json +++ b/docs/translations/api-docs/data-grid/grid-api.json @@ -10,7 +10,6 @@ "deleteFilterItem": { "description": "Deletes a GridFilterItem." }, - "enqueueChildrenFetch": { "description": "Enqueues the fetch of the children of a row." }, "exportDataAsCsv": { "description": "Downloads and exports a CSV of the grid's data." }, "exportDataAsExcel": { "description": "Downloads and exports an Excel file of the grid's data." @@ -126,6 +125,7 @@ "isRowSelected": { "description": "Determines if a row is selected or not." }, "pinColumn": { "description": "Pins a column to the left or right side of the grid." }, "publishEvent": { "description": "Emits an event." }, + "queueChildrenFetch": { "description": "Adds the fetch of the children of a row to queue." }, "removeRowGroupingCriteria": { "description": "Remove the field from the row grouping model." }, "resetRowHeights": { "description": "Forces the recalculation of the heights of all rows." }, "resize": { diff --git a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx index c94ab0b83666..b60b33ec0122 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx @@ -255,6 +255,11 @@ DataGridPremiumRaw.propTypes = { * @default false */ disableColumnSorting: PropTypes.bool, + /** + * If `true`, the server-side cache will be disabled. + * @default false + */ + disableDataSourceCache: PropTypes.bool, /** * If `true`, the density selector is disabled. * @default false @@ -291,11 +296,6 @@ DataGridPremiumRaw.propTypes = { * @default false */ disableRowSelectionOnClick: PropTypes.bool, - /** - * If `true`, the server-side cache will be disabled. - * @default false - */ - disableServerSideCache: PropTypes.bool, /** * If `true`, the virtualization is disabled. * @default false @@ -1056,13 +1056,13 @@ DataGridPremiumRaw.propTypes = { getRows: PropTypes.func.isRequired, updateRow: PropTypes.func, }), - unstable_onServerSideError: PropTypes.func, - unstable_serverSideCache: PropTypes.shape({ + unstable_dataSourceCache: PropTypes.shape({ clear: PropTypes.func.isRequired, get: PropTypes.func.isRequired, getKey: PropTypes.func.isRequired, set: PropTypes.func.isRequired, }), + unstable_onDataSourceError: PropTypes.func, } as any; interface DataGridPremiumComponent { diff --git a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx index 9fe118c79cea..ce750f8e50d0 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx @@ -222,6 +222,11 @@ DataGridProRaw.propTypes = { * @default false */ disableColumnSorting: PropTypes.bool, + /** + * If `true`, the server-side cache will be disabled. + * @default false + */ + disableDataSourceCache: PropTypes.bool, /** * If `true`, the density selector is disabled. * @default false @@ -253,11 +258,6 @@ DataGridProRaw.propTypes = { * @default false */ disableRowSelectionOnClick: PropTypes.bool, - /** - * If `true`, the server-side cache will be disabled. - * @default false - */ - disableServerSideCache: PropTypes.bool, /** * If `true`, the virtualization is disabled. * @default false @@ -955,11 +955,11 @@ DataGridProRaw.propTypes = { getRows: PropTypes.func.isRequired, updateRow: PropTypes.func, }), - unstable_onServerSideError: PropTypes.func, - unstable_serverSideCache: PropTypes.shape({ + unstable_dataSourceCache: PropTypes.shape({ clear: PropTypes.func.isRequired, get: PropTypes.func.isRequired, getKey: PropTypes.func.isRequired, set: PropTypes.func.isRequired, }), + unstable_onDataSourceError: PropTypes.func, } as any; diff --git a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts index a65dde7e58cc..47b074a2c832 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts +++ b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts @@ -37,7 +37,7 @@ const GET_DATA_GRID_PRO_FORCED_PROPS: GetDataGridProForcedProps = (themedProps) */ export const DATA_GRID_PRO_PROPS_DEFAULT_VALUES: DataGridProPropsWithDefaultValue = { ...DATA_GRID_PROPS_DEFAULT_VALUES, - disableServerSideCache: false, + disableDataSourceCache: false, scrollEndThreshold: 80, treeData: false, defaultGroupingExpansionDepth: 0, diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts index 8f65c80f6cb7..f1013706b622 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts @@ -40,7 +40,7 @@ export const useGridDataSource = ( props: Pick< DataGridProProcessedProps, | 'unstable_dataSource' - | 'unstable_onServerSideError' + | 'unstable_onDataSourceError' | 'sortingMode' | 'filterMode' | 'paginationMode' @@ -52,7 +52,7 @@ export const useGridDataSource = ( ).current; const groupsToAutoFetch = useGridSelector(privateApiRef, gridRowGroupsToFetchSelector); const scheduledGroups = React.useRef(0); - const onError = props.unstable_onServerSideError; + const onError = props.unstable_onDataSourceError; const fetchTopLevelRows = React.useCallback(async () => { const getRows = props.unstable_dataSource?.getRows; @@ -61,7 +61,7 @@ export const useGridDataSource = ( } nestedDataManager.clearPendingRequests(); - + scheduledGroups.current = 0; const serverSideState = privateApiRef.current.state.serverSideData; if (serverSideState !== INITIAL_STATE) { diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridServerSideCache.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridServerSideCache.ts index 76fb92f78875..3dbb47ae1da7 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridServerSideCache.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridServerSideCache.ts @@ -46,42 +46,42 @@ export const useGridServerSideCache = ( privateApiRef: React.MutableRefObject, props: Pick< DataGridProProcessedProps, - 'unstable_dataSource' | 'disableServerSideCache' | 'unstable_serverSideCache' + 'unstable_dataSource' | 'disableDataSourceCache' | 'unstable_dataSourceCache' >, ): void => { const defaultCache = React.useRef(getDefaultCache(new SimpleServerSideCache())); const cache = React.useRef( - props.unstable_serverSideCache || defaultCache.current, + props.unstable_dataSourceCache || defaultCache.current, ); const getCacheData = React.useCallback( (params: GridGetRowsParams) => { - if (props.disableServerSideCache) { + if (props.disableDataSourceCache) { return undefined; } const key = cache.current.getKey(params); return cache.current.get(key); }, - [props.disableServerSideCache], + [props.disableDataSourceCache], ); const setCacheData = React.useCallback( (params: GridGetRowsParams, data: GridGetRowsResponse) => { - if (props.disableServerSideCache) { + if (props.disableDataSourceCache) { return; } const key = cache.current.getKey(params); cache.current.set(key, data); }, - [props.disableServerSideCache], + [props.disableDataSourceCache], ); const clearCache = React.useCallback(() => { - if (props.disableServerSideCache) { + if (props.disableDataSourceCache) { return; } cache.current.clear(); - }, [props.disableServerSideCache]); + }, [props.disableDataSourceCache]); const serverSideCacheApi: GridServerSideCacheApi = { getCacheData, @@ -97,8 +97,8 @@ export const useGridServerSideCache = ( isFirstRender.current = false; return; } - if (props.unstable_serverSideCache) { - cache.current = props.unstable_serverSideCache; + if (props.disableDataSourceCache) { + cache.current = props.disableDataSourceCache; } - }, [props.unstable_serverSideCache]); + }, [props.disableDataSourceCache]); }; diff --git a/packages/x-data-grid-pro/src/models/dataGridProProps.ts b/packages/x-data-grid-pro/src/models/dataGridProProps.ts index 632432a846cc..0e97fd482eea 100644 --- a/packages/x-data-grid-pro/src/models/dataGridProProps.ts +++ b/packages/x-data-grid-pro/src/models/dataGridProProps.ts @@ -17,7 +17,7 @@ import type { GridPinnedColumnFields, DataGridProSharedPropsWithDefaultValue, DataGridProSharedPropsWithoutDefaultValue, - GridServerSideCache, + GridDataSourceCache, GridGetRowsParams, } from '@mui/x-data-grid/internals'; import type { GridPinnedRowsProp } from '../hooks/features/rowPinning'; @@ -116,7 +116,7 @@ export interface DataGridProPropsWithDefaultValue void; + unstable_dataSourceCache?: GridDataSourceCache; + unstable_onDataSourceError?: (error: Error, params: GridGetRowsParams) => void; getGroupKey?: (row: GridValidRowModel) => string; hasChildren?: (row: GridValidRowModel) => boolean; getChildrenCount?: (row: GridValidRowModel) => number; diff --git a/packages/x-data-grid/src/models/gridDataSource.ts b/packages/x-data-grid/src/models/gridDataSource.ts index 9f49692fbf88..9fd1b4257fa0 100644 --- a/packages/x-data-grid/src/models/gridDataSource.ts +++ b/packages/x-data-grid/src/models/gridDataSource.ts @@ -65,7 +65,7 @@ export interface GridDataSource { updateRow?(updatedRow: GridRowModel): Promise; } -export interface GridServerSideCache { +export interface GridDataSourceCache { /** * Provide a key for the cache to be used in `set` and `get` * @param {GridGetRowsParams} params The parameters required to fetch the rows From 1f496d9ff186b70223149f526ad190eea77d23dd Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 3 Jun 2024 12:10:24 +0500 Subject: [PATCH 42/90] Rename variables in useDataGridXProps hooks --- .../src/DataGridPremium/useDataGridPremiumProps.ts | 8 ++++---- .../src/DataGridPro/useDataGridProProps.ts | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts index ced59206751d..2a7c934a6007 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts +++ b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts @@ -24,7 +24,7 @@ type GetDataGridProForcedProps = ( themedProps: GetDataGridPremiumPropsDefaultValues, ) => DataGridProForcedProps; -const GET_DATA_GRID_PREMIUM_FORCED_PROPS: GetDataGridProForcedProps = (themedProps) => ({ +const getDataGridPremiumForcedProps: GetDataGridProForcedProps = (themedProps) => ({ signature: 'DataGridPremium', ...(themedProps.unstable_dataSource ? { @@ -55,7 +55,7 @@ export const DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES: DataGridPremiumPropsWithDef }, }; -const GET_DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES: ( +const getDataGridPremiumDefaultProps: ( themedProps: GetDataGridPremiumPropsDefaultValues, ) => DataGridPremiumPropsWithDefaultValue = (themedProps) => ({ ...DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES, @@ -91,11 +91,11 @@ export const useDataGridPremiumProps = (inProps: DataGridPremiumProps) => { return React.useMemo( () => ({ - ...GET_DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES(themedProps), + ...getDataGridPremiumDefaultProps(themedProps), ...themedProps, localeText, slots, - ...GET_DATA_GRID_PREMIUM_FORCED_PROPS(themedProps), + ...getDataGridPremiumForcedProps(themedProps), }), [themedProps, localeText, slots], ); diff --git a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts index 47b074a2c832..839190daa0ec 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts +++ b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts @@ -21,7 +21,7 @@ type GetDataGridProForcedProps = ( themedProps: GetDataGridProPropsDefaultValues, ) => DataGridProForcedProps; -const GET_DATA_GRID_PRO_FORCED_PROPS: GetDataGridProForcedProps = (themedProps) => ({ +const getDataGridProForcedProps: GetDataGridProForcedProps = (themedProps) => ({ signature: 'DataGridPro', ...(themedProps.unstable_dataSource ? { @@ -53,7 +53,7 @@ export const DATA_GRID_PRO_PROPS_DEFAULT_VALUES: DataGridProPropsWithDefaultValu headerFilters: false, }; -const GET_DATA_GRID_PRO_PROPS_DEFAULT_VALUES: ( +const getDataGridProDefaultProps: ( themedProps: GetDataGridProPropsDefaultValues, ) => DataGridProPropsWithDefaultValue = (themedProps) => ({ ...DATA_GRID_PRO_PROPS_DEFAULT_VALUES, @@ -89,11 +89,11 @@ export const useDataGridProProps = (inProps: DataGr return React.useMemo>( () => ({ - ...GET_DATA_GRID_PRO_PROPS_DEFAULT_VALUES(themedProps), + ...getDataGridProDefaultProps(themedProps), ...themedProps, localeText, slots, - ...GET_DATA_GRID_PRO_FORCED_PROPS(themedProps), + ...getDataGridProForcedProps(themedProps), }), [themedProps, localeText, slots], ); From 21b2da4a6174e52c3e0d61ae6d2541444dce59c2 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 3 Jun 2024 13:11:19 +0500 Subject: [PATCH 43/90] Refactor variables, files, and folders naming --- .../ServerSideTreeDataCustomCache.tsx | 4 +- .../useDataGridPremiumComponent.tsx | 4 +- .../src/models/gridApiPremium.ts | 4 +- .../DataGridPro/useDataGridProComponent.tsx | 6 +-- .../GridServerSideTreeDataGroupingCell.tsx | 4 +- .../gridDataSourceSelector.ts} | 14 +++--- .../interfaces.ts} | 17 +++++-- .../useGridDataSource.ts | 46 +++++++++---------- .../useGridDataSourceCache.ts} | 20 ++++---- .../{serverSideData => dataSource}/utils.ts | 0 .../src/hooks/features/index.ts | 2 +- .../features/serverSideData/interfaces.ts | 10 ---- ...useGridServerSideTreeDataPreProcessors.tsx | 2 +- .../x-data-grid-pro/src/internals/index.ts | 4 +- .../x-data-grid-pro/src/models/gridApiPro.ts | 4 +- .../src/models/gridStatePro.ts | 4 +- packages/x-data-grid-pro/src/models/index.ts | 2 +- .../src/typeOverloads/modules.ts | 4 +- packages/x-data-grid-pro/tsconfig.json | 5 ++ scripts/x-data-grid-premium.exports.json | 6 ++- scripts/x-data-grid-pro.exports.json | 6 ++- 21 files changed, 88 insertions(+), 80 deletions(-) rename packages/x-data-grid-pro/src/hooks/features/{serverSideData/gridServerSideDataSelector.ts => dataSource/gridDataSourceSelector.ts} (69%) rename packages/x-data-grid-pro/src/hooks/features/{serverSideData/serverSideInterfaces.ts => dataSource/interfaces.ts} (84%) rename packages/x-data-grid-pro/src/hooks/features/{serverSideData => dataSource}/useGridDataSource.ts (86%) rename packages/x-data-grid-pro/src/hooks/features/{serverSideData/useGridServerSideCache.ts => dataSource/useGridDataSourceCache.ts} (83%) rename packages/x-data-grid-pro/src/hooks/features/{serverSideData => dataSource}/utils.ts (100%) delete mode 100644 packages/x-data-grid-pro/src/hooks/features/serverSideData/interfaces.ts diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx index 15fab52103bc..b2e48e8e9410 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx @@ -4,7 +4,7 @@ import { useGridApiRef, GridInitialState, GridToolbar, - GridServerSideCache, + GridDataSourceCache, GridDataSource, } from '@mui/x-data-grid-pro'; import Button from '@mui/material/Button'; @@ -20,7 +20,7 @@ const queryClient = new QueryClient({ }, }); -const cache: GridServerSideCache = { +const cache: GridDataSourceCache = { set: (key: any[], value) => { queryClient.setQueryData(key, value); }, diff --git a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx index 9cbc43d5f9b1..07bccdb92c87 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx @@ -68,7 +68,7 @@ import { useGridServerSideTreeDataPreProcessors, useGridDataSource, dataSourceStateInitializer, - useGridServerSideCache, + useGridDataSourceCache, } from '@mui/x-data-grid-pro/internals'; import { GridApiPremium, GridPrivateApiPremium } from '../models/gridApiPremium'; import { DataGridPremiumProcessedProps } from '../models/dataGridPremiumProps'; @@ -181,7 +181,7 @@ export const useDataGridPremiumComponent = ( useGridEvents(apiRef, props); useGridStatePersistence(apiRef); useGridDataSource(apiRef, props); - useGridServerSideCache(apiRef, props); + useGridDataSourceCache(apiRef, props); useGridVirtualization(apiRef, props); return apiRef; diff --git a/packages/x-data-grid-premium/src/models/gridApiPremium.ts b/packages/x-data-grid-premium/src/models/gridApiPremium.ts index 4300169af23b..063b21dc6718 100644 --- a/packages/x-data-grid-premium/src/models/gridApiPremium.ts +++ b/packages/x-data-grid-premium/src/models/gridApiPremium.ts @@ -9,7 +9,7 @@ import { GridColumnReorderApi, GridRowProApi, GridDataSourceApi, - GridServerSideCacheApi, + GridDataSourceCacheApi, GridDataSourcePrivateApi, } from '@mui/x-data-grid-pro'; import { GridInitialStatePremium, GridStatePremium } from './gridStatePremium'; @@ -31,7 +31,7 @@ export interface GridApiPremium GridAggregationApi, GridRowPinningApi, GridDataSourceApi, - GridServerSideCacheApi, + GridDataSourceCacheApi, GridCellSelectionApi, // APIs that are private in Community plan, but public in Pro and Premium plans GridRowMultiSelectionApi, diff --git a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx index b7509be9c97e..8ab0846c7dcf 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx @@ -81,8 +81,8 @@ import { useGridRowPinningPreProcessors } from '../hooks/features/rowPinning/use import { useGridDataSource, dataSourceStateInitializer, -} from '../hooks/features/serverSideData/useGridDataSource'; -import { useGridServerSideCache } from '../hooks/features/serverSideData/useGridServerSideCache'; +} from '../hooks/features/dataSource/useGridDataSource'; +import { useGridDataSourceCache } from '../hooks/features/dataSource/useGridDataSourceCache'; export const useDataGridProComponent = ( inputApiRef: React.MutableRefObject | undefined, @@ -166,7 +166,7 @@ export const useDataGridProComponent = ( useGridStatePersistence(apiRef); useGridVirtualization(apiRef, props); useGridDataSource(apiRef, props); - useGridServerSideCache(apiRef, props); + useGridDataSourceCache(apiRef, props); return apiRef; }; diff --git a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx b/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx index ceeba9d7dcc0..34980b0bc997 100644 --- a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx +++ b/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx @@ -57,11 +57,11 @@ function GridTreeDataGroupingCellIcon(props: GridTreeDataGroupingCellIconProps) const { rowNode, id, field, descendantCount } = props; const loadingSelector = React.useCallback( - (state: GridStatePro) => state.serverSideData.loading[id] ?? false, + (state: GridStatePro) => state.dataSource.loading[id] ?? false, [id], ); const errorSelector = React.useCallback( - (state: GridStatePro) => state.serverSideData.errors[id] ?? null, + (state: GridStatePro) => state.dataSource.errors[id] ?? null, [id], ); const isDataLoading = useGridSelector(apiRef, loadingSelector); diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/gridServerSideDataSelector.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts similarity index 69% rename from packages/x-data-grid-pro/src/hooks/features/serverSideData/gridServerSideDataSelector.ts rename to packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts index 425273bc0b0b..a7bee9eec1d9 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/gridServerSideDataSelector.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts @@ -30,14 +30,14 @@ export const gridGetRowsParamsSelector = createSelector( }, ); -export const gridServerSideDataStateSelector = (state: GridStatePro) => state.serverSideData; +export const gridDataSourceStateSelector = (state: GridStatePro) => state.dataSource; -export const gridServerSideDataLoadingSelector = createSelector( - gridServerSideDataStateSelector, - (serverSideData) => serverSideData.loading, +export const gridDataSourceLoadingSelector = createSelector( + gridDataSourceStateSelector, + (dataSource) => dataSource.loading, ); -export const gridServerSideDataErrorsSelector = createSelector( - gridServerSideDataStateSelector, - (serverSideData) => serverSideData.errors, +export const gridDataSourceErrorsSelector = createSelector( + gridDataSourceStateSelector, + (dataSource) => dataSource.errors, ); diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/interfaces.ts similarity index 84% rename from packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts rename to packages/x-data-grid-pro/src/hooks/features/dataSource/interfaces.ts index c1add56bef6d..9b3d66da086e 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/serverSideInterfaces.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/interfaces.ts @@ -1,6 +1,15 @@ import { GridRowId } from '@mui/x-data-grid'; import { GridGetRowsParams, GridGetRowsResponse } from '../../../models'; +export interface GridDataSourceInternalCache { + groupKeys: any[]; +} + +export interface GridDataSourceState { + loading: Record; + errors: Record; +} + /** * The dataSource API interface that is available in the grid [[apiRef]]. */ @@ -35,15 +44,15 @@ export interface GridDataSourcePrivateApi { */ fetchRowChildren: (id: GridRowId) => void; /** - * Resets the server side state. + * Resets the data source state. */ - resetServerSideState: () => void; + resetDataSourceState: () => void; } /** - * The server side cache API interface that is available in the grid [[apiRef]]. + * The data source cache API interface that is available in the grid [[apiRef]]. */ -export interface GridServerSideCacheApi { +export interface GridDataSourceCacheApi { /** * Get data from the cache * @param {GridGetRowsParams} params The params of type `GridGetRowsParams`. diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts similarity index 86% rename from packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts rename to packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index f1013706b622..84da4d37964c 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -13,10 +13,10 @@ import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; import { gridGetRowsParamsSelector, - gridServerSideDataLoadingSelector, - gridServerSideDataErrorsSelector, -} from './gridServerSideDataSelector'; -import { GridDataSourceApi, GridDataSourcePrivateApi } from './serverSideInterfaces'; + gridDataSourceLoadingSelector, + gridDataSourceErrorsSelector, +} from './gridDataSourceSelector'; +import { GridDataSourceApi, GridDataSourcePrivateApi } from './interfaces'; import { runIfServerMode, NestedDataManager, RequestStatus } from './utils'; const INITIAL_STATE = { @@ -25,13 +25,13 @@ const INITIAL_STATE = { }; export const dataSourceStateInitializer: GridStateInitializer = (state, _, apiRef) => { - apiRef.current.caches.serverSideData = { + apiRef.current.caches.dataSource = { groupKeys: [], }; return { ...state, - serverSideData: INITIAL_STATE, + dataSource: INITIAL_STATE, }; }; @@ -63,9 +63,9 @@ export const useGridDataSource = ( nestedDataManager.clearPendingRequests(); scheduledGroups.current = 0; - const serverSideState = privateApiRef.current.state.serverSideData; + const serverSideState = privateApiRef.current.state.dataSource; if (serverSideState !== INITIAL_STATE) { - privateApiRef.current.resetServerSideState(); + privateApiRef.current.resetDataSourceState(); } const fetchParams = gridGetRowsParamsSelector(privateApiRef); @@ -76,7 +76,7 @@ export const useGridDataSource = ( if (cachedData != null) { const rows = cachedData.rows; - privateApiRef.current.caches.serverSideData.groupKeys = []; + privateApiRef.current.caches.dataSource.groupKeys = []; privateApiRef.current.setRows(rows); if (cachedData.rowCount) { privateApiRef.current.setRowCount(cachedData.rowCount); @@ -95,7 +95,7 @@ export const useGridDataSource = ( if (getRowsResponse.rowCount) { privateApiRef.current.setRowCount(getRowsResponse.rowCount); } - privateApiRef.current.caches.serverSideData.groupKeys = []; + privateApiRef.current.caches.dataSource.groupKeys = []; privateApiRef.current.setRows(getRowsResponse.rows); privateApiRef.current.setLoading(false); } catch (error) { @@ -136,7 +136,7 @@ export const useGridDataSource = ( if (cachedData != null) { const rows = cachedData.rows; - privateApiRef.current.caches.serverSideData.groupKeys = rowNode.path; + privateApiRef.current.caches.dataSource.groupKeys = rowNode.path; nestedDataManager.setRequestSettled(id); privateApiRef.current.updateRows(rows, false); if (cachedData.rowCount) { @@ -146,12 +146,12 @@ export const useGridDataSource = ( return; } - const isLoading = gridServerSideDataLoadingSelector(privateApiRef)[id] ?? false; + const isLoading = gridDataSourceLoadingSelector(privateApiRef)[id] ?? false; if (!isLoading) { privateApiRef.current.setChildrenLoading(id, true); } - const existingError = gridServerSideDataErrorsSelector(privateApiRef)[id] ?? null; + const existingError = gridDataSourceErrorsSelector(privateApiRef)[id] ?? null; if (existingError) { privateApiRef.current.setChildrenFetchError(id, null); } @@ -174,7 +174,7 @@ export const useGridDataSource = ( if (getRowsResponse.rowCount) { privateApiRef.current.setRowCount(getRowsResponse.rowCount); } - privateApiRef.current.caches.serverSideData.groupKeys = rowNode.path; + privateApiRef.current.caches.dataSource.groupKeys = rowNode.path; privateApiRef.current.updateRows(getRowsResponse.rows, false); privateApiRef.current.setRowChildrenExpansion(id, true); privateApiRef.current.setChildrenLoading(id, false); @@ -194,9 +194,9 @@ export const useGridDataSource = ( privateApiRef.current.setState((state) => { return { ...state, - serverSideData: { - ...state.serverSideData, - loading: { ...state.serverSideData.loading, [parentId]: isLoading }, + dataSource: { + ...state.dataSource, + loading: { ...state.dataSource.loading, [parentId]: isLoading }, }, }; }); @@ -209,9 +209,9 @@ export const useGridDataSource = ( privateApiRef.current.setState((state) => { return { ...state, - serverSideData: { - ...state.serverSideData, - errors: { ...state.serverSideData.errors, [parentId]: error }, + dataSource: { + ...state.dataSource, + errors: { ...state.dataSource.errors, [parentId]: error }, }, }; }); @@ -219,11 +219,11 @@ export const useGridDataSource = ( [privateApiRef], ); - const resetServerSideState = React.useCallback(() => { + const resetDataSourceState = React.useCallback(() => { privateApiRef.current.setState((state) => { return { ...state, - serverSideData: INITIAL_STATE, + dataSource: INITIAL_STATE, }; }); }, [privateApiRef]); @@ -237,7 +237,7 @@ export const useGridDataSource = ( const dataSourcePrivateApi: GridDataSourcePrivateApi = { fetchRowChildren, - resetServerSideState, + resetDataSourceState, }; useGridApiMethod(privateApiRef, dataSourceApi, 'public'); diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridServerSideCache.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts similarity index 83% rename from packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridServerSideCache.ts rename to packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts index 3dbb47ae1da7..384f3daa107b 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/useGridServerSideCache.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts @@ -2,8 +2,8 @@ import * as React from 'react'; import { useGridApiMethod } from '@mui/x-data-grid'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; -import { GridGetRowsParams, GridGetRowsResponse, GridServerSideCache } from '../../../models'; -import { GridServerSideCacheApi } from './serverSideInterfaces'; +import { GridGetRowsParams, GridGetRowsResponse, GridDataSourceCache } from '../../../models'; +import { GridDataSourceCacheApi } from './interfaces'; class SimpleServerSideCache { private cache: Record; @@ -34,7 +34,7 @@ class SimpleServerSideCache { } } -const getDefaultCache = (cacheInstance: SimpleServerSideCache): GridServerSideCache => ({ +const getDefaultCache = (cacheInstance: SimpleServerSideCache): GridDataSourceCache => ({ getKey: SimpleServerSideCache.getKey, set: (key: string, value: GridGetRowsResponse) => cacheInstance.set(key as string, value as GridGetRowsResponse), @@ -42,7 +42,7 @@ const getDefaultCache = (cacheInstance: SimpleServerSideCache): GridServerSideCa clear: () => cacheInstance.clear(), }); -export const useGridServerSideCache = ( +export const useGridDataSourceCache = ( privateApiRef: React.MutableRefObject, props: Pick< DataGridProProcessedProps, @@ -50,7 +50,7 @@ export const useGridServerSideCache = ( >, ): void => { const defaultCache = React.useRef(getDefaultCache(new SimpleServerSideCache())); - const cache = React.useRef( + const cache = React.useRef( props.unstable_dataSourceCache || defaultCache.current, ); @@ -83,13 +83,13 @@ export const useGridServerSideCache = ( cache.current.clear(); }, [props.disableDataSourceCache]); - const serverSideCacheApi: GridServerSideCacheApi = { + const dataSourceCacheApi: GridDataSourceCacheApi = { getCacheData, setCacheData, clearCache, }; - useGridApiMethod(privateApiRef, serverSideCacheApi, 'public'); + useGridApiMethod(privateApiRef, dataSourceCacheApi, 'public'); const isFirstRender = React.useRef(true); React.useEffect(() => { @@ -97,8 +97,8 @@ export const useGridServerSideCache = ( isFirstRender.current = false; return; } - if (props.disableDataSourceCache) { - cache.current = props.disableDataSourceCache; + if (props.unstable_dataSourceCache) { + cache.current = props.unstable_dataSourceCache; } - }, [props.disableDataSourceCache]); + }, [props.unstable_dataSourceCache]); }; diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/utils.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts similarity index 100% rename from packages/x-data-grid-pro/src/hooks/features/serverSideData/utils.ts rename to packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts 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 ff5e58de2cb7..6b165b46f80e 100644 --- a/packages/x-data-grid-pro/src/hooks/features/index.ts +++ b/packages/x-data-grid-pro/src/hooks/features/index.ts @@ -5,4 +5,4 @@ export * from './rowReorder'; export * from './treeData'; export * from './detailPanel'; export * from './rowPinning'; -export * from './serverSideData/serverSideInterfaces'; +export * from './dataSource/interfaces'; diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideData/interfaces.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideData/interfaces.ts deleted file mode 100644 index 921debb8fcd5..000000000000 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideData/interfaces.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { GridRowId } from '@mui/x-data-grid'; - -export interface GridServerSideDataInternalCache { - groupKeys: any[]; -} - -export interface GridServerSideDataState { - loading: Record; - errors: Record; -} diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx index 959ce41d653b..4c936f5ee4e5 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx @@ -141,7 +141,7 @@ export const useGridServerSideTreeDataPreProcessors = ( throw new Error('MUI X: No `hasChildren` prop provided.'); } - const parentPath = privateApiRef.current.caches.serverSideData?.groupKeys || []; + const parentPath = privateApiRef.current.caches.dataSource?.groupKeys || []; const getRowTreeBuilderNode = (rowId: GridRowId) => ({ id: rowId, diff --git a/packages/x-data-grid-pro/src/internals/index.ts b/packages/x-data-grid-pro/src/internals/index.ts index bb128e661545..c33c90fb441b 100644 --- a/packages/x-data-grid-pro/src/internals/index.ts +++ b/packages/x-data-grid-pro/src/internals/index.ts @@ -40,8 +40,8 @@ export { useGridLazyLoaderPreProcessors } from '../hooks/features/lazyLoader/use export { useGridDataSource, dataSourceStateInitializer, -} from '../hooks/features/serverSideData/useGridDataSource'; -export { useGridServerSideCache } from '../hooks/features/serverSideData/useGridServerSideCache'; +} from '../hooks/features/dataSource/useGridDataSource'; +export { useGridDataSourceCache } from '../hooks/features/dataSource/useGridDataSourceCache'; export type { GridExperimentalProFeatures, diff --git a/packages/x-data-grid-pro/src/models/gridApiPro.ts b/packages/x-data-grid-pro/src/models/gridApiPro.ts index dd538df80bb9..db3ddac8907a 100644 --- a/packages/x-data-grid-pro/src/models/gridApiPro.ts +++ b/packages/x-data-grid-pro/src/models/gridApiPro.ts @@ -13,7 +13,7 @@ import type { GridDetailPanelPrivateApi, GridDataSourceApi, GridDataSourcePrivateApi, - GridServerSideCacheApi, + GridDataSourceCacheApi, } from '../hooks'; import type { DataGridProProcessedProps } from './dataGridProProps'; @@ -27,7 +27,7 @@ export interface GridApiPro GridDetailPanelApi, GridRowPinningApi, GridDataSourceApi, - GridServerSideCacheApi, + GridDataSourceCacheApi, // APIs that are private in Community plan, but public in Pro and Premium plans GridRowMultiSelectionApi, GridColumnReorderApi {} diff --git a/packages/x-data-grid-pro/src/models/gridStatePro.ts b/packages/x-data-grid-pro/src/models/gridStatePro.ts index 4e6d27dc05ce..e26694abd3e8 100644 --- a/packages/x-data-grid-pro/src/models/gridStatePro.ts +++ b/packages/x-data-grid-pro/src/models/gridStatePro.ts @@ -9,7 +9,7 @@ import type { GridDetailPanelInitialState, GridColumnReorderState, } from '../hooks'; -import type { GridServerSideDataState } from '../hooks/features/serverSideData/interfaces'; +import type { GridDataSourceState } from '../hooks/features/dataSource/interfaces'; /** * The state of `DataGridPro`. @@ -18,7 +18,7 @@ export interface GridStatePro extends GridStateCommunity { columnReorder: GridColumnReorderState; pinnedColumns: GridColumnPinningState; detailPanel: GridDetailPanelState; - serverSideData: GridServerSideDataState; + dataSource: GridDataSourceState; } /** diff --git a/packages/x-data-grid-pro/src/models/index.ts b/packages/x-data-grid-pro/src/models/index.ts index 652e6f63d6d5..8110b6c70a91 100644 --- a/packages/x-data-grid-pro/src/models/index.ts +++ b/packages/x-data-grid-pro/src/models/index.ts @@ -2,7 +2,7 @@ export type { GridGetRowsParams, GridGetRowsResponse, GridDataSource, - GridServerSideCache, + GridDataSourceCache, } from '@mui/x-data-grid/internals'; export * from './gridApiPro'; export * from './gridGroupingColDefOverride'; diff --git a/packages/x-data-grid-pro/src/typeOverloads/modules.ts b/packages/x-data-grid-pro/src/typeOverloads/modules.ts index e0d17c9e5e03..78a9d75d5e0f 100644 --- a/packages/x-data-grid-pro/src/typeOverloads/modules.ts +++ b/packages/x-data-grid-pro/src/typeOverloads/modules.ts @@ -5,7 +5,7 @@ import type { GridFetchRowsParams, } from '../models'; import type { GridRenderHeaderFilterProps } from '../components/headerFiltering/GridHeaderFilterCell'; -import type { GridServerSideDataInternalCache } from '../hooks/features/serverSideData/interfaces'; +import type { GridDataSourceInternalCache } from '../hooks/features/dataSource/interfaces'; import type { GridColumnPinningInternalCache } from '../hooks/features/columnPinning/gridColumnPinningInterface'; import type { GridCanBeReorderedPreProcessingContext } from '../hooks/features/columnReorder/columnReorderInterfaces'; import { GridRowPinningInternalCache } from '../hooks/features/rowPinning/gridRowPinningInterface'; @@ -57,7 +57,7 @@ export interface GridPipeProcessingLookupPro { export interface GridApiCachesPro { columnPinning: GridColumnPinningInternalCache; pinnedRows: GridRowPinningInternalCache; - serverSideData: GridServerSideDataInternalCache; + dataSource: GridDataSourceInternalCache; } declare module '@mui/x-data-grid' { diff --git a/packages/x-data-grid-pro/tsconfig.json b/packages/x-data-grid-pro/tsconfig.json index aba8438e2441..4915ae435f4e 100644 --- a/packages/x-data-grid-pro/tsconfig.json +++ b/packages/x-data-grid-pro/tsconfig.json @@ -1,6 +1,11 @@ { "extends": "../../tsconfig.json", "compilerOptions": { + "jsx": "react", + "jsx": "react", + "jsx": "react", + "jsx": "react", + "jsx": "react", "types": [ "@mui/internal-test-utils/initMatchers", "@mui/material/themeCssVarsAugmentation", diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index c2caa378fc4c..081ab74cbc85 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -246,7 +246,11 @@ { "name": "gridDataRowIdsSelector", "kind": "Variable" }, { "name": "GridDataSource", "kind": "Interface" }, { "name": "GridDataSourceApi", "kind": "Interface" }, + { "name": "GridDataSourceCache", "kind": "Interface" }, + { "name": "GridDataSourceCacheApi", "kind": "Interface" }, + { "name": "GridDataSourceInternalCache", "kind": "Interface" }, { "name": "GridDataSourcePrivateApi", "kind": "Interface" }, + { "name": "GridDataSourceState", "kind": "Interface" }, { "name": "gridDateComparator", "kind": "Variable" }, { "name": "gridDateFormatter", "kind": "Variable" }, { "name": "gridDateTimeFormatter", "kind": "Variable" }, @@ -542,8 +546,6 @@ { "name": "GridSearchIcon", "kind": "Variable" }, { "name": "GridSelectedRowCount", "kind": "Variable" }, { "name": "GridSeparatorIcon", "kind": "Variable" }, - { "name": "GridServerSideCache", "kind": "Interface" }, - { "name": "GridServerSideCacheApi", "kind": "Interface" }, { "name": "GridServerSideGroupNode", "kind": "Interface" }, { "name": "GridSignature", "kind": "Enum" }, { "name": "GridSingleSelectColDef", "kind": "Interface" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index 6fa3911f02ea..6c6e0d00f09a 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -220,7 +220,11 @@ { "name": "gridDataRowIdsSelector", "kind": "Variable" }, { "name": "GridDataSource", "kind": "Interface" }, { "name": "GridDataSourceApi", "kind": "Interface" }, + { "name": "GridDataSourceCache", "kind": "Interface" }, + { "name": "GridDataSourceCacheApi", "kind": "Interface" }, + { "name": "GridDataSourceInternalCache", "kind": "Interface" }, { "name": "GridDataSourcePrivateApi", "kind": "Interface" }, + { "name": "GridDataSourceState", "kind": "Interface" }, { "name": "gridDateComparator", "kind": "Variable" }, { "name": "gridDateFormatter", "kind": "Variable" }, { "name": "gridDateTimeFormatter", "kind": "Variable" }, @@ -496,8 +500,6 @@ { "name": "GridSearchIcon", "kind": "Variable" }, { "name": "GridSelectedRowCount", "kind": "Variable" }, { "name": "GridSeparatorIcon", "kind": "Variable" }, - { "name": "GridServerSideCache", "kind": "Interface" }, - { "name": "GridServerSideCacheApi", "kind": "Interface" }, { "name": "GridServerSideGroupNode", "kind": "Interface" }, { "name": "GridSignature", "kind": "Enum" }, { "name": "GridSingleSelectColDef", "kind": "Interface" }, From 12cedf8ce8962dba6a0af96d850263b6c310d7e5 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 3 Jun 2024 13:22:40 +0500 Subject: [PATCH 44/90] Rename enums --- .../src/hooks/features/dataSource/useGridDataSource.ts | 3 +-- .../src/hooks/features/dataSource/utils.ts | 8 ++++---- 2 files changed, 5 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 84da4d37964c..c11362253fae 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 @@ -164,8 +164,7 @@ export const useGridDataSource = ( privateApiRef.current.setChildrenLoading(id, false); return; } - if (nestedDataManager.getRequestStatus(id) === RequestStatus.UNKNOWN) { - // Unregistered or cancelled request + if (nestedDataManager.getRequestStatus(id) === RequestStatus.INVALID) { privateApiRef.current.setChildrenLoading(id, false); return; } 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 939eb6fec2ab..427fabc989a4 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 @@ -12,10 +12,10 @@ export const runIfServerMode = (modeProp: 'server' | 'client', fn: Function) => }; export enum RequestStatus { - INQUEUE, + QUEUED, PENDING, SETTLED, - UNKNOWN, + INVALID, } /** @@ -110,12 +110,12 @@ export class NestedDataManager { return RequestStatus.PENDING; } if (this.queuedRequests.has(id)) { - return RequestStatus.INQUEUE; + return RequestStatus.QUEUED; } if (this.settledRequests.has(id)) { return RequestStatus.SETTLED; } - return RequestStatus.UNKNOWN; + return RequestStatus.INVALID; }; public getActiveRequestsCount = () => this.pendingRequests.size + this.queuedRequests.size; From 0d9026c0eb472f1303825959ac0404cf394c4701 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 3 Jun 2024 16:45:39 +0500 Subject: [PATCH 45/90] Move relevant props to dataSource interface --- .../server-side-data/ServerSideTreeData.js | 21 ++++---- .../server-side-data/ServerSideTreeData.tsx | 21 ++++---- .../ServerSideTreeDataCustomCache.js | 14 ++++-- .../ServerSideTreeDataCustomCache.tsx | 14 ++++-- .../ServerSideTreeDataErrorHandling.js | 14 ++++-- .../ServerSideTreeDataErrorHandling.tsx | 14 ++++-- .../ServerSideTreeDataGroupExpansion.js | 24 ++++----- .../ServerSideTreeDataGroupExpansion.tsx | 26 ++++------ ...rverSideTreeDataGroupExpansion.tsx.preview | 15 ++++++ .../data-grid/server-side-data/tree-data.md | 50 +++++++------------ docs/data/pages.ts | 3 +- .../src/DataGridPremium/DataGridPremium.tsx | 6 +-- .../src/DataGridPro/DataGridPro.tsx | 6 +-- .../GridServerSideTreeDataGroupingCell.tsx | 2 +- ...useGridServerSideTreeDataPreProcessors.tsx | 19 ++++--- .../src/models/dataGridProProps.ts | 3 -- .../x-data-grid/src/models/gridDataSource.ts | 18 +++++++ 17 files changed, 148 insertions(+), 122 deletions(-) create mode 100644 docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx.preview diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.js b/docs/data/data-grid/server-side-data/ServerSideTreeData.js index 8a5aa525ac23..1ef630c3c0c9 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.js @@ -5,6 +5,11 @@ import { useMockServer } from '@mui/x-data-grid-generator'; import LoadingSlate from './LoadingSlateNoSnap'; const pageSizeOptions = [5, 10, 50]; +const dataSetOptions = { + dataSet: 'Employee', + rowLength: 1000, + treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, +}; const dataSource = { getRows: async (params) => { @@ -23,21 +28,17 @@ const dataSource = { rowCount: getRowsResponse.rowCount, }; }, + getGroupKey: (row) => row[dataSetOptions.treeData.groupingField], + hasChildren: (row) => row.hasChildren, + getChildrenCount: (row) => row.descendantCount, }; export default function ServerSideTreeData() { const apiRef = useGridApiRef(); - const { isInitialized, ...props } = useMockServer( - { - dataSet: 'Employee', - rowLength: 1000, - treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, - }, - { - startServer: true, - }, - ); + const { isInitialized, ...props } = useMockServer(dataSetOptions, { + startServer: true, + }); const initialState = React.useMemo( () => ({ diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx index 142013bde8bb..727d88d2893c 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx @@ -11,6 +11,11 @@ import { useMockServer } from '@mui/x-data-grid-generator'; import LoadingSlate from './LoadingSlateNoSnap'; const pageSizeOptions = [5, 10, 50]; +const dataSetOptions = { + dataSet: 'Employee' as 'Employee', + rowLength: 1000, + treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, +}; const dataSource: GridDataSource = { getRows: async (params) => { @@ -29,21 +34,17 @@ const dataSource: GridDataSource = { rowCount: getRowsResponse.rowCount, }; }, + getGroupKey: (row) => row[dataSetOptions.treeData.groupingField], + hasChildren: (row) => row.hasChildren, + getChildrenCount: (row) => row.descendantCount, }; export default function ServerSideTreeData() { const apiRef = useGridApiRef(); - const { isInitialized, ...props } = useMockServer( - { - dataSet: 'Employee', - rowLength: 1000, - treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, - }, - { - startServer: true, - }, - ); + const { isInitialized, ...props } = useMockServer(dataSetOptions, { + startServer: true, + }); const initialState: GridInitialState = React.useMemo( () => ({ diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js index ee5d715aae47..e0e045524289 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js @@ -34,15 +34,16 @@ const cache = { }; const pageSizeOptions = [5, 10, 50]; +const dataSetOptions = { + dataSet: 'Employee', + rowLength: 1000, + treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, +}; export default function ServerSideTreeDataCustomCache() { const apiRef = useGridApiRef(); - const { isInitialized, fetchRows, ...props } = useMockServer({ - dataSet: 'Employee', - rowLength: 1000, - treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, - }); + const { isInitialized, fetchRows, ...props } = useMockServer(dataSetOptions); const dataSource = React.useMemo( () => ({ @@ -63,6 +64,9 @@ export default function ServerSideTreeDataCustomCache() { rowCount: getRowsResponse.rowCount, }; }, + getGroupKey: (row) => row[dataSetOptions.treeData.groupingField], + hasChildren: (row) => row.hasChildren, + getChildrenCount: (row) => row.descendantCount, }), [fetchRows], ); diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx index b2e48e8e9410..701bea253c5c 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx @@ -41,15 +41,16 @@ const cache: GridDataSourceCache = { }; const pageSizeOptions = [5, 10, 50]; +const dataSetOptions = { + dataSet: 'Employee' as 'Employee', + rowLength: 1000, + treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, +}; export default function ServerSideTreeDataCustomCache() { const apiRef = useGridApiRef(); - const { isInitialized, fetchRows, ...props } = useMockServer({ - dataSet: 'Employee', - rowLength: 1000, - treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, - }); + const { isInitialized, fetchRows, ...props } = useMockServer(dataSetOptions); const dataSource: GridDataSource = React.useMemo( () => ({ @@ -70,6 +71,9 @@ export default function ServerSideTreeDataCustomCache() { rowCount: getRowsResponse.rowCount, }; }, + getGroupKey: (row) => row[dataSetOptions.treeData.groupingField], + hasChildren: (row) => row.hasChildren, + getChildrenCount: (row) => row.descendantCount, }), [fetchRows], ); diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js index 5c5b6f82f333..4de1a8fb26f0 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js @@ -10,6 +10,11 @@ import LoadingSlate from './LoadingSlateNoSnap'; const pageSizeOptions = [5, 10, 50]; const serverOptions = { useCursorPagination: false }; +const dataSetOptions = { + dataSet: 'Employee', + rowLength: 1000, + treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, +}; export default function ServerSideTreeDataErrorHandling() { const apiRef = useGridApiRef(); @@ -18,11 +23,7 @@ export default function ServerSideTreeDataErrorHandling() { const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); const { isInitialized, fetchRows, ...props } = useMockServer( - { - dataSet: 'Employee', - rowLength: 1000, - treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, - }, + dataSetOptions, serverOptions, shouldRequestsFail, ); @@ -46,6 +47,9 @@ export default function ServerSideTreeDataErrorHandling() { rowCount: getRowsResponse.rowCount, }; }, + getGroupKey: (row) => row[dataSetOptions.treeData.groupingField], + hasChildren: (row) => row.hasChildren, + getChildrenCount: (row) => row.descendantCount, }), [fetchRows], ); diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx index fb24d484a838..53b7320327a1 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx @@ -15,6 +15,11 @@ import LoadingSlate from './LoadingSlateNoSnap'; const pageSizeOptions = [5, 10, 50]; const serverOptions = { useCursorPagination: false }; +const dataSetOptions = { + dataSet: 'Employee' as 'Employee', + rowLength: 1000, + treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, +}; export default function ServerSideTreeDataErrorHandling() { const apiRef = useGridApiRef(); @@ -23,11 +28,7 @@ export default function ServerSideTreeDataErrorHandling() { const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); const { isInitialized, fetchRows, ...props } = useMockServer( - { - dataSet: 'Employee', - rowLength: 1000, - treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, - }, + dataSetOptions, serverOptions, shouldRequestsFail, ); @@ -51,6 +52,9 @@ export default function ServerSideTreeDataErrorHandling() { rowCount: getRowsResponse.rowCount, }; }, + getGroupKey: (row) => row[dataSetOptions.treeData.groupingField], + hasChildren: (row) => row.hasChildren, + getChildrenCount: (row) => row.descendantCount, }), [fetchRows], ); diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js index 048d72cdd0b0..acc8e8635e38 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js @@ -4,22 +4,16 @@ import Button from '@mui/material/Button'; import { useMockServer } from '@mui/x-data-grid-generator'; const pageSizeOptions = [5, 10, 50]; +const dataSetOptions = { + dataSet: 'Employee', + rowLength: 1000, + treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, +}; export default function ServerSideTreeDataGroupExpansion() { const apiRef = useGridApiRef(); - const { - fetchRows, - columns, - initialState, - getGroupKey, - getChildrenCount, - hasChildren, - } = useMockServer({ - dataSet: 'Employee', - rowLength: 1000, - treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, - }); + const { fetchRows, columns, initialState } = useMockServer(dataSetOptions); const dataSource = React.useMemo( () => ({ @@ -40,6 +34,9 @@ export default function ServerSideTreeDataGroupExpansion() { rowCount: getRowsResponse.rowCount, }; }, + getGroupKey: (row) => row[dataSetOptions.treeData.groupingField], + hasChildren: (row) => row.hasChildren, + getChildrenCount: (row) => row.descendantCount, }), [fetchRows], ); @@ -64,9 +61,6 @@ export default function ServerSideTreeDataGroupExpansion() { ({ @@ -46,6 +42,9 @@ export default function ServerSideTreeDataGroupExpansion() { rowCount: getRowsResponse.rowCount, }; }, + getGroupKey: (row) => row[dataSetOptions.treeData.groupingField], + hasChildren: (row) => row.hasChildren, + getChildrenCount: (row) => row.descendantCount, }), [fetchRows], ); @@ -70,9 +69,6 @@ export default function ServerSideTreeDataGroupExpansion() { apiRef.current.clearCache()}>Reset cache +
+ +
\ No newline at end of file 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 8457b6cf96cc..a142ba092760 100644 --- a/docs/data/data-grid/server-side-data/tree-data.md +++ b/docs/data/data-grid/server-side-data/tree-data.md @@ -8,41 +8,29 @@ title: React Server-side tree data To dynamically load tree data from the server, including lazy-loading of children, you must create a data source and pass the `unstable_dataSource` prop to the Data Grid, as detailed in the [overview section](/x/react-data-grid/server-side-data/). -Additionally, you must supply the following required props, listed and explained below. +The data source also requires some additional props to handle tree data, namely `getGroupKey` and `hasChildren`. The data source also supports and optional prop `getChildrenCount` to let the Data Grid print the children count next to each parent. ```tsx - +const customDataSource: GridDataSource = { + getRows: async (params) => { + // Fetch the data from the server + }, + getGroupKey: (row) => { + // Return the group key for the row, e.g. `name` + return row.name; + }, + hasChildren: (row) => { + // Return true if the row has children + return row.hasChildren; + }, + getChildrenCount: (row) => { + // Return the number of children for the row + return row.childrenCount; + }, +}; ``` -- `getGroupKey(row: GridRowModel): string` - - Used to group rows by their parent group. Replaces `getTreeDataPath` used in client-side tree-data. - For example, consider this tree structure for tree data. - - ```js - - (1) Sarah // groupKey 'Sarah' - - (2) Thomas // groupKey 'Thomas' - ``` - - When **(2) Thomas** is expanded, the `getRows` function will be called with group keys `['Sarah', 'Thomas']`. - -- `hasChildren(row: GridRowModel): boolean` - - Used by the grid to check if a row has children on server - -- `getChildrenCount?: (row: GridRowModel) => number` - - Used by the grid to determine the number of children of a row on server - -## Demo - -Following is a demo of the server-side tree data with the data source which supports filtering, sorting, and pagination on the server. It also caches the data by default. +Following tree-data example supports filtering, sorting, and pagination on the server. It also caches the data by default. {{"demo": "ServerSideTreeData.js", "bg": "inline"}} diff --git a/docs/data/pages.ts b/docs/data/pages.ts index 0c29e63497e6..9cd9e96db511 100644 --- a/docs/data/pages.ts +++ b/docs/data/pages.ts @@ -126,8 +126,9 @@ const pages: MuiPage[] = [ { pathname: '/x/react-data-grid/server-side-data/infinite-loading', plan: 'pro', + planned: true, }, - { pathname: '/x/react-data-grid/server-side-data/tree-data', plan: 'pro', planned: true }, + { pathname: '/x/react-data-grid/server-side-data/tree-data', plan: 'pro' }, { pathname: '/x/react-data-grid/server-side-data/row-grouping', plan: 'pro', diff --git a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx index b60b33ec0122..86c751df0f49 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx @@ -365,7 +365,6 @@ DataGridPremiumRaw.propTypes = { * @returns {string} The CSS class to apply to the cell. */ getCellClassName: PropTypes.func, - getChildrenCount: PropTypes.func, /** * Function that returns the element to render in row detail. * @param {GridRowParams} params With all properties from [[GridRowParams]]. @@ -387,7 +386,6 @@ DataGridPremiumRaw.propTypes = { * @returns {number | null} The estimated row height value. If `null` or `undefined` then the default row height, based on the density, is applied. */ getEstimatedRowHeight: PropTypes.func, - getGroupKey: PropTypes.func, /** * Function that applies CSS classes dynamically on rows. * @param {GridRowClassNameParams} params With all properties from [[GridRowClassNameParams]]. @@ -423,7 +421,6 @@ DataGridPremiumRaw.propTypes = { * The grouping column used by the tree data. */ groupingColDef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - hasChildren: PropTypes.func, /** * Override the height of the header filters. */ @@ -1053,7 +1050,10 @@ DataGridPremiumRaw.propTypes = { */ treeData: PropTypes.bool, unstable_dataSource: PropTypes.shape({ + getChildrenCount: PropTypes.func, + getGroupKey: PropTypes.func, getRows: PropTypes.func.isRequired, + hasChildren: PropTypes.func, 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 ce750f8e50d0..98a2f3c506d8 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx @@ -320,7 +320,6 @@ DataGridProRaw.propTypes = { * @returns {string} The CSS class to apply to the cell. */ getCellClassName: PropTypes.func, - getChildrenCount: PropTypes.func, /** * Function that returns the element to render in row detail. * @param {GridRowParams} params With all properties from [[GridRowParams]]. @@ -342,7 +341,6 @@ DataGridProRaw.propTypes = { * @returns {number | null} The estimated row height value. If `null` or `undefined` then the default row height, based on the density, is applied. */ getEstimatedRowHeight: PropTypes.func, - getGroupKey: PropTypes.func, /** * Function that applies CSS classes dynamically on rows. * @param {GridRowClassNameParams} params With all properties from [[GridRowClassNameParams]]. @@ -378,7 +376,6 @@ DataGridProRaw.propTypes = { * The grouping column used by the tree data. */ groupingColDef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), - hasChildren: PropTypes.func, /** * Override the height of the header filters. */ @@ -952,7 +949,10 @@ DataGridProRaw.propTypes = { */ treeData: PropTypes.bool, unstable_dataSource: PropTypes.shape({ + getChildrenCount: PropTypes.func, + getGroupKey: PropTypes.func, getRows: PropTypes.func.isRequired, + hasChildren: PropTypes.func, updateRow: PropTypes.func, }), unstable_dataSourceCache: PropTypes.shape({ diff --git a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx b/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx index 34980b0bc997..e49d67718345 100644 --- a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx +++ b/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx @@ -120,7 +120,7 @@ export function GridServerSideTreeDataGroupingCell(props: GridTreeDataGroupingCe const row = apiRef.current.getRow(rowNode.id); const ownerState: OwnerState = { classes: rootProps.classes }; const classes = useUtilityClasses(ownerState); - const descendantCount = rootProps.getChildrenCount?.(row) ?? 0; + const descendantCount = rootProps.unstable_dataSource?.getChildrenCount?.(row) ?? 0; return ( diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx index 4c936f5ee4e5..025cba11db3c 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx @@ -42,13 +42,11 @@ export const useGridServerSideTreeDataPreProcessors = ( DataGridProProcessedProps, | 'treeData' | 'groupingColDef' - | 'getGroupKey' | 'disableChildrenSorting' | 'disableChildrenFiltering' | 'defaultGroupingExpansionDepth' | 'isGroupExpandedByDefault' | 'unstable_dataSource' - | 'hasChildren' >, ) => { const setStrategyAvailability = React.useCallback(() => { @@ -133,22 +131,24 @@ export const useGridServerSideTreeDataPreProcessors = ( const createRowTreeForTreeData = React.useCallback>( (params) => { - if (!props.getGroupKey) { - throw new Error('MUI X: No `getGroupKey` prop provided.'); + const getGroupKey = props.unstable_dataSource?.getGroupKey; + if (!getGroupKey) { + throw new Error('MUI X: No `getGroupKey` method provided with the dataSource.'); } - if (!props.hasChildren) { - throw new Error('MUI X: No `hasChildren` prop provided.'); + const hasChildren = props.unstable_dataSource?.hasChildren; + if (!hasChildren) { + throw new Error('MUI X: No `hasChildren` method provided with the dataSource.'); } const parentPath = privateApiRef.current.caches.dataSource?.groupKeys || []; const getRowTreeBuilderNode = (rowId: GridRowId) => ({ id: rowId, - path: [...parentPath, props.getGroupKey!(params.dataRowIdToModelLookup[rowId])].map( + path: [...parentPath, getGroupKey!(params.dataRowIdToModelLookup[rowId])].map( (key): RowTreeBuilderGroupingCriterion => ({ key, field: null }), ), - hasServerChildren: props.hasChildren!(params.dataRowIdToModelLookup[rowId]), + hasServerChildren: hasChildren!(params.dataRowIdToModelLookup[rowId]), }); const onDuplicatePath: GridTreePathDuplicateHandler = (firstId, secondId, path) => { @@ -187,8 +187,7 @@ export const useGridServerSideTreeDataPreProcessors = ( }); }, [ - props.getGroupKey, - props.hasChildren, + props.unstable_dataSource, props.defaultGroupingExpansionDepth, props.isGroupExpandedByDefault, privateApiRef, diff --git a/packages/x-data-grid-pro/src/models/dataGridProProps.ts b/packages/x-data-grid-pro/src/models/dataGridProProps.ts index 0e97fd482eea..a4bbd0dc5661 100644 --- a/packages/x-data-grid-pro/src/models/dataGridProProps.ts +++ b/packages/x-data-grid-pro/src/models/dataGridProProps.ts @@ -147,9 +147,6 @@ export interface DataGridProPropsWithDefaultValue void; - getGroupKey?: (row: GridValidRowModel) => string; - hasChildren?: (row: GridValidRowModel) => boolean; - getChildrenCount?: (row: GridValidRowModel) => number; } interface DataGridProRegularProps { diff --git a/packages/x-data-grid/src/models/gridDataSource.ts b/packages/x-data-grid/src/models/gridDataSource.ts index 9fd1b4257fa0..a6a61fe604f1 100644 --- a/packages/x-data-grid/src/models/gridDataSource.ts +++ b/packages/x-data-grid/src/models/gridDataSource.ts @@ -63,6 +63,24 @@ export interface GridDataSource { * @returns {Promise} If resolved (synced on the backend), the grid will update the row and mutate the cache */ updateRow?(updatedRow: GridRowModel): Promise; + /** + * Used to group rows by their parent group. Replaces `getTreeDataPath` used in client side tree-data . + * @param {GridRowModel} row The row to get the group key of + * @returns {string} The group key for the row + */ + getGroupKey?: (row: GridRowModel) => string; + /** + * Used to determine if a row has children on server. + * @param {GridRowModel} row The row to check if it has children + * @returns {boolean} A boolean indicating if the row has children + */ + hasChildren?: (row: GridRowModel) => boolean; + /** + * Used to determine the number of children a row has on server. + * @param {GridRowModel} row The row to check the number of children + * @returns {number} The number of children the row has + */ + getChildrenCount?: (row: GridRowModel) => number; } export interface GridDataSourceCache { From 102a8fe295fb65350665e8ba3e5d2236e01d0ed4 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 3 Jun 2024 19:13:07 +0500 Subject: [PATCH 46/90] Use useLazyRef for initilization --- .../src/hooks/features/dataSource/useGridDataSource.ts | 5 +++-- .../src/hooks/features/dataSource/useGridDataSourceCache.ts | 3 ++- 2 files changed, 5 insertions(+), 3 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 c11362253fae..6dead24629f8 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 @@ -1,4 +1,5 @@ import * as React from 'react'; +import useLazyRef from '@mui/utils/useLazyRef'; import { useGridApiEventHandler, gridRowsLoadingSelector, @@ -47,8 +48,8 @@ export const useGridDataSource = ( | 'treeData' >, ) => { - const nestedDataManager = React.useRef( - new NestedDataManager(privateApiRef), + const nestedDataManager = useLazyRef( + () => new NestedDataManager(privateApiRef), ).current; const groupsToAutoFetch = useGridSelector(privateApiRef, gridRowGroupsToFetchSelector); const scheduledGroups = React.useRef(0); diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts index 384f3daa107b..4b1dbbdad07c 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts @@ -1,4 +1,5 @@ import * as React from 'react'; +import useLazyRef from '@mui/utils/useLazyRef'; import { useGridApiMethod } from '@mui/x-data-grid'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; @@ -49,7 +50,7 @@ export const useGridDataSourceCache = ( 'unstable_dataSource' | 'disableDataSourceCache' | 'unstable_dataSourceCache' >, ): void => { - const defaultCache = React.useRef(getDefaultCache(new SimpleServerSideCache())); + const defaultCache = useLazyRef(() => getDefaultCache(new SimpleServerSideCache())); const cache = React.useRef( props.unstable_dataSourceCache || defaultCache.current, ); From ba269eacc60ab058a6903c976f669e45148a2405 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 3 Jun 2024 20:48:42 +0500 Subject: [PATCH 47/90] Use derived selector for row + remove extra useCallbacks --- .../GridServerSideTreeDataGroupingCell.tsx | 18 ++++++------------ .../dataSource/useGridDataSourceCache.ts | 4 +++- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx b/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx index e49d67718345..d2440871b728 100644 --- a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx +++ b/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx @@ -16,7 +16,7 @@ import { DataGridProProcessedProps } from '../models/dataGridProProps'; import { GridPrivateApiPro } from '../models/gridApiPro'; import { GridStatePro } from '../models/gridStatePro'; -type OwnerState = { classes: DataGridProProcessedProps['classes'] }; +type OwnerState = DataGridProProcessedProps; const useUtilityClasses = (ownerState: OwnerState) => { const { classes } = ownerState; @@ -56,14 +56,8 @@ function GridTreeDataGroupingCellIcon(props: GridTreeDataGroupingCellIconProps) const rootProps = useGridRootProps(); const { rowNode, id, field, descendantCount } = props; - const loadingSelector = React.useCallback( - (state: GridStatePro) => state.dataSource.loading[id] ?? false, - [id], - ); - const errorSelector = React.useCallback( - (state: GridStatePro) => state.dataSource.errors[id] ?? null, - [id], - ); + const loadingSelector = (state: GridStatePro) => state.dataSource.loading[id] ?? false; + const errorSelector = (state: GridStatePro) => state.dataSource.errors[id]; const isDataLoading = useGridSelector(apiRef, loadingSelector); const error = useGridSelector(apiRef, errorSelector); @@ -117,9 +111,9 @@ export function GridServerSideTreeDataGroupingCell(props: GridTreeDataGroupingCe const rootProps = useGridRootProps(); const apiRef = useGridPrivateApiContext(); - const row = apiRef.current.getRow(rowNode.id); - const ownerState: OwnerState = { classes: rootProps.classes }; - const classes = useUtilityClasses(ownerState); + const rowSelector = (state: GridStatePro) => state.rows.dataRowIdToModelLookup[id]; + const row = useGridSelector(apiRef, rowSelector); + const classes = useUtilityClasses(rootProps); const descendantCount = rootProps.unstable_dataSource?.getChildrenCount?.(row) ?? 0; return ( diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts index 4b1dbbdad07c..47014b3010b7 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts @@ -50,7 +50,9 @@ export const useGridDataSourceCache = ( 'unstable_dataSource' | 'disableDataSourceCache' | 'unstable_dataSourceCache' >, ): void => { - const defaultCache = useLazyRef(() => getDefaultCache(new SimpleServerSideCache())); + const defaultCache = useLazyRef(() => + getDefaultCache(new SimpleServerSideCache()), + ); const cache = React.useRef( props.unstable_dataSourceCache || defaultCache.current, ); From 8b565856b305602b15ea7f26400be2076252ed0a Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 3 Jun 2024 20:54:29 +0500 Subject: [PATCH 48/90] Use rootProps in GridTreeDataGroupingCell --- .../src/components/GridTreeDataGroupingCell.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/x-data-grid-pro/src/components/GridTreeDataGroupingCell.tsx b/packages/x-data-grid-pro/src/components/GridTreeDataGroupingCell.tsx index 8e1e85688d89..673b637ee038 100644 --- a/packages/x-data-grid-pro/src/components/GridTreeDataGroupingCell.tsx +++ b/packages/x-data-grid-pro/src/components/GridTreeDataGroupingCell.tsx @@ -13,7 +13,7 @@ import { useGridRootProps } from '../hooks/utils/useGridRootProps'; import { useGridApiContext } from '../hooks/utils/useGridApiContext'; import { DataGridProProcessedProps } from '../models/dataGridProProps'; -type OwnerState = { classes: DataGridProProcessedProps['classes'] }; +type OwnerState = DataGridProProcessedProps; const useUtilityClasses = (ownerState: OwnerState) => { const { classes } = ownerState; @@ -40,8 +40,7 @@ function GridTreeDataGroupingCell(props: GridTreeDataGroupingCellProps) { const rootProps = useGridRootProps(); const apiRef = useGridApiContext(); - const ownerState: OwnerState = { classes: rootProps.classes }; - const classes = useUtilityClasses(ownerState); + const classes = useUtilityClasses(rootProps); const filteredDescendantCountLookup = useGridSelector( apiRef, gridFilteredDescendantCountLookupSelector, From fb3c15a81f212a42cbc92ef44f85da8adda918d5 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Wed, 5 Jun 2024 14:53:34 +0500 Subject: [PATCH 49/90] Move processQueue to run on Status change --- .../features/dataSource/useGridDataSource.ts | 15 +++--- .../src/hooks/features/dataSource/utils.ts | 46 ++++++------------- 2 files changed, 22 insertions(+), 39 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 6dead24629f8..7e51ef236c1c 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 @@ -62,10 +62,9 @@ export const useGridDataSource = ( } nestedDataManager.clearPendingRequests(); - scheduledGroups.current = 0; - const serverSideState = privateApiRef.current.state.dataSource; - if (serverSideState !== INITIAL_STATE) { + const dataSourceState = privateApiRef.current.state.dataSource; + if (dataSourceState !== INITIAL_STATE) { privateApiRef.current.resetDataSourceState(); } @@ -165,7 +164,7 @@ export const useGridDataSource = ( privateApiRef.current.setChildrenLoading(id, false); return; } - if (nestedDataManager.getRequestStatus(id) === RequestStatus.INVALID) { + if (nestedDataManager.getRequestStatus(id) === RequestStatus.UNKNOWN) { privateApiRef.current.setChildrenLoading(id, false); return; } @@ -177,14 +176,14 @@ export const useGridDataSource = ( privateApiRef.current.caches.dataSource.groupKeys = rowNode.path; privateApiRef.current.updateRows(getRowsResponse.rows, false); privateApiRef.current.setRowChildrenExpansion(id, true); - privateApiRef.current.setChildrenLoading(id, false); } catch (error) { const e = error as Error; - nestedDataManager.setRequestSettled(id); - privateApiRef.current.setChildrenLoading(id, false); privateApiRef.current.setChildrenFetchError(id, e); onError?.(e, fetchParams); - } + } finally { + privateApiRef.current.setChildrenLoading(id, false); + nestedDataManager.setRequestSettled(id); + } }, [nestedDataManager, onError, privateApiRef, props.treeData, props.unstable_dataSource?.getRows], ); 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 427fabc989a4..6f5a9b6c90e4 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,9 +1,7 @@ import { GridRowId } from '@mui/x-data-grid'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; -// Make these configurable using dedicated props? const MAX_CONCURRENT_REQUESTS = Infinity; -const QUEUE_PROCESS_INTERVAL_MS = 300; export const runIfServerMode = (modeProp: 'server' | 'client', fn: Function) => () => { if (modeProp === 'server') { @@ -15,7 +13,7 @@ export enum RequestStatus { QUEUED, PENDING, SETTLED, - INVALID, + UNKNOWN, } /** @@ -34,40 +32,32 @@ export class NestedDataManager { private maxConcurrentRequests: number; - private queueProcessInterval: number; - - private timer?: string | number | NodeJS.Timeout; - constructor( privateApiRef: React.MutableRefObject, maxConcurrentRequests = MAX_CONCURRENT_REQUESTS, - queueProcessInterval = QUEUE_PROCESS_INTERVAL_MS, ) { this.api = privateApiRef.current; this.maxConcurrentRequests = maxConcurrentRequests; - this.queueProcessInterval = queueProcessInterval; } private processQueue = async () => { - if (this.queuedRequests.size === 0) { - clearInterval(this.timer); + if (this.queuedRequests.size === 0 || this.pendingRequests.size >= this.maxConcurrentRequests) { return; } - if (this.pendingRequests.size >= this.maxConcurrentRequests) { + const loopLength = Math.min( + this.maxConcurrentRequests - this.pendingRequests.size, + this.queuedRequests.size, + ); + if (loopLength === 0) { return; } const fetchQueue = Array.from(this.queuedRequests); - const availableSlots = this.maxConcurrentRequests - this.pendingRequests.size; - for (let i = 0; i < availableSlots; i += 1) { - const nextId = fetchQueue[i]; - if (typeof nextId === 'undefined') { - clearInterval(this.timer); - return; - } - this.queuedRequests.delete(nextId); - this.api.fetchRowChildren(nextId); - this.pendingRequests.add(nextId); + for (let i = 0; i < loopLength; i += 1) { + const id = fetchQueue[i]; + this.queuedRequests.delete(id); + this.api.fetchRowChildren(id); + this.pendingRequests.add(id); } }; @@ -79,23 +69,16 @@ export class NestedDataManager { } else { this.queuedRequests.add(id); } - - if (this.queuedRequests.size > 0) { - if (this.timer) { - clearInterval(this.timer); - } - this.timer = setInterval(this.processQueue, this.queueProcessInterval); - } }); }; public setRequestSettled = (id: GridRowId) => { this.pendingRequests.delete(id); this.settledRequests.add(id); + this.processQueue(); }; public clearPendingRequests = () => { - clearInterval(this.timer); this.queuedRequests.clear(); Array.from(this.pendingRequests).forEach((id) => this.clearPendingRequest(id)); }; @@ -103,6 +86,7 @@ export class NestedDataManager { public clearPendingRequest = (id: GridRowId) => { this.api.setChildrenLoading(id, false); this.pendingRequests.delete(id); + this.processQueue(); }; public getRequestStatus = (id: GridRowId) => { @@ -115,7 +99,7 @@ export class NestedDataManager { if (this.settledRequests.has(id)) { return RequestStatus.SETTLED; } - return RequestStatus.INVALID; + return RequestStatus.UNKNOWN; }; public getActiveRequestsCount = () => this.pendingRequests.size + this.queuedRequests.size; From 5e93c8212d58b1b8c92c8739a1267b9df93c8ce1 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Wed, 5 Jun 2024 15:17:09 +0500 Subject: [PATCH 50/90] Remove hasChildren in favor of getChildrenCount --- .../data-grid/server-side-data/ServerSideTreeData.js | 1 - .../data-grid/server-side-data/ServerSideTreeData.tsx | 1 - .../server-side-data/ServerSideTreeDataCustomCache.js | 1 - .../server-side-data/ServerSideTreeDataCustomCache.tsx | 1 - .../ServerSideTreeDataErrorHandling.js | 1 - .../ServerSideTreeDataErrorHandling.tsx | 1 - .../ServerSideTreeDataGroupExpansion.js | 1 - .../ServerSideTreeDataGroupExpansion.tsx | 1 - docs/data/data-grid/server-side-data/tree-data.md | 6 +----- .../x-data-grid-generator/src/hooks/serverUtils.ts | 2 +- .../x-data-grid-generator/src/hooks/useMockServer.ts | 9 --------- .../components/GridServerSideTreeDataGroupingCell.tsx | 6 +++++- .../src/hooks/features/dataSource/useGridDataSource.ts | 2 +- .../useGridServerSideTreeDataPreProcessors.tsx | 10 +++++----- packages/x-data-grid/src/models/gridDataSource.ts | 6 ------ 15 files changed, 13 insertions(+), 36 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.js b/docs/data/data-grid/server-side-data/ServerSideTreeData.js index 1ef630c3c0c9..f81c57f7db29 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.js @@ -29,7 +29,6 @@ const dataSource = { }; }, getGroupKey: (row) => row[dataSetOptions.treeData.groupingField], - hasChildren: (row) => row.hasChildren, getChildrenCount: (row) => row.descendantCount, }; diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx index 727d88d2893c..38ff1c3e1002 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx @@ -35,7 +35,6 @@ const dataSource: GridDataSource = { }; }, getGroupKey: (row) => row[dataSetOptions.treeData.groupingField], - hasChildren: (row) => row.hasChildren, getChildrenCount: (row) => row.descendantCount, }; diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js index e0e045524289..c91e0d5e4b80 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js @@ -65,7 +65,6 @@ export default function ServerSideTreeDataCustomCache() { }; }, getGroupKey: (row) => row[dataSetOptions.treeData.groupingField], - hasChildren: (row) => row.hasChildren, getChildrenCount: (row) => row.descendantCount, }), [fetchRows], diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx index 701bea253c5c..8ecb2009909d 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx @@ -72,7 +72,6 @@ export default function ServerSideTreeDataCustomCache() { }; }, getGroupKey: (row) => row[dataSetOptions.treeData.groupingField], - hasChildren: (row) => row.hasChildren, getChildrenCount: (row) => row.descendantCount, }), [fetchRows], diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js index 4de1a8fb26f0..741d428ac2c2 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js @@ -48,7 +48,6 @@ export default function ServerSideTreeDataErrorHandling() { }; }, getGroupKey: (row) => row[dataSetOptions.treeData.groupingField], - hasChildren: (row) => row.hasChildren, getChildrenCount: (row) => row.descendantCount, }), [fetchRows], diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx index 53b7320327a1..e659492409b7 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx @@ -53,7 +53,6 @@ export default function ServerSideTreeDataErrorHandling() { }; }, getGroupKey: (row) => row[dataSetOptions.treeData.groupingField], - hasChildren: (row) => row.hasChildren, getChildrenCount: (row) => row.descendantCount, }), [fetchRows], diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js index acc8e8635e38..9e7703d58dd5 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js @@ -35,7 +35,6 @@ export default function ServerSideTreeDataGroupExpansion() { }; }, getGroupKey: (row) => row[dataSetOptions.treeData.groupingField], - hasChildren: (row) => row.hasChildren, getChildrenCount: (row) => row.descendantCount, }), [fetchRows], diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx index df2de19e3016..34c72a5e35b9 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx @@ -43,7 +43,6 @@ export default function ServerSideTreeDataGroupExpansion() { }; }, getGroupKey: (row) => row[dataSetOptions.treeData.groupingField], - hasChildren: (row) => row.hasChildren, getChildrenCount: (row) => row.descendantCount, }), [fetchRows], 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 a142ba092760..94ddacab893a 100644 --- a/docs/data/data-grid/server-side-data/tree-data.md +++ b/docs/data/data-grid/server-side-data/tree-data.md @@ -8,7 +8,7 @@ title: React Server-side tree data To dynamically load tree data from the server, including lazy-loading of children, you must create a data source and pass the `unstable_dataSource` prop to the Data Grid, as detailed in the [overview section](/x/react-data-grid/server-side-data/). -The data source also requires some additional props to handle tree data, namely `getGroupKey` and `hasChildren`. The data source also supports and optional prop `getChildrenCount` to let the Data Grid print the children count next to each parent. +The data source also requires some additional props to handle tree data, namely `getGroupKey` and `getChildrenCount`. If the children count is not available for some reason, but there are some children, `getChildrenCount` should return `-1`. ```tsx const customDataSource: GridDataSource = { @@ -19,10 +19,6 @@ const customDataSource: GridDataSource = { // Return the group key for the row, e.g. `name` return row.name; }, - hasChildren: (row) => { - // Return true if the row has children - return row.hasChildren; - }, getChildrenCount: (row) => { // Return the number of children for the row return row.childrenCount; diff --git a/packages/x-data-grid-generator/src/hooks/serverUtils.ts b/packages/x-data-grid-generator/src/hooks/serverUtils.ts index 03288b5c8886..b6a918a3b569 100644 --- a/packages/x-data-grid-generator/src/hooks/serverUtils.ts +++ b/packages/x-data-grid-generator/src/hooks/serverUtils.ts @@ -464,7 +464,7 @@ export const processTreeDataRows = ( let childRowsWithDescendantCounts = childRows.map((row) => { const descendants = findTreeDataRowChildren(filteredRows, row[pathKey], pathKey, -1); const descendantCount = descendants.length; - return { ...row, descendantCount, hasChildren: descendantCount > 0 } as GridRowModel; + return { ...row, descendantCount } as GridRowModel; }); if (queryOptions.sortModel) { diff --git a/packages/x-data-grid-generator/src/hooks/useMockServer.ts b/packages/x-data-grid-generator/src/hooks/useMockServer.ts index ce370fd4fab9..a5d39c0e63ad 100644 --- a/packages/x-data-grid-generator/src/hooks/useMockServer.ts +++ b/packages/x-data-grid-generator/src/hooks/useMockServer.ts @@ -40,7 +40,6 @@ type UseMockServerResponse = { columns: GridColDef[]; initialState: GridInitialState; getGroupKey?: (row: GridRowModel) => string; - hasChildren?: (row: GridRowModel) => boolean; getChildrenCount?: (row: GridRowModel) => number; fetchRows: (url: string) => Promise; isInitialized: boolean; @@ -132,13 +131,6 @@ export const useMockServer = ( // eslint-disable-next-line react-hooks/exhaustive-deps }, [options.treeData?.groupingField, isTreeData]); - const hasChildren = React.useMemo(() => { - if (isTreeData) { - return (row: GridRowModel): boolean => row.hasChildren; - } - return undefined; - }, [isTreeData]); - const getChildrenCount = React.useMemo(() => { if (isTreeData) { return (row: GridRowModel): number => row.descendantCount; @@ -337,7 +329,6 @@ export const useMockServer = ( columns: columnsWithDefaultColDef, initialState, getGroupKey, - hasChildren, getChildrenCount, fetchRows, isInitialized, diff --git a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx b/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx index d2440871b728..7c7f4e6e19f8 100644 --- a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx +++ b/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx @@ -114,7 +114,11 @@ export function GridServerSideTreeDataGroupingCell(props: GridTreeDataGroupingCe const rowSelector = (state: GridStatePro) => state.rows.dataRowIdToModelLookup[id]; const row = useGridSelector(apiRef, rowSelector); const classes = useUtilityClasses(rootProps); - const descendantCount = rootProps.unstable_dataSource?.getChildrenCount?.(row) ?? 0; + + let descendantCount = 0; + if (row) { + descendantCount = Math.max(rootProps.unstable_dataSource?.getChildrenCount?.(row) ?? 0, 0); + } return ( 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 7e51ef236c1c..cd03c6f1c12c 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 @@ -183,7 +183,7 @@ export const useGridDataSource = ( } finally { privateApiRef.current.setChildrenLoading(id, false); nestedDataManager.setRequestSettled(id); - } + } }, [nestedDataManager, onError, privateApiRef, props.treeData, props.unstable_dataSource?.getRows], ); diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx index 025cba11db3c..e8fc21a253fb 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx @@ -136,19 +136,19 @@ export const useGridServerSideTreeDataPreProcessors = ( throw new Error('MUI X: No `getGroupKey` method provided with the dataSource.'); } - const hasChildren = props.unstable_dataSource?.hasChildren; - if (!hasChildren) { - throw new Error('MUI X: No `hasChildren` method provided with the dataSource.'); + const getChildrenCount = props.unstable_dataSource?.getChildrenCount; + if (!getChildrenCount) { + throw new Error('MUI X: No `getChildrenCount` method provided with the dataSource.'); } const parentPath = privateApiRef.current.caches.dataSource?.groupKeys || []; const getRowTreeBuilderNode = (rowId: GridRowId) => ({ id: rowId, - path: [...parentPath, getGroupKey!(params.dataRowIdToModelLookup[rowId])].map( + path: [...parentPath, getGroupKey(params.dataRowIdToModelLookup[rowId])].map( (key): RowTreeBuilderGroupingCriterion => ({ key, field: null }), ), - hasServerChildren: hasChildren!(params.dataRowIdToModelLookup[rowId]), + hasServerChildren: getChildrenCount(params.dataRowIdToModelLookup[rowId]) !== 0, }); const onDuplicatePath: GridTreePathDuplicateHandler = (firstId, secondId, path) => { diff --git a/packages/x-data-grid/src/models/gridDataSource.ts b/packages/x-data-grid/src/models/gridDataSource.ts index a6a61fe604f1..88bc17786a7b 100644 --- a/packages/x-data-grid/src/models/gridDataSource.ts +++ b/packages/x-data-grid/src/models/gridDataSource.ts @@ -69,12 +69,6 @@ export interface GridDataSource { * @returns {string} The group key for the row */ getGroupKey?: (row: GridRowModel) => string; - /** - * Used to determine if a row has children on server. - * @param {GridRowModel} row The row to check if it has children - * @returns {boolean} A boolean indicating if the row has children - */ - hasChildren?: (row: GridRowModel) => boolean; /** * Used to determine the number of children a row has on server. * @param {GridRowModel} row The row to check the number of children From 7d0d26d1f5cc6e8e92cb79e2e08ca57ec1b5f4e6 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Wed, 5 Jun 2024 15:31:57 +0500 Subject: [PATCH 51/90] Move loading container styling to root styles --- .../GridServerSideTreeDataGroupingCell.tsx | 14 ++++---------- .../src/components/containers/GridRootStyles.ts | 8 ++++++++ packages/x-data-grid/src/constants/gridClasses.ts | 5 +++++ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx b/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx index 7c7f4e6e19f8..f0215b17c37f 100644 --- a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx +++ b/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import { unstable_composeClasses as composeClasses } from '@mui/utils'; -import { styled } from '@mui/system'; import Box from '@mui/material/Box'; import Badge from '@mui/material/Badge'; import { @@ -24,6 +23,7 @@ const useUtilityClasses = (ownerState: OwnerState) => { const slots = { root: ['treeDataGroupingCell'], toggle: ['treeDataGroupingCellToggle'], + loadingContainer: ['treeDataGroupingCellLoadingContainer'], }; return composeClasses(slots, getDataGridUtilityClass, classes); @@ -44,16 +44,10 @@ interface GridTreeDataGroupingCellIconProps descendantCount: number; } -const LoadingContainer = styled('div')({ - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - height: '100%', -}); - function GridTreeDataGroupingCellIcon(props: GridTreeDataGroupingCellIconProps) { const apiRef = useGridPrivateApiContext() as React.MutableRefObject; const rootProps = useGridRootProps(); + const classes = useUtilityClasses(rootProps); const { rowNode, id, field, descendantCount } = props; const loadingSelector = (state: GridStatePro) => state.dataSource.loading[id] ?? false; @@ -80,9 +74,9 @@ function GridTreeDataGroupingCellIcon(props: GridTreeDataGroupingCellIconProps) if (isDataLoading) { return ( - +
- +
); } return descendantCount > 0 || hasServerChildren ? ( diff --git a/packages/x-data-grid/src/components/containers/GridRootStyles.ts b/packages/x-data-grid/src/components/containers/GridRootStyles.ts index 0d5e88de51f6..b3731b380ea2 100644 --- a/packages/x-data-grid/src/components/containers/GridRootStyles.ts +++ b/packages/x-data-grid/src/components/containers/GridRootStyles.ts @@ -115,6 +115,7 @@ export const GridRootStyles = styled('div', { { [`& .${c.withBorderColor}`]: styles.withBorderColor }, { [`& .${c.treeDataGroupingCell}`]: styles.treeDataGroupingCell }, { [`& .${c.treeDataGroupingCellToggle}`]: styles.treeDataGroupingCellToggle }, + { [`& .${c.treeDataGroupingCellLoadingContainer}`]: styles.treeDataGroupingCellLoadingContainer }, { [`& .${c.detailPanelToggleCell}`]: styles.detailPanelToggleCell }, { [`& .${c['detailPanelToggleCell--expanded']}`]: styles['detailPanelToggleCell--expanded'], @@ -620,6 +621,12 @@ export const GridRootStyles = styled('div', { alignSelf: 'stretch', marginRight: t.spacing(2), }, + [`& .${c.treeDataGroupingCellLoadingContainer}`]: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + height: '100%', + }, [`& .${c.groupingCriteriaCell}`]: { display: 'flex', alignItems: 'center', @@ -651,6 +658,7 @@ export const GridRootStyles = styled('div', { [`& .${c['filler--borderTop']}`]: { borderTop: '1px solid var(--DataGrid-rowBorderColor)', }, + [`& .${c['datasource--loadingContainer']}`]: {}, }; return gridStyle; diff --git a/packages/x-data-grid/src/constants/gridClasses.ts b/packages/x-data-grid/src/constants/gridClasses.ts index c795ccda8336..f3c33a3accb1 100644 --- a/packages/x-data-grid/src/constants/gridClasses.ts +++ b/packages/x-data-grid/src/constants/gridClasses.ts @@ -578,6 +578,10 @@ export interface GridClasses { * Styles applied to the toggle of the grouping cell of the tree data. */ treeDataGroupingCellToggle: string; + /** + * Styles applied to the loading container of the grouping cell of the tree data. + */ + treeDataGroupingCellLoadingContainer: string; /** * Styles applied to the root element of the grouping criteria cell */ @@ -754,6 +758,7 @@ export const gridClasses = generateUtilityClasses('MuiDataGrid', [ 'columnHeader--withLeftBorder', 'treeDataGroupingCell', 'treeDataGroupingCellToggle', + 'treeDataGroupingCellLoadingContainer', 'groupingCriteriaCell', 'groupingCriteriaCellToggle', 'pinnedRows', From 0d42b8123aadb70185a05843ffc4abdda03c6744 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Wed, 5 Jun 2024 15:51:11 +0500 Subject: [PATCH 52/90] Remove msw and add console printing --- .../server-side-data/ServerSideDataGrid.js | 41 +-- .../server-side-data/ServerSideDataGrid.tsx | 41 +-- .../server-side-data/ServerSideTreeData.js | 61 ++-- .../server-side-data/ServerSideTreeData.tsx | 61 ++-- docs/data/data-grid/server-side-data/index.md | 6 + .../data-grid/server-side-data/tree-data.md | 4 +- docs/public/mockServiceWorker.js | 281 ------------------ packages/x-data-grid-generator/package.json | 3 +- .../src/hooks/useMockServer.ts | 66 +--- .../components/containers/GridRootStyles.ts | 4 +- pnpm-lock.yaml | 186 +----------- 11 files changed, 118 insertions(+), 636 deletions(-) delete mode 100644 docs/public/mockServiceWorker.js diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGrid.js b/docs/data/data-grid/server-side-data/ServerSideDataGrid.js index 0b75401cf703..219caa9f4221 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGrid.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGrid.js @@ -1,16 +1,12 @@ import * as React from 'react'; import { DataGridPro } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; -import Checkbox from '@mui/material/Checkbox'; -import FormControlLabel from '@mui/material/FormControlLabel'; import LoadingSlate from './LoadingSlateNoSnap'; function ServerSideDataGrid() { - const [verbose, setVerbose] = React.useState(false); - const { isInitialized, columns, initialState, fetchRows } = useMockServer( {}, - { useCursorPagination: false, verbose }, + { useCursorPagination: false }, ); const dataSource = React.useMemo( @@ -47,31 +43,18 @@ function ServerSideDataGrid() { ); return ( -
-
- setVerbose(e.target.checked)} - /> - } - label="Verbose" +
+ {isInitialized ? ( + -
-
- {isInitialized ? ( - - ) : ( - - )} -
+ ) : ( + + )}
); } diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx index 9a0bee05ed9c..c10ccb657162 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx @@ -1,16 +1,12 @@ import * as React from 'react'; import { DataGridPro, GridDataSource } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; -import Checkbox from '@mui/material/Checkbox'; -import FormControlLabel from '@mui/material/FormControlLabel'; import LoadingSlate from './LoadingSlateNoSnap'; function ServerSideDataGrid() { - const [verbose, setVerbose] = React.useState(false); - const { isInitialized, columns, initialState, fetchRows } = useMockServer( {}, - { useCursorPagination: false, verbose }, + { useCursorPagination: false }, ); const dataSource: GridDataSource = React.useMemo( @@ -47,31 +43,18 @@ function ServerSideDataGrid() { ); return ( -
-
- setVerbose(e.target.checked)} - /> - } - label="Verbose" +
+ {isInitialized ? ( + -
-
- {isInitialized ? ( - - ) : ( - - )} -
+ ) : ( + + )}
); } diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.js b/docs/data/data-grid/server-side-data/ServerSideTreeData.js index f81c57f7db29..57458f9fcef0 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.js @@ -11,37 +11,15 @@ const dataSetOptions = { treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, }; -const dataSource = { - getRows: async (params) => { - const urlParams = new URLSearchParams({ - paginationModel: encodeURIComponent(JSON.stringify(params.paginationModel)), - filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), - sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), - groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), - }); - const serverResponse = await fetch( - `https://mui.com/x/api/data-grid?${urlParams.toString()}`, - ); - const getRowsResponse = await serverResponse.json(); - return { - rows: getRowsResponse.rows, - rowCount: getRowsResponse.rowCount, - }; - }, - getGroupKey: (row) => row[dataSetOptions.treeData.groupingField], - getChildrenCount: (row) => row.descendantCount, -}; - export default function ServerSideTreeData() { const apiRef = useGridApiRef(); - const { isInitialized, ...props } = useMockServer(dataSetOptions, { - startServer: true, - }); + const { isInitialized, fetchRows, columns, initialState } = + useMockServer(dataSetOptions); - const initialState = React.useMemo( + const initialStateWithPagination = React.useMemo( () => ({ - ...props.initialState, + ...initialState, pagination: { paginationModel: { pageSize: 5, @@ -49,7 +27,32 @@ export default function ServerSideTreeData() { rowCount: 0, }, }), - [props.initialState], + [initialState], + ); + + const dataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent( + JSON.stringify(params.paginationModel), + ), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + getGroupKey: (row) => row[dataSetOptions.treeData.groupingField], + getChildrenCount: (row) => row.descendantCount, + }), + [fetchRows], ); return ( @@ -58,13 +61,13 @@ export default function ServerSideTreeData() {
{isInitialized ? ( diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx index 38ff1c3e1002..a42512d4a549 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx @@ -17,37 +17,15 @@ const dataSetOptions = { treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, }; -const dataSource: GridDataSource = { - getRows: async (params) => { - const urlParams = new URLSearchParams({ - paginationModel: encodeURIComponent(JSON.stringify(params.paginationModel)), - filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), - sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), - groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), - }); - const serverResponse = await fetch( - `https://mui.com/x/api/data-grid?${urlParams.toString()}`, - ); - const getRowsResponse = await serverResponse.json(); - return { - rows: getRowsResponse.rows, - rowCount: getRowsResponse.rowCount, - }; - }, - getGroupKey: (row) => row[dataSetOptions.treeData.groupingField], - getChildrenCount: (row) => row.descendantCount, -}; - export default function ServerSideTreeData() { const apiRef = useGridApiRef(); - const { isInitialized, ...props } = useMockServer(dataSetOptions, { - startServer: true, - }); + const { isInitialized, fetchRows, columns, initialState } = + useMockServer(dataSetOptions); - const initialState: GridInitialState = React.useMemo( + const initialStateWithPagination: GridInitialState = React.useMemo( () => ({ - ...props.initialState, + ...initialState, pagination: { paginationModel: { pageSize: 5, @@ -55,7 +33,32 @@ export default function ServerSideTreeData() { rowCount: 0, }, }), - [props.initialState], + [initialState], + ); + + const dataSource: GridDataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent( + JSON.stringify(params.paginationModel), + ), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + getGroupKey: (row) => row[dataSetOptions.treeData.groupingField], + getChildrenCount: (row) => row.descendantCount, + }), + [fetchRows], ); return ( @@ -64,13 +67,13 @@ export default function ServerSideTreeData() {
{isInitialized ? ( diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index 7c5daaf9a718..9404ed687af9 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -177,6 +177,12 @@ The Data source supports caching the data it receives from the server by default The out-of-the-box cache is a simple in-memory cache that stores the data in a plain object. It could be seen in action in the demo below. +:::info +The demos on this page above 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. +::: + {{"demo": "ServerSideDataGrid.js", "bg": "inline"}} ### Custom cache 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 94ddacab893a..8199aa1742ae 100644 --- a/docs/data/data-grid/server-side-data/tree-data.md +++ b/docs/data/data-grid/server-side-data/tree-data.md @@ -31,7 +31,9 @@ Following tree-data example supports filtering, sorting, and pagination on the s {{"demo": "ServerSideTreeData.js", "bg": "inline"}} :::info -The demo above uses a utility `msw` to create a mock server that intercepts the actual Network calls and provide data. Open Network tab in the browser's developer tools to see the requests and corresponding responses. +The demos on this page above 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. ::: ## Error handling diff --git a/docs/public/mockServiceWorker.js b/docs/public/mockServiceWorker.js deleted file mode 100644 index 55e469b28b71..000000000000 --- a/docs/public/mockServiceWorker.js +++ /dev/null @@ -1,281 +0,0 @@ -/* eslint-disable */ -/* tslint:disable */ - -/** - * Mock Service Worker. - * @see https://github.com/mswjs/msw - * - Please do NOT modify this file. - * - Please do NOT serve this file on production. - */ - -const PACKAGE_VERSION = '2.3.0'; -const INTEGRITY_CHECKSUM = '26357c79639bfa20d64c0efca2a87423'; -const IS_MOCKED_RESPONSE = Symbol('isMockedResponse'); -const activeClientIds = new Set(); - -self.addEventListener('install', function () { - self.skipWaiting(); -}); - -self.addEventListener('activate', function (event) { - event.waitUntil(self.clients.claim()); -}); - -self.addEventListener('message', async function (event) { - const clientId = event.source.id; - - if (!clientId || !self.clients) { - return; - } - - const client = await self.clients.get(clientId); - - if (!client) { - return; - } - - const allClients = await self.clients.matchAll({ - type: 'window', - }); - - switch (event.data) { - case 'KEEPALIVE_REQUEST': { - sendToClient(client, { - type: 'KEEPALIVE_RESPONSE', - }); - break; - } - - case 'INTEGRITY_CHECK_REQUEST': { - sendToClient(client, { - type: 'INTEGRITY_CHECK_RESPONSE', - payload: { - packageVersion: PACKAGE_VERSION, - checksum: INTEGRITY_CHECKSUM, - }, - }); - break; - } - - case 'MOCK_ACTIVATE': { - activeClientIds.add(clientId); - - sendToClient(client, { - type: 'MOCKING_ENABLED', - payload: true, - }); - break; - } - - case 'MOCK_DEACTIVATE': { - activeClientIds.delete(clientId); - break; - } - - case 'CLIENT_CLOSED': { - activeClientIds.delete(clientId); - - const remainingClients = allClients.filter((client) => { - return client.id !== clientId; - }); - - // Unregister itself when there are no more clients - if (remainingClients.length === 0) { - self.registration.unregister(); - } - - break; - } - } -}); - -self.addEventListener('fetch', function (event) { - const { request } = event; - - // Bypass navigation requests. - if (request.mode === 'navigate') { - return; - } - - // Opening the DevTools triggers the "only-if-cached" request - // that cannot be handled by the worker. Bypass such requests. - if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { - return; - } - - // Bypass all requests when there are no active clients. - // Prevents the self-unregistered worked from handling requests - // after it's been deleted (still remains active until the next reload). - if (activeClientIds.size === 0) { - return; - } - - // Generate unique request ID. - const requestId = crypto.randomUUID(); - event.respondWith(handleRequest(event, requestId)); -}); - -async function handleRequest(event, requestId) { - const client = await resolveMainClient(event); - const response = await getResponse(event, client, requestId); - - // Send back the response clone for the "response:*" life-cycle events. - // Ensure MSW is active and ready to handle the message, otherwise - // this message will pend indefinitely. - if (client && activeClientIds.has(client.id)) { - (async function () { - const responseClone = response.clone(); - - sendToClient( - client, - { - type: 'RESPONSE', - payload: { - requestId, - isMockedResponse: IS_MOCKED_RESPONSE in response, - type: responseClone.type, - status: responseClone.status, - statusText: responseClone.statusText, - body: responseClone.body, - headers: Object.fromEntries(responseClone.headers.entries()), - }, - }, - [responseClone.body], - ); - })(); - } - - return response; -} - -// Resolve the main client for the given event. -// Client that issues a request doesn't necessarily equal the client -// that registered the worker. It's with the latter the worker should -// communicate with during the response resolving phase. -async function resolveMainClient(event) { - const client = await self.clients.get(event.clientId); - - if (client?.frameType === 'top-level') { - return client; - } - - const allClients = await self.clients.matchAll({ - type: 'window', - }); - - return allClients - .filter((client) => { - // Get only those clients that are currently visible. - return client.visibilityState === 'visible'; - }) - .find((client) => { - // Find the client ID that's recorded in the - // set of clients that have registered the worker. - return activeClientIds.has(client.id); - }); -} - -async function getResponse(event, client, requestId) { - const { request } = event; - - // Clone the request because it might've been already used - // (i.e. its body has been read and sent to the client). - const requestClone = request.clone(); - - function passthrough() { - const headers = Object.fromEntries(requestClone.headers.entries()); - - // Remove internal MSW request header so the passthrough request - // complies with any potential CORS preflight checks on the server. - // Some servers forbid unknown request headers. - delete headers['x-msw-intention']; - - return fetch(requestClone, { headers }); - } - - // Bypass mocking when the client is not active. - if (!client) { - return passthrough(); - } - - // Bypass initial page load requests (i.e. static assets). - // The absence of the immediate/parent client in the map of the active clients - // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet - // and is not ready to handle requests. - if (!activeClientIds.has(client.id)) { - return passthrough(); - } - - // Notify the client that a request has been intercepted. - const requestBuffer = await request.arrayBuffer(); - const clientMessage = await sendToClient( - client, - { - type: 'REQUEST', - payload: { - id: requestId, - url: request.url, - mode: request.mode, - method: request.method, - headers: Object.fromEntries(request.headers.entries()), - cache: request.cache, - credentials: request.credentials, - destination: request.destination, - integrity: request.integrity, - redirect: request.redirect, - referrer: request.referrer, - referrerPolicy: request.referrerPolicy, - body: requestBuffer, - keepalive: request.keepalive, - }, - }, - [requestBuffer], - ); - - switch (clientMessage.type) { - case 'MOCK_RESPONSE': { - return respondWithMock(clientMessage.data); - } - - case 'PASSTHROUGH': { - return passthrough(); - } - } - - return passthrough(); -} - -function sendToClient(client, message, transferrables = []) { - return new Promise((resolve, reject) => { - const channel = new MessageChannel(); - - channel.port1.onmessage = (event) => { - if (event.data && event.data.error) { - return reject(event.data.error); - } - - resolve(event.data); - }; - - client.postMessage(message, [channel.port2].concat(transferrables.filter(Boolean))); - }); -} - -async function respondWithMock(response) { - // Setting response status code to 0 is a no-op. - // However, when responding with a "Response.error()", the produced Response - // instance will have status code set to 0. Since it's not possible to create - // a Response instance with status code 0, handle that use-case separately. - if (response.status === 0) { - return Response.error(); - } - - const mockedResponse = new Response(response.body, response); - - Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { - value: true, - enumerable: true, - }); - - return mockedResponse; -} diff --git a/packages/x-data-grid-generator/package.json b/packages/x-data-grid-generator/package.json index 18bf1eff52ca..e750b2efed62 100644 --- a/packages/x-data-grid-generator/package.json +++ b/packages/x-data-grid-generator/package.json @@ -38,8 +38,7 @@ "@mui/x-data-grid-premium": "workspace:*", "chance": "^1.1.11", "clsx": "^2.1.1", - "lru-cache": "^7.18.3", - "msw": "^2.3.0" + "lru-cache": "^7.18.3" }, "devDependencies": { "@types/chance": "^1.1.6", diff --git a/packages/x-data-grid-generator/src/hooks/useMockServer.ts b/packages/x-data-grid-generator/src/hooks/useMockServer.ts index a5d39c0e63ad..d85b4d8276f7 100644 --- a/packages/x-data-grid-generator/src/hooks/useMockServer.ts +++ b/packages/x-data-grid-generator/src/hooks/useMockServer.ts @@ -1,7 +1,5 @@ import * as React from 'react'; import LRUCache from 'lru-cache'; -import { http, HttpResponse } from 'msw'; -import { SetupWorkerApi } from 'msw/browser'; import { getGridDefaultColumnTypes, GridRowModel, @@ -81,11 +79,10 @@ const defaultColDef = getGridDefaultColumnTypes(); export const useMockServer = ( dataSetOptions?: Partial, - serverOptions?: ServerOptions & { startServer?: boolean; verbose?: boolean }, + serverOptions?: ServerOptions & { verbose?: boolean }, shouldRequestsFail?: boolean, ): UseMockServerResponse => { const [isInitialized, setIsInitialized] = React.useState(false); - const [worker, setWorker] = React.useState(); const [data, setData] = React.useState(); const [index, setIndex] = React.useState(0); const shouldRequestsFailRef = React.useRef(shouldRequestsFail ?? false); @@ -204,11 +201,11 @@ export const useMockServer = ( }); } const params = decodeParams(requestUrl); - const verbose = serverOptions?.verbose ?? false; + const verbose = serverOptions?.verbose ?? true; // eslint-disable-next-line no-console const print = console.info; if (verbose) { - print('MUI X: SERVER REQUEST RECIEVED WITH PARAMS', params); + print('MUI X: DATASOURCE REQUEST', params); } let getRowsResponse: GridGetRowsResponse; const serverOptionsWithDefault = { @@ -223,7 +220,7 @@ export const useMockServer = ( const delay = randomInt(minDelay, maxDelay); return new Promise((_, reject) => { if (verbose) { - print('MUI X: SERVER REQUEST FAILURE WITH PARAMS', params); + print('MUI X: DATASOURCE REQUEST FAILURE', params); } setTimeout(() => reject(new Error('Could not fetch the data')), delay); }); @@ -254,7 +251,7 @@ export const useMockServer = ( return new Promise((resolve) => { if (verbose) { - print('MUI X: SERVER RESPONSE WITH PARAMS', params, getRowsResponse); + print('MUI X: DATASOURCE RESPONSE', params, getRowsResponse); } resolve(getRowsResponse); }); @@ -271,59 +268,10 @@ export const useMockServer = ( ); React.useEffect(() => { - if (!data || !serverOptions?.startServer) { - return; - } - async function startServer() { - if (typeof window !== 'undefined') { - // eslint-disable-next-line global-require - const { setupWorker } = require('msw/browser'); - if (!setupWorker) { - return; - } - const handlers = [ - http.get(BASE_URL, async ({ request }) => { - if (!request.url) { - return HttpResponse.json({ error: 'Bad request.' }, { status: 400 }); - } - try { - if (shouldRequestsFail) { - return HttpResponse.json({ error: 'Could not fetch the data' }, { status: 500 }); - } - const response = await fetchRows(request.url); - return HttpResponse.json(response); - } catch (error) { - return HttpResponse.json({ error }, { status: 500 }); - } - }), - ]; - const w = setupWorker(...handlers); - try { - await w.start({ quiet: true }); - setWorker(w); - } catch (e) { - console.error(e); - } - } - } - startServer(); - // eslint-disable-next-line consistent-return - return () => { - if (worker) { - setWorker((prev) => { - prev?.stop(); - return undefined; - }); - } - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [fetchRows, data, shouldRequestsFail, serverOptions?.startServer]); - - React.useEffect(() => { - if (data && (!serverOptions?.startServer || worker) && !isInitialized) { + if (data && !isInitialized) { setIsInitialized(true); } - }, [data, worker, isInitialized, serverOptions?.startServer]); + }, [data, isInitialized]); return { columns: columnsWithDefaultColDef, diff --git a/packages/x-data-grid/src/components/containers/GridRootStyles.ts b/packages/x-data-grid/src/components/containers/GridRootStyles.ts index b3731b380ea2..5fbb98fefda2 100644 --- a/packages/x-data-grid/src/components/containers/GridRootStyles.ts +++ b/packages/x-data-grid/src/components/containers/GridRootStyles.ts @@ -115,7 +115,9 @@ export const GridRootStyles = styled('div', { { [`& .${c.withBorderColor}`]: styles.withBorderColor }, { [`& .${c.treeDataGroupingCell}`]: styles.treeDataGroupingCell }, { [`& .${c.treeDataGroupingCellToggle}`]: styles.treeDataGroupingCellToggle }, - { [`& .${c.treeDataGroupingCellLoadingContainer}`]: styles.treeDataGroupingCellLoadingContainer }, + { + [`& .${c.treeDataGroupingCellLoadingContainer}`]: styles.treeDataGroupingCellLoadingContainer, + }, { [`& .${c.detailPanelToggleCell}`]: styles.detailPanelToggleCell }, { [`& .${c['detailPanelToggleCell--expanded']}`]: styles['detailPanelToggleCell--expanded'], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ea980b393016..1a875a8c17b1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -992,9 +992,6 @@ importers: lru-cache: specifier: ^7.18.3 version: 7.18.3 - msw: - specifier: ^2.3.0 - version: 2.3.0(typescript@5.4.5) react: specifier: ^17.0.0 || ^18.0.0 version: 18.2.0 @@ -2989,18 +2986,6 @@ packages: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: false - /@bundled-es-modules/cookie@2.0.0: - resolution: {integrity: sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==} - dependencies: - cookie: 0.5.0 - dev: false - - /@bundled-es-modules/statuses@1.0.1: - resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==} - dependencies: - statuses: 2.0.1 - dev: false - /@colors/colors@1.5.0: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} @@ -3523,43 +3508,6 @@ packages: engines: {node: '>=6.9.0'} dev: true - /@inquirer/confirm@3.1.7: - resolution: {integrity: sha512-BZjjj19W8gnh5UGFTdP5ZxpgMNRjy03Dzq3k28sB2MDlEUFrcyTkMEoGgvBmGpUw0vNBoCJkTcbHZ3e9tb+d+w==} - engines: {node: '>=18'} - dependencies: - '@inquirer/core': 8.2.0 - '@inquirer/type': 1.3.1 - dev: false - - /@inquirer/core@8.2.0: - resolution: {integrity: sha512-pexNF9j2orvMMTgoQ/uKOw8V6/R7x/sIDwRwXRhl4i0pPSh6paRzFehpFKpfMbqix1/+gzCekhYTmVbQpWkVjQ==} - engines: {node: '>=18'} - dependencies: - '@inquirer/figures': 1.0.1 - '@inquirer/type': 1.3.1 - '@types/mute-stream': 0.0.4 - '@types/node': 18.19.33 - '@types/wrap-ansi': 3.0.0 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - cli-spinners: 2.9.2 - cli-width: 4.1.0 - mute-stream: 1.0.0 - signal-exit: 4.1.0 - strip-ansi: 6.0.1 - wrap-ansi: 6.2.0 - dev: false - - /@inquirer/figures@1.0.1: - resolution: {integrity: sha512-mtup3wVKia3ZwULPHcbs4Mor8Voi+iIXEWD7wCNbIO6lYR62oPCTQyrddi5OMYVXHzeCSoneZwJuS8sBvlEwDw==} - engines: {node: '>=18'} - dev: false - - /@inquirer/type@1.3.1: - resolution: {integrity: sha512-Pe3PFccjPVJV1vtlfVvm9OnlbxqdnP5QcscFEFEnK5quChf1ufZtM0r8mR5ToWHMxZOh0s8o/qp9ANGRTo/DAw==} - engines: {node: '>=18'} - dev: false - /@isaacs/cliui@8.0.2: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -3725,35 +3673,6 @@ packages: semver: 5.7.2 dev: true - /@mswjs/cookies@1.1.0: - resolution: {integrity: sha512-0ZcCVQxifZmhwNBoQIrystCb+2sWBY2Zw8lpfJBPCHGCA/HWqehITeCRVIv4VMy8MPlaHo2w2pTHFV2pFfqKPw==} - engines: {node: '>=18'} - dev: false - - /@mswjs/interceptors@0.27.2: - resolution: {integrity: sha512-mE6PhwcoW70EX8+h+Y/4dLfHk33GFt/y5PzDJz56ktMyaVGFXMJ5BYLbUjdmGEABfE0x5GgAGyKbrbkYww2s3A==} - engines: {node: '>=18'} - dependencies: - '@open-draft/deferred-promise': 2.2.0 - '@open-draft/logger': 0.3.0 - '@open-draft/until': 2.1.0 - is-node-process: 1.2.0 - outvariant: 1.4.2 - strict-event-emitter: 0.5.1 - dev: true - - /@mswjs/interceptors@0.29.1: - resolution: {integrity: sha512-3rDakgJZ77+RiQUuSK69t1F0m8BQKA8Vh5DCS5V0DWvNY67zob2JhhQrhCO0AKLGINTRSFd1tBaHcJTkhefoSw==} - engines: {node: '>=18'} - dependencies: - '@open-draft/deferred-promise': 2.2.0 - '@open-draft/logger': 0.3.0 - '@open-draft/until': 2.1.0 - is-node-process: 1.2.0 - outvariant: 1.4.2 - strict-event-emitter: 0.5.1 - dev: false - /@mui/base@5.0.0-beta.40(@types/react@18.2.60)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==} engines: {node: '>=12.0.0'} @@ -4849,18 +4768,6 @@ packages: '@octokit/openapi-types': 18.1.1 dev: true - /@open-draft/deferred-promise@2.2.0: - resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} - - /@open-draft/logger@0.3.0: - resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} - dependencies: - is-node-process: 1.2.0 - outvariant: 1.4.2 - - /@open-draft/until@2.1.0: - resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} - /@opentelemetry/api-logs@0.50.0: resolution: {integrity: sha512-JdZuKrhOYggqOpUljAq4WWNi5nB10PmgoF0y2CvedLGXd0kSawb/UBnWT8gg1ND3bHCNHStAIVT0ELlxJJRqrA==} engines: {node: '>=14'} @@ -5296,7 +5203,7 @@ packages: '@swc/counter': 0.1.3 tslib: 2.6.2 dev: false - + /@tanstack/query-core@5.32.0: resolution: {integrity: sha512-Z3flEgCat55DRXU5UMwYU1U+DgFZKA3iufyOKs+II7iRAo0uXkeU7PH5e6sOH1CGEag0IpKmZxlUFpCg6roSKw==} dev: false @@ -5431,10 +5338,6 @@ packages: resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} dev: true - /@types/cookie@0.6.0: - resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} - dev: false - /@types/cors@2.8.17: resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==} dependencies: @@ -5644,12 +5547,6 @@ packages: moment: 2.30.1 dev: true - /@types/mute-stream@0.0.4: - resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==} - dependencies: - '@types/node': 18.19.33 - dev: false - /@types/node@18.19.33: resolution: {integrity: sha512-NR9+KrpSajr2qBVp/Yt5TU/rp+b5Mayi3+OlMlcg2cVCfRmcG5PWZ7S4+MG9PZ5gWBoc9Pd0BKSRViuBCRPu0A==} dependencies: @@ -5764,10 +5661,6 @@ packages: resolution: {integrity: sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==} dev: true - /@types/statuses@2.0.5: - resolution: {integrity: sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==} - dev: false - /@types/stylis@4.2.5: resolution: {integrity: sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==} dev: false @@ -5797,10 +5690,6 @@ packages: - webpack-cli dev: true - /@types/wrap-ansi@3.0.0: - resolution: {integrity: sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==} - dev: false - /@types/ws@7.4.7: resolution: {integrity: sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==} dependencies: @@ -6337,6 +6226,7 @@ packages: engines: {node: '>=8'} dependencies: type-fest: 0.21.3 + dev: true /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} @@ -7479,17 +7369,13 @@ packages: /cli-spinners@2.9.2: resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} engines: {node: '>=6'} + dev: true /cli-width@3.0.0: resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} engines: {node: '>= 10'} dev: true - /cli-width@4.1.0: - resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} - engines: {node: '>= 12'} - dev: false - /client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} dev: false @@ -7882,6 +7768,7 @@ packages: /cookie@0.5.0: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} engines: {node: '>= 0.6'} + dev: true /copy-descriptor@0.1.1: resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==} @@ -10401,11 +10288,6 @@ packages: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} dev: true - /graphql@16.8.1: - resolution: {integrity: sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==} - engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} - dev: false - /gtoken@7.0.1: resolution: {integrity: sha512-KcFVtoP1CVFtQu0aSk3AyAt2og66PFhZAlkUOuWKwzMLoulHXG5W5wE5xAnHb+yl3/wEFoqGW7/cDGMU8igDZQ==} engines: {node: '>=14.0.0'} @@ -10545,10 +10427,6 @@ packages: hasBin: true dev: true - /headers-polyfill@4.0.3: - resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} - dev: false - /hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} dependencies: @@ -11128,9 +11006,6 @@ packages: engines: {node: '>= 0.4'} dev: true - /is-node-process@1.2.0: - resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} - /is-number-object@1.0.7: resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} engines: {node: '>= 0.4'} @@ -12981,37 +12856,6 @@ packages: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} dev: true - /msw@2.3.0(typescript@5.4.5): - resolution: {integrity: sha512-cDr1q/QTMzaWhY8n9lpGhceY209k29UZtdTgJ3P8Bzne3TSMchX2EM/ldvn4ATLOktpCefCU2gcEgzHc31GTPw==} - engines: {node: '>=18'} - hasBin: true - requiresBuild: true - peerDependencies: - typescript: '>= 4.7.x' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@bundled-es-modules/cookie': 2.0.0 - '@bundled-es-modules/statuses': 1.0.1 - '@inquirer/confirm': 3.1.7 - '@mswjs/cookies': 1.1.0 - '@mswjs/interceptors': 0.29.1 - '@open-draft/until': 2.1.0 - '@types/cookie': 0.6.0 - '@types/statuses': 2.0.5 - chalk: 4.1.2 - graphql: 16.8.1 - headers-polyfill: 4.0.3 - is-node-process: 1.2.0 - outvariant: 1.4.2 - path-to-regexp: 6.2.1 - strict-event-emitter: 0.5.1 - type-fest: 4.18.2 - typescript: 5.4.5 - yargs: 17.7.2 - dev: false - /multimatch@5.0.0: resolution: {integrity: sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==} engines: {node: '>=10'} @@ -13037,6 +12881,7 @@ packages: /mute-stream@1.0.0: resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dev: true /mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -13788,9 +13633,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /outvariant@1.4.2: - resolution: {integrity: sha512-Ou3dJ6bA/UJ5GVHxah4LnqDwZRwAmWxrG3wtrHrbGnP4RnLCtA64A4F+ae7Y8ww660JaddSoArUR5HjipWSHAQ==} - /override-require@1.1.1: resolution: {integrity: sha512-eoJ9YWxFcXbrn2U8FKT6RV+/Kj7fiGAB1VvHzbYKt8xM5ZuKZgCGvnHzDxmreEjcBH28ejg5MiOH4iyY1mQnkg==} dev: true @@ -14172,6 +14014,7 @@ packages: /path-to-regexp@6.2.1: resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} + dev: true /path-type@3.0.0: resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} @@ -15850,6 +15693,7 @@ packages: /statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} + dev: true /stop-iteration-iterator@1.0.0: resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} @@ -15888,14 +15732,6 @@ packages: queue-tick: 1.0.1 dev: true - /strict-event-emitter@0.5.1: - resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} - - /strict-uri-encode@2.0.0: - resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} - engines: {node: '>=4'} - dev: true - /string-replace-loader@3.1.0(webpack@5.91.0): resolution: {integrity: sha512-5AOMUZeX5HE/ylKDnEa/KKBqvlnFmRZudSOjVJHxhoJg9QYTwl1rECx7SLR8BBH7tfxb4Rp7EM2XVfQFxIhsbQ==} peerDependencies: @@ -16525,6 +16361,7 @@ packages: /type-fest@0.21.3: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} + dev: true /type-fest@0.4.1: resolution: {integrity: sha512-IwzA/LSfD2vC1/YDYMv/zHP4rDF1usCwllsDpbolT3D4fUepIO7f9K70jjmUewU/LmGUKJcwcVtDCpnKk4BPMw==} @@ -16546,11 +16383,6 @@ packages: engines: {node: '>=12.20'} dev: true - /type-fest@4.18.2: - resolution: {integrity: sha512-+suCYpfJLAe4OXS6+PPXjW3urOS4IoP9waSiLuXfLgqZODKw/aWwASvzqE886wA0kQgGy0mIWyhd87VpqIy6Xg==} - engines: {node: '>=16'} - dev: false - /type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -16617,6 +16449,7 @@ packages: resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} engines: {node: '>=14.17'} hasBin: true + dev: true /ua-parser-js@0.7.37: resolution: {integrity: sha512-xV8kqRKM+jhMvcHWUKthV9fNebIzrNy//2O9ZwWcfiBFR5f25XVZPLlEajk/sf3Ra15V92isyQqnIEXRDaZWEA==} @@ -17221,6 +17054,7 @@ packages: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 + dev: true /wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} From f06bff666d5d4d619b5dfa8ee5b2562e65cecfff Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Wed, 5 Jun 2024 16:30:47 +0500 Subject: [PATCH 53/90] Add TTL option to the default cache --- .../server-side-data/ServerSideDataGridTTL.js | 65 +++++++++++++++ .../ServerSideDataGridTTL.tsx | 69 ++++++++++++++++ docs/data/data-grid/server-side-data/index.md | 20 ++++- .../x/api/data-grid/data-grid-premium.json | 7 +- docs/pages/x/api/data-grid/data-grid-pro.json | 7 +- docs/pages/x/api/data-grid/data-grid.json | 6 ++ .../data-grid-premium/data-grid-premium.json | 7 +- .../data-grid-pro/data-grid-pro.json | 7 +- .../data-grid/data-grid/data-grid.json | 4 + .../src/DataGridPremium/DataGridPremium.tsx | 6 -- .../src/DataGridPro/DataGridPro.tsx | 6 -- .../src/DataGridPro/useDataGridProProps.ts | 1 - .../src/hooks/features/dataSource/cache.ts | 51 ++++++++++++ .../dataSource/useGridDataSourceCache.ts | 81 +++++-------------- .../src/hooks/features/index.ts | 1 + .../src/models/dataGridProProps.ts | 7 +- scripts/x-data-grid-premium.exports.json | 1 + scripts/x-data-grid-pro.exports.json | 1 + 18 files changed, 260 insertions(+), 87 deletions(-) create mode 100644 docs/data/data-grid/server-side-data/ServerSideDataGridTTL.js create mode 100644 docs/data/data-grid/server-side-data/ServerSideDataGridTTL.tsx create mode 100644 packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.js b/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.js new file mode 100644 index 000000000000..b51f35890611 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.js @@ -0,0 +1,65 @@ +import * as React from 'react'; +import { DataGridPro, SimpleServerSideCache } from '@mui/x-data-grid-pro'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import LoadingSlate from './LoadingSlateNoSnap'; + +const lowTTLCache = new SimpleServerSideCache({ ttl: 1000 * 60 * 1 }); // 1 minute + +function ServerSideDataGridTTL() { + const { isInitialized, columns, initialState, fetchRows } = useMockServer( + {}, + { useCursorPagination: false }, + ); + + const dataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent( + JSON.stringify(params.paginationModel), + ), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + }), + [fetchRows], + ); + + const initialStateWithPagination = React.useMemo( + () => ({ + ...initialState, + pagination: { + paginationModel: { pageSize: 10, page: 0 }, + rowCount: 0, + }, + }), + [initialState], + ); + + return ( +
+ {isInitialized ? ( + + ) : ( + + )} +
+ ); +} + +export default ServerSideDataGridTTL; diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.tsx new file mode 100644 index 000000000000..e21b76313179 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.tsx @@ -0,0 +1,69 @@ +import * as React from 'react'; +import { + DataGridPro, + GridDataSource, + SimpleServerSideCache, +} from '@mui/x-data-grid-pro'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import LoadingSlate from './LoadingSlateNoSnap'; + +const lowTTLCache = new SimpleServerSideCache({ ttl: 1000 * 60 * 1 }); // 1 minute + +function ServerSideDataGridTTL() { + const { isInitialized, columns, initialState, fetchRows } = useMockServer( + {}, + { useCursorPagination: false }, + ); + + const dataSource: GridDataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent( + JSON.stringify(params.paginationModel), + ), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + }), + [fetchRows], + ); + + const initialStateWithPagination = React.useMemo( + () => ({ + ...initialState, + pagination: { + paginationModel: { pageSize: 10, page: 0 }, + rowCount: 0, + }, + }), + [initialState], + ); + + return ( +
+ {isInitialized ? ( + + ) : ( + + )} +
+ ); +} + +export default ServerSideDataGridTTL; diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index 9404ed687af9..51d7c503c69a 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -175,7 +175,7 @@ It supports a property called `verbose`, in the demos below, you can set it usin The Data source supports caching the data it receives from the server by default. This means that if the user navigates to a page or expands a node that has already been fetched, the grid will not call the `getRows` function again to avoid unnecessary calls to the server. -The out-of-the-box cache is a simple in-memory cache that stores the data in a plain object. It could be seen in action in the demo below. +The `SimpleServerSideCache` is a simple in-memory cache that stores the data in a plain object. It could be seen in action in the demo below. :::info The demos on this page above 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. @@ -185,6 +185,24 @@ Open info section of the browser console to see the requests being made and the {{"demo": "ServerSideDataGrid.js", "bg": "inline"}} +### Customize the cache ttl + +The `SimpleServerSideCache` has a default `ttl` of 5 minutes. You can customize this by passing the `ttl` option in milliseconds to the `SimpleServerSideCache` constructor, and then passing it to the `unstable_dataSourceCache` prop. + +```tsx +import { SimpleServerSideCache } from '@mui/x-data-grid-pro'; + +const lowTTLCache = new SimpleServerSideCache({ ttl: 1000 * 60 * 1 }); // 1 minute + +; +``` + +{{"demo": "ServerSideDataGridTTL.js", "bg": "inline"}} + ### Custom cache To provide a custom cache, use `unstable_dataSourceCache` prop, which could be either written from scratch or based out of another cache library. This prop accepts a generic interface of type `GridDataSourceCache`. 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 5e79dd24c4c2..312030166a8f 100644 --- a/docs/pages/x/api/data-grid/data-grid-premium.json +++ b/docs/pages/x/api/data-grid/data-grid-premium.json @@ -58,7 +58,6 @@ "disableColumnResize": { "type": { "name": "bool" }, "default": "false" }, "disableColumnSelector": { "type": { "name": "bool" }, "default": "false" }, "disableColumnSorting": { "type": { "name": "bool" }, "default": "false" }, - "disableDataSourceCache": { "type": { "name": "bool" }, "default": "false" }, "disableDensitySelector": { "type": { "name": "bool" }, "default": "false" }, "disableEval": { "type": { "name": "bool" }, "default": "false" }, "disableMultipleColumnsFiltering": { "type": { "name": "bool" }, "default": "false" }, @@ -1825,6 +1824,12 @@ "description": "Styles applied to the root of the grouping column of the tree data.", "isGlobal": false }, + { + "key": "treeDataGroupingCellLoadingContainer", + "className": "MuiDataGridPremium-treeDataGroupingCellLoadingContainer", + "description": "Styles applied to the loading container of the grouping cell of the tree data.", + "isGlobal": false + }, { "key": "treeDataGroupingCellToggle", "className": "MuiDataGridPremium-treeDataGroupingCellToggle", 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 672ac1dfe1bb..844a038bf33e 100644 --- a/docs/pages/x/api/data-grid/data-grid-pro.json +++ b/docs/pages/x/api/data-grid/data-grid-pro.json @@ -45,7 +45,6 @@ "disableColumnResize": { "type": { "name": "bool" }, "default": "false" }, "disableColumnSelector": { "type": { "name": "bool" }, "default": "false" }, "disableColumnSorting": { "type": { "name": "bool" }, "default": "false" }, - "disableDataSourceCache": { "type": { "name": "bool" }, "default": "false" }, "disableDensitySelector": { "type": { "name": "bool" }, "default": "false" }, "disableEval": { "type": { "name": "bool" }, "default": "false" }, "disableMultipleColumnsFiltering": { "type": { "name": "bool" }, "default": "false" }, @@ -1742,6 +1741,12 @@ "description": "Styles applied to the root of the grouping column of the tree data.", "isGlobal": false }, + { + "key": "treeDataGroupingCellLoadingContainer", + "className": "MuiDataGridPro-treeDataGroupingCellLoadingContainer", + "description": "Styles applied to the loading container of the grouping cell of the tree data.", + "isGlobal": false + }, { "key": "treeDataGroupingCellToggle", "className": "MuiDataGridPro-treeDataGroupingCellToggle", diff --git a/docs/pages/x/api/data-grid/data-grid.json b/docs/pages/x/api/data-grid/data-grid.json index b7d2b16bc7ea..4f019ec5e72e 100644 --- a/docs/pages/x/api/data-grid/data-grid.json +++ b/docs/pages/x/api/data-grid/data-grid.json @@ -1627,6 +1627,12 @@ "description": "Styles applied to the root of the grouping column of the tree data.", "isGlobal": false }, + { + "key": "treeDataGroupingCellLoadingContainer", + "className": "MuiDataGrid-treeDataGroupingCellLoadingContainer", + "description": "Styles applied to the loading container of the grouping cell of the tree data.", + "isGlobal": false + }, { "key": "treeDataGroupingCellToggle", "className": "MuiDataGrid-treeDataGroupingCellToggle", 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 500b5274e14c..579b84e640df 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 @@ -81,9 +81,6 @@ "disableColumnSorting": { "description": "If true, the column sorting feature will be disabled." }, - "disableDataSourceCache": { - "description": "If true, the server-side cache will be disabled." - }, "disableDensitySelector": { "description": "If true, the density selector is disabled." }, @@ -1172,6 +1169,10 @@ "description": "Styles applied to {{nodeName}}.", "nodeName": "the root of the grouping column of the tree data" }, + "treeDataGroupingCellLoadingContainer": { + "description": "Styles applied to {{nodeName}}.", + "nodeName": "the loading container of the grouping cell of the tree data" + }, "treeDataGroupingCellToggle": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the toggle of the grouping cell of the tree data" 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 f8be967124d6..04f84285b842 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 @@ -70,9 +70,6 @@ "disableColumnSorting": { "description": "If true, the column sorting feature will be disabled." }, - "disableDataSourceCache": { - "description": "If true, the server-side cache will be disabled." - }, "disableDensitySelector": { "description": "If true, the density selector is disabled." }, @@ -1110,6 +1107,10 @@ "description": "Styles applied to {{nodeName}}.", "nodeName": "the root of the grouping column of the tree data" }, + "treeDataGroupingCellLoadingContainer": { + "description": "Styles applied to {{nodeName}}.", + "nodeName": "the loading container of the grouping cell of the tree data" + }, "treeDataGroupingCellToggle": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the toggle of the grouping cell of the tree data" diff --git a/docs/translations/api-docs/data-grid/data-grid/data-grid.json b/docs/translations/api-docs/data-grid/data-grid/data-grid.json index 770bf5af8fa5..b152c82c50d4 100644 --- a/docs/translations/api-docs/data-grid/data-grid/data-grid.json +++ b/docs/translations/api-docs/data-grid/data-grid/data-grid.json @@ -996,6 +996,10 @@ "description": "Styles applied to {{nodeName}}.", "nodeName": "the root of the grouping column of the tree data" }, + "treeDataGroupingCellLoadingContainer": { + "description": "Styles applied to {{nodeName}}.", + "nodeName": "the loading container of the grouping cell of the tree data" + }, "treeDataGroupingCellToggle": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the toggle of the grouping cell of the tree data" diff --git a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx index 86c751df0f49..c3c56ea06916 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx @@ -255,11 +255,6 @@ DataGridPremiumRaw.propTypes = { * @default false */ disableColumnSorting: PropTypes.bool, - /** - * If `true`, the server-side cache will be disabled. - * @default false - */ - disableDataSourceCache: PropTypes.bool, /** * If `true`, the density selector is disabled. * @default false @@ -1053,7 +1048,6 @@ DataGridPremiumRaw.propTypes = { getChildrenCount: PropTypes.func, getGroupKey: PropTypes.func, getRows: PropTypes.func.isRequired, - hasChildren: PropTypes.func, 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 98a2f3c506d8..ce229cfcb815 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx @@ -222,11 +222,6 @@ DataGridProRaw.propTypes = { * @default false */ disableColumnSorting: PropTypes.bool, - /** - * If `true`, the server-side cache will be disabled. - * @default false - */ - disableDataSourceCache: PropTypes.bool, /** * If `true`, the density selector is disabled. * @default false @@ -952,7 +947,6 @@ DataGridProRaw.propTypes = { getChildrenCount: PropTypes.func, getGroupKey: PropTypes.func, getRows: PropTypes.func.isRequired, - hasChildren: PropTypes.func, updateRow: PropTypes.func, }), unstable_dataSourceCache: PropTypes.shape({ diff --git a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts index 839190daa0ec..b0d8257cf872 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts +++ b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts @@ -37,7 +37,6 @@ const getDataGridProForcedProps: GetDataGridProForcedProps = (themedProps) => ({ */ export const DATA_GRID_PRO_PROPS_DEFAULT_VALUES: DataGridProPropsWithDefaultValue = { ...DATA_GRID_PROPS_DEFAULT_VALUES, - disableDataSourceCache: false, scrollEndThreshold: 80, treeData: false, defaultGroupingExpansionDepth: 0, 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 new file mode 100644 index 000000000000..8b586286dd74 --- /dev/null +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts @@ -0,0 +1,51 @@ +import { GridGetRowsParams, GridGetRowsResponse } from '../../../models'; + +type SimpleServerSideCacheConfig = { + /** + * The ttl for each cache entry in milliseconds. + * @default 300000 (Five minutes) + */ + ttl?: number; +}; + +export class SimpleServerSideCache { + private cache: Record; + + private ttl: number; + + constructor({ ttl = 300000 }: SimpleServerSideCacheConfig) { + this.cache = {}; + this.ttl = ttl; + } + + // eslint-disable-next-line class-methods-use-this + getKey(params: GridGetRowsParams) { + return JSON.stringify([ + params.paginationModel, + params.filterModel, + params.sortModel, + params.groupKeys, + ]); + } + + set(key: string, value: GridGetRowsResponse) { + const expiry = Date.now() + this.ttl; + this.cache[key] = { value, expiry }; + } + + get(key: string): GridGetRowsResponse | undefined { + const entry = this.cache[key]; + if (!entry) { + return undefined; + } + if (Date.now() > entry.expiry) { + delete this.cache[key]; + return undefined; + } + return entry.value; + } + + clear() { + this.cache = {}; + } +} diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts index 47014b3010b7..a0d37202842d 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts @@ -5,38 +5,10 @@ import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; import { GridGetRowsParams, GridGetRowsResponse, GridDataSourceCache } from '../../../models'; import { GridDataSourceCacheApi } from './interfaces'; - -class SimpleServerSideCache { - private cache: Record; - - constructor() { - this.cache = {}; - } - - static getKey(params: GridGetRowsParams) { - return JSON.stringify([ - params.paginationModel, - params.filterModel, - params.sortModel, - params.groupKeys, - ]); - } - - set(key: string, value: GridGetRowsResponse) { - this.cache[key] = value; - } - - get(key: string) { - return this.cache[key]; - } - - clear() { - this.cache = {}; - } -} +import { SimpleServerSideCache } from './cache'; const getDefaultCache = (cacheInstance: SimpleServerSideCache): GridDataSourceCache => ({ - getKey: SimpleServerSideCache.getKey, + getKey: cacheInstance.getKey, set: (key: string, value: GridGetRowsResponse) => cacheInstance.set(key as string, value as GridGetRowsResponse), get: (key: string) => cacheInstance.get(key as string), @@ -45,46 +17,37 @@ const getDefaultCache = (cacheInstance: SimpleServerSideCache): GridDataSourceCa export const useGridDataSourceCache = ( privateApiRef: React.MutableRefObject, - props: Pick< - DataGridProProcessedProps, - 'unstable_dataSource' | 'disableDataSourceCache' | 'unstable_dataSourceCache' - >, + props: Pick, ): void => { const defaultCache = useLazyRef(() => - getDefaultCache(new SimpleServerSideCache()), + getDefaultCache(new SimpleServerSideCache({})), ); - const cache = React.useRef( + const cache = React.useRef( props.unstable_dataSourceCache || defaultCache.current, ); - const getCacheData = React.useCallback( - (params: GridGetRowsParams) => { - if (props.disableDataSourceCache) { - return undefined; - } - const key = cache.current.getKey(params); - return cache.current.get(key); - }, - [props.disableDataSourceCache], - ); + const getCacheData = React.useCallback((params: GridGetRowsParams) => { + if (!cache.current) { + return undefined; + } + const key = cache.current.getKey(params); + return cache.current.get(key); + }, []); - const setCacheData = React.useCallback( - (params: GridGetRowsParams, data: GridGetRowsResponse) => { - if (props.disableDataSourceCache) { - return; - } - const key = cache.current.getKey(params); - cache.current.set(key, data); - }, - [props.disableDataSourceCache], - ); + const setCacheData = React.useCallback((params: GridGetRowsParams, data: GridGetRowsResponse) => { + if (!cache.current) { + return; + } + const key = cache.current.getKey(params); + cache.current.set(key, data); + }, []); const clearCache = React.useCallback(() => { - if (props.disableDataSourceCache) { + if (!cache.current) { return; } cache.current.clear(); - }, [props.disableDataSourceCache]); + }, []); const dataSourceCacheApi: GridDataSourceCacheApi = { getCacheData, @@ -100,7 +63,7 @@ export const useGridDataSourceCache = ( isFirstRender.current = false; return; } - if (props.unstable_dataSourceCache) { + if (props.unstable_dataSourceCache !== undefined) { cache.current = props.unstable_dataSourceCache; } }, [props.unstable_dataSourceCache]); 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 6b165b46f80e..dd9209be6a53 100644 --- a/packages/x-data-grid-pro/src/hooks/features/index.ts +++ b/packages/x-data-grid-pro/src/hooks/features/index.ts @@ -6,3 +6,4 @@ export * from './treeData'; export * from './detailPanel'; export * from './rowPinning'; export * from './dataSource/interfaces'; +export * from './dataSource/cache'; diff --git a/packages/x-data-grid-pro/src/models/dataGridProProps.ts b/packages/x-data-grid-pro/src/models/dataGridProProps.ts index a4bbd0dc5661..4d611b57ea06 100644 --- a/packages/x-data-grid-pro/src/models/dataGridProProps.ts +++ b/packages/x-data-grid-pro/src/models/dataGridProProps.ts @@ -112,11 +112,6 @@ export interface DataGridProPropsWithDefaultValue void; } diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 081ab74cbc85..17473ded010e 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -645,6 +645,7 @@ { "name": "selectedGridRowsSelector", "kind": "Variable" }, { "name": "selectedIdsLookupSelector", "kind": "Variable" }, { "name": "setupExcelExportWebWorker", "kind": "Function" }, + { "name": "SimpleServerSideCache", "kind": "Class" }, { "name": "SkeletonCellPropsOverrides", "kind": "Interface" }, { "name": "ToolbarPropsOverrides", "kind": "Interface" }, { "name": "unstable_resetCleanupTracking", "kind": "Variable" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index 6c6e0d00f09a..035fe2a8ebe3 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -596,6 +596,7 @@ { "name": "selectedGridRowsCountSelector", "kind": "Variable" }, { "name": "selectedGridRowsSelector", "kind": "Variable" }, { "name": "selectedIdsLookupSelector", "kind": "Variable" }, + { "name": "SimpleServerSideCache", "kind": "Class" }, { "name": "SkeletonCellPropsOverrides", "kind": "Interface" }, { "name": "ToolbarPropsOverrides", "kind": "Interface" }, { "name": "unstable_resetCleanupTracking", "kind": "Variable" }, From 0b185a34f9c8ca3bac847b886ee064f0eac82ece Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Wed, 5 Jun 2024 18:55:13 +0500 Subject: [PATCH 54/90] Fix prop validation --- packages/x-data-grid/src/internals/utils/propValidation.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/x-data-grid/src/internals/utils/propValidation.ts b/packages/x-data-grid/src/internals/utils/propValidation.ts index 6fa67ab6cc15..825db3991f71 100644 --- a/packages/x-data-grid/src/internals/utils/propValidation.ts +++ b/packages/x-data-grid/src/internals/utils/propValidation.ts @@ -35,6 +35,7 @@ export const propValidatorsDataGrid: PropValidator[] = [ (props) => (props.paginationMode === 'server' && props.rowCount == null && + !props.unstable_dataSource && [ "MUI X: The `rowCount` prop must be passed using `paginationMode='server'`", 'For more detail, see http://mui.com/components/data-grid/pagination/#index-based-pagination', From 4c28dcd465ca16b082a84e0f6858400aed82328a Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Wed, 5 Jun 2024 19:11:04 +0500 Subject: [PATCH 55/90] Fix loading indicator not showing with less concurrent requests --- .../src/hooks/features/dataSource/useGridDataSource.ts | 3 ++- 1 file changed, 2 insertions(+), 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 cd03c6f1c12c..4957d924b47f 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 @@ -107,9 +107,10 @@ export const useGridDataSource = ( const queueChildrenFetch = React.useCallback( (id: GridRowId) => { + privateApiRef.current.setChildrenLoading(id, true); nestedDataManager.enqueue([id]); }, - [nestedDataManager], + [privateApiRef, nestedDataManager], ); const fetchRowChildren = React.useCallback( From 2646b36cf44d57d60a09b34d57ae2b1117f10f90 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Wed, 5 Jun 2024 23:17:49 +0500 Subject: [PATCH 56/90] Fix types' issues --- .../data-grid/server-side-data/ServerSideDataGridNoCache.js | 2 +- .../data-grid/server-side-data/ServerSideDataGridNoCache.tsx | 2 +- .../server-side-data/ServerSideDataGridNoCache.tsx.preview | 2 +- .../data-grid/server-side-data/ServerSideErrorHandling.js | 2 +- .../data-grid/server-side-data/ServerSideErrorHandling.tsx | 2 +- .../server-side-data/ServerSideTreeDataErrorHandling.js | 2 +- .../server-side-data/ServerSideTreeDataErrorHandling.tsx | 2 +- docs/data/data-grid/server-side-data/index.md | 4 ++-- .../x-data-grid/src/components/containers/GridRootStyles.ts | 1 - 9 files changed, 9 insertions(+), 10 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js index 6f4aabe1d1c9..32016504b574 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js @@ -53,7 +53,7 @@ export default function ServerSideDataGridNoCache() { initialState={initialStateWithPagination} columns={columns} unstable_dataSource={dataSource} - disableDataSourceCache + unstable_dataSourceCache={null} pagination pageSizeOptions={pageSizeOptions} /> diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx index 6a841afe44fe..8d7b06203cb0 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx @@ -53,7 +53,7 @@ export default function ServerSideDataGridNoCache() { initialState={initialStateWithPagination} columns={columns} unstable_dataSource={dataSource} - disableDataSourceCache + unstable_dataSourceCache={null} pagination pageSizeOptions={pageSizeOptions} /> diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx.preview index 3e1802301caf..dc1ba74de974 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx.preview +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx.preview @@ -3,7 +3,7 @@ initialState={initialStateWithPagination} columns={columns} unstable_dataSource={dataSource} - disableDataSourceCache + unstable_dataSourceCache={null} pagination pageSizeOptions={pageSizeOptions} /> diff --git a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js index cb0b2be64309..32d7b9115a1f 100644 --- a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js +++ b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js @@ -113,7 +113,7 @@ export default function ServerSideErrorHandling() { {...props} unstable_dataSource={dataSource} unstable_onDataSourceError={(e) => setError(e.message)} - disableDataSourceCache + unstable_dataSourceCache={null} apiRef={apiRef} pagination pageSizeOptions={pageSizeOptions} diff --git a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx index b12c01341d2a..fb44d54ab616 100644 --- a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx @@ -119,7 +119,7 @@ export default function ServerSideErrorHandling() { {...props} unstable_dataSource={dataSource} unstable_onDataSourceError={(e) => setError(e.message)} - disableDataSourceCache + unstable_dataSourceCache={null} apiRef={apiRef} pagination pageSizeOptions={pageSizeOptions} diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js index 741d428ac2c2..7039f821d14d 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js @@ -103,7 +103,7 @@ export default function ServerSideTreeDataErrorHandling() { ); } }} - disableDataSourceCache + unstable_dataSourceCache={null} apiRef={apiRef} pagination pageSizeOptions={pageSizeOptions} diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx index e659492409b7..eb155464a782 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx @@ -108,7 +108,7 @@ export default function ServerSideTreeDataErrorHandling() { ); } }} - disableDataSourceCache + unstable_dataSourceCache={null} apiRef={apiRef} pagination pageSizeOptions={pageSizeOptions} diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index 51d7c503c69a..f62305acb658 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -221,13 +221,13 @@ The following demo uses cache used by a popular library [`swr`](https://github.c ### Disable caching -To disable the caching on the server-side data, pass the `disableDataSourceCache` prop. +To disable the caching on the server-side data, pass the `unstable_dataSourceCache={null}` prop. ```tsx ``` diff --git a/packages/x-data-grid/src/components/containers/GridRootStyles.ts b/packages/x-data-grid/src/components/containers/GridRootStyles.ts index 5fbb98fefda2..e1ea183b8bcb 100644 --- a/packages/x-data-grid/src/components/containers/GridRootStyles.ts +++ b/packages/x-data-grid/src/components/containers/GridRootStyles.ts @@ -660,7 +660,6 @@ export const GridRootStyles = styled('div', { [`& .${c['filler--borderTop']}`]: { borderTop: '1px solid var(--DataGrid-rowBorderColor)', }, - [`& .${c['datasource--loadingContainer']}`]: {}, }; return gridStyle; From 30909adac5ce56c58c6588d5161c091ba8570df1 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Thu, 6 Jun 2024 01:25:03 +0500 Subject: [PATCH 57/90] Fix a few issues --- .../hooks/features/dataSource/useGridDataSource.ts | 12 ++++++------ .../src/hooks/features/dataSource/utils.ts | 12 ++++-------- 2 files changed, 10 insertions(+), 14 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 4957d924b47f..6ec4485fde5a 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 @@ -9,7 +9,6 @@ import { useGridSelector, } from '@mui/x-data-grid'; import { gridRowGroupsToFetchSelector, GridStateInitializer } from '@mui/x-data-grid/internals'; -import { GridGetRowsResponse } from '../../../models'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; import { @@ -61,7 +60,7 @@ export const useGridDataSource = ( return; } - nestedDataManager.clearPendingRequests(); + nestedDataManager.clear(); scheduledGroups.current = 0; const dataSourceState = privateApiRef.current.state.dataSource; if (dataSourceState !== INITIAL_STATE) { @@ -70,9 +69,7 @@ export const useGridDataSource = ( const fetchParams = gridGetRowsParamsSelector(privateApiRef); - const cachedData = privateApiRef.current.getCacheData(fetchParams) as - | GridGetRowsResponse - | undefined; + const cachedData = privateApiRef.current.getCacheData(fetchParams); if (cachedData != null) { const rows = cachedData.rows; @@ -135,6 +132,7 @@ export const useGridDataSource = ( const cachedData = privateApiRef.current.getCacheData(fetchParams); + const isLoading = gridDataSourceLoadingSelector(privateApiRef)[id] ?? false; if (cachedData != null) { const rows = cachedData.rows; privateApiRef.current.caches.dataSource.groupKeys = rowNode.path; @@ -144,10 +142,12 @@ export const useGridDataSource = ( privateApiRef.current.setRowCount(cachedData.rowCount); } privateApiRef.current.setRowChildrenExpansion(id, true); + if (isLoading) { + privateApiRef.current.setChildrenLoading(id, false); + } return; } - const isLoading = gridDataSourceLoadingSelector(privateApiRef)[id] ?? false; if (!isLoading) { privateApiRef.current.setChildrenLoading(id, true); } 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 6f5a9b6c90e4..8fcdccb03c4f 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 @@ -56,20 +56,16 @@ export class NestedDataManager { for (let i = 0; i < loopLength; i += 1) { const id = fetchQueue[i]; this.queuedRequests.delete(id); - this.api.fetchRowChildren(id); this.pendingRequests.add(id); + this.api.fetchRowChildren(id); } }; public enqueue = async (ids: GridRowId[]) => { ids.forEach((id) => { - if (this.pendingRequests.size < this.maxConcurrentRequests) { - this.pendingRequests.add(id); - this.api.fetchRowChildren(id); - } else { - this.queuedRequests.add(id); - } + this.queuedRequests.add(id); }); + this.processQueue(); }; public setRequestSettled = (id: GridRowId) => { @@ -78,7 +74,7 @@ export class NestedDataManager { this.processQueue(); }; - public clearPendingRequests = () => { + public clear = () => { this.queuedRequests.clear(); Array.from(this.pendingRequests).forEach((id) => this.clearPendingRequest(id)); }; From 3576334cb3a1f5da84d333b365e91f814c60e981 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Thu, 6 Jun 2024 01:39:42 +0500 Subject: [PATCH 58/90] Improve docs a bit --- docs/data/data-grid/server-side-data/index.md | 20 +++++++------------ .../data-grid/server-side-data/tree-data.md | 2 +- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index f62305acb658..aadecc9cb82b 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -166,22 +166,16 @@ The following demo showcases this behavior. {{"demo": "ServerSideDataGrid.js", "bg": "inline"}} :::info -The demos used with server-side data use a utility `useMockServer` coming from the `@mui/x-data-grid-generator` package to simulate the server-side data fetching. +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. -It supports a property called `verbose`, in the demos below, you can set it using a checkbox. Set it checked to observe the request parameters and the response data coming from the `useMockServer` in the info section of the browser console. +Open info section of the browser console to see the requests being made and the data being fetched in response. ::: ## Data caching The Data source supports caching the data it receives from the server by default. This means that if the user navigates to a page or expands a node that has already been fetched, the grid will not call the `getRows` function again to avoid unnecessary calls to the server. -The `SimpleServerSideCache` is a simple in-memory cache that stores the data in a plain object. It could be seen in action in the demo below. - -:::info -The demos on this page above 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. -::: +The `SimpleServerSideCache` is the cache used by default which is a simple in-memory cache that stores the data in a plain object. It could be seen in action in the demo below. {{"demo": "ServerSideDataGrid.js", "bg": "inline"}} @@ -219,9 +213,9 @@ The following demo uses cache used by a popular library [`swr`](https://github.c {{"demo": "ServerSideDataGridWithSWR.js", "bg": "inline"}} -### Disable caching +### Disable cache -To disable the caching on the server-side data, pass the `unstable_dataSourceCache={null}` prop. +To disable the caching on the server-side data, pass `null` to the `unstable_dataSourceCache` prop. ```tsx ``` -The demo below uses the `useDemoDataSource` utility to simulate the server-side error. Change the value of success rate to make the server-side error occur randomly. - {{"demo": "ServerSideErrorHandling.js", "bg": "inline"}} ## Updating data 🚧 This feature is yet to be implemented, when completed, the method `dataSource.updateRow` will be called with the `GridRowModel` whenever the user edits a row. It will work in a similar way as the `processRowUpdate` prop. +Feel free to upvote the related GitHub [issue](https://github.com/mui/mui-x/issues/13261) to see this feature land faster. + ## API - [DataGrid](/x/api/data-grid/data-grid/) 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 8199aa1742ae..916d078320a5 100644 --- a/docs/data/data-grid/server-side-data/tree-data.md +++ b/docs/data/data-grid/server-side-data/tree-data.md @@ -31,7 +31,7 @@ Following tree-data example supports filtering, sorting, and pagination on the s {{"demo": "ServerSideTreeData.js", "bg": "inline"}} :::info -The demos on this page above 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 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. ::: From 7b119fc966b9767e7cc80da83f2b131be315e889 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Thu, 6 Jun 2024 20:34:15 +0500 Subject: [PATCH 59/90] Resolve a few comments --- .../server-side-data/LoadingSlateNoSnap.js | 33 ---------- .../server-side-data/ServerSideDataGrid.js | 21 +++---- .../server-side-data/ServerSideDataGrid.tsx | 21 +++---- .../ServerSideDataGridNoCache.js | 23 +++---- .../ServerSideDataGridNoCache.tsx | 23 +++---- .../ServerSideDataGridNoCache.tsx.preview | 20 +++--- .../server-side-data/ServerSideDataGridTTL.js | 25 +++----- .../ServerSideDataGridTTL.tsx | 25 +++----- .../ServerSideDataGridWithSWR.js | 23 +++---- .../ServerSideDataGridWithSWR.tsx | 23 +++---- .../ServerSideErrorHandling.js | 30 ++++----- .../ServerSideErrorHandling.tsx | 29 ++++----- .../server-side-data/ServerSideTreeData.js | 30 ++++----- .../server-side-data/ServerSideTreeData.tsx | 30 ++++----- .../ServerSideTreeData.tsx.preview | 14 +++++ .../ServerSideTreeDataCustomCache.js | 31 ++++------ .../ServerSideTreeDataCustomCache.tsx | 31 ++++------ .../ServerSideTreeDataCustomCache.tsx.preview | 15 +++++ .../ServerSideTreeDataErrorHandling.js | 61 ++++++++----------- .../ServerSideTreeDataErrorHandling.tsx | 61 ++++++++----------- docs/data/data-grid/server-side-data/index.md | 6 +- .../src/hooks/useMockServer.ts | 9 --- .../useDataGridPremiumProps.ts | 12 +--- .../src/DataGridPro/useDataGridProProps.ts | 11 +--- .../src/hooks/features/dataSource/cache.ts | 5 +- 25 files changed, 246 insertions(+), 366 deletions(-) delete mode 100644 docs/data/data-grid/server-side-data/LoadingSlateNoSnap.js create mode 100644 docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview create mode 100644 docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx.preview diff --git a/docs/data/data-grid/server-side-data/LoadingSlateNoSnap.js b/docs/data/data-grid/server-side-data/LoadingSlateNoSnap.js deleted file mode 100644 index 73ad4517bad6..000000000000 --- a/docs/data/data-grid/server-side-data/LoadingSlateNoSnap.js +++ /dev/null @@ -1,33 +0,0 @@ -import * as React from 'react'; -import CircularProgress from '@mui/material/CircularProgress'; -import { lighten, darken, alpha, styled } from '@mui/material/styles'; - -function getBorderColor(theme) { - if (theme.palette.mode === 'light') { - return lighten(alpha(theme.palette.divider, 1), 0.88); - } - return darken(alpha(theme.palette.divider, 1), 0.68); -} - -function getBorderRadius(theme) { - const radius = theme.shape.borderRadius; - return typeof radius === 'number' ? `${radius}px` : radius; -} - -const StyledDiv = styled('div')(({ theme }) => ({ - height: '100%', - width: '100%', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - border: `1px solid ${getBorderColor(theme)}`, - borderRadius: getBorderRadius(theme), -})); - -export default function LoadingSlateNoSnap() { - return ( - - - - ); -} diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGrid.js b/docs/data/data-grid/server-side-data/ServerSideDataGrid.js index 219caa9f4221..3c0dbeb76f86 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGrid.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGrid.js @@ -1,10 +1,9 @@ import * as React from 'react'; import { DataGridPro } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; -import LoadingSlate from './LoadingSlateNoSnap'; function ServerSideDataGrid() { - const { isInitialized, columns, initialState, fetchRows } = useMockServer( + const { columns, initialState, fetchRows } = useMockServer( {}, { useCursorPagination: false }, ); @@ -44,17 +43,13 @@ function ServerSideDataGrid() { return (
- {isInitialized ? ( - - ) : ( - - )} +
); } diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx index c10ccb657162..514b77b23bf9 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx @@ -1,10 +1,9 @@ import * as React from 'react'; import { DataGridPro, GridDataSource } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; -import LoadingSlate from './LoadingSlateNoSnap'; function ServerSideDataGrid() { - const { isInitialized, columns, initialState, fetchRows } = useMockServer( + const { columns, initialState, fetchRows } = useMockServer( {}, { useCursorPagination: false }, ); @@ -44,17 +43,13 @@ function ServerSideDataGrid() { return (
- {isInitialized ? ( - - ) : ( - - )} +
); } diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js index 32016504b574..e059e7b98b00 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js @@ -1,7 +1,6 @@ import * as React from 'react'; import { DataGridPro } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; -import LoadingSlate from './LoadingSlateNoSnap'; const pageSizeOptions = [5, 10, 50]; @@ -9,7 +8,7 @@ const serverOptions = { useCursorPagination: false }; const dataSetOptions = {}; export default function ServerSideDataGridNoCache() { - const { isInitialized, fetchRows, columns, initialState } = useMockServer( + const { fetchRows, columns, initialState } = useMockServer( dataSetOptions, serverOptions, ); @@ -48,18 +47,14 @@ export default function ServerSideDataGridNoCache() { return (
- {isInitialized ? ( - - ) : ( - - )} +
); } diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx index 8d7b06203cb0..ca8a9fe14814 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import { DataGridPro, GridDataSource } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; -import LoadingSlate from './LoadingSlateNoSnap'; const pageSizeOptions = [5, 10, 50]; @@ -9,7 +8,7 @@ const serverOptions = { useCursorPagination: false }; const dataSetOptions = {}; export default function ServerSideDataGridNoCache() { - const { isInitialized, fetchRows, columns, initialState } = useMockServer( + const { fetchRows, columns, initialState } = useMockServer( dataSetOptions, serverOptions, ); @@ -48,18 +47,14 @@ export default function ServerSideDataGridNoCache() { return (
- {isInitialized ? ( - - ) : ( - - )} +
); } diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx.preview index dc1ba74de974..ed2e75557b91 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx.preview +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx.preview @@ -1,12 +1,8 @@ -{isInitialized ? ( - -) : ( - -)} \ No newline at end of file + \ No newline at end of file diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.js b/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.js index b51f35890611..b81a60a0fe00 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.js @@ -1,12 +1,11 @@ import * as React from 'react'; import { DataGridPro, SimpleServerSideCache } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; -import LoadingSlate from './LoadingSlateNoSnap'; -const lowTTLCache = new SimpleServerSideCache({ ttl: 1000 * 60 * 1 }); // 1 minute +const lowTTLCache = new SimpleServerSideCache({ ttl: 1000 * 10 }); // 10 seconds function ServerSideDataGridTTL() { - const { isInitialized, columns, initialState, fetchRows } = useMockServer( + const { columns, initialState, fetchRows } = useMockServer( {}, { useCursorPagination: false }, ); @@ -46,18 +45,14 @@ function ServerSideDataGridTTL() { return (
- {isInitialized ? ( - - ) : ( - - )} +
); } diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.tsx index e21b76313179..0ccd22db4d74 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.tsx @@ -5,12 +5,11 @@ import { SimpleServerSideCache, } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; -import LoadingSlate from './LoadingSlateNoSnap'; -const lowTTLCache = new SimpleServerSideCache({ ttl: 1000 * 60 * 1 }); // 1 minute +const lowTTLCache = new SimpleServerSideCache({ ttl: 1000 * 10 }); // 10 seconds function ServerSideDataGridTTL() { - const { isInitialized, columns, initialState, fetchRows } = useMockServer( + const { columns, initialState, fetchRows } = useMockServer( {}, { useCursorPagination: false }, ); @@ -50,18 +49,14 @@ function ServerSideDataGridTTL() { return (
- {isInitialized ? ( - - ) : ( - - )} +
); } diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js index 8122d981383f..85db37d5f06e 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js @@ -2,13 +2,12 @@ import * as React from 'react'; import { DataGridPro } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; import { useSWRConfig } from 'swr'; -import LoadingSlate from './LoadingSlateNoSnap'; const serverOptions = { useCursorPagination: false }; const dataSetOptions = {}; function ServerSideDataGridWithSWR() { - const { isInitialized, fetchRows, columns, initialState } = useMockServer( + const { fetchRows, columns, initialState } = useMockServer( dataSetOptions, serverOptions, ); @@ -68,18 +67,14 @@ function ServerSideDataGridWithSWR() { return (
- {isInitialized ? ( - - ) : ( - - )} +
); } diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx index bc0d62aa11a2..8a324ca4d9b8 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx @@ -7,13 +7,12 @@ import { } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; import { useSWRConfig } from 'swr'; -import LoadingSlate from './LoadingSlateNoSnap'; const serverOptions = { useCursorPagination: false }; const dataSetOptions = {}; function ServerSideDataGridWithSWR() { - const { isInitialized, fetchRows, columns, initialState } = useMockServer( + const { fetchRows, columns, initialState } = useMockServer( dataSetOptions, serverOptions, ); @@ -73,18 +72,14 @@ function ServerSideDataGridWithSWR() { return (
- {isInitialized ? ( - - ) : ( - - )} +
); } diff --git a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js index 32d7b9115a1f..d104a8d2b1d6 100644 --- a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js +++ b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js @@ -5,7 +5,6 @@ import Checkbox from '@mui/material/Checkbox'; import FormControlLabel from '@mui/material/FormControlLabel'; import { alpha, styled, darken, lighten } from '@mui/material/styles'; import { useMockServer } from '@mui/x-data-grid-generator'; -import LoadingSlate from './LoadingSlateNoSnap'; const pageSizeOptions = [5, 10, 50]; const serverOptions = { useCursorPagination: false }; @@ -45,7 +44,7 @@ export default function ServerSideErrorHandling() { const [error, setError] = React.useState(); const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); - const { isInitialized, fetchRows, ...props } = useMockServer( + const { fetchRows, ...props } = useMockServer( datasetOptions, serverOptions, shouldRequestsFail, @@ -108,22 +107,17 @@ export default function ServerSideErrorHandling() { />
- {isInitialized ? ( - setError(e.message)} - unstable_dataSourceCache={null} - apiRef={apiRef} - pagination - pageSizeOptions={pageSizeOptions} - initialState={initialState} - slots={{ toolbar: GridToolbar }} - /> - ) : ( - - )} - + setError(e.message)} + unstable_dataSourceCache={null} + apiRef={apiRef} + pagination + pageSizeOptions={pageSizeOptions} + initialState={initialState} + slots={{ toolbar: GridToolbar }} + /> {error && }
diff --git a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx index fb44d54ab616..80a2dea89e16 100644 --- a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx @@ -11,7 +11,6 @@ import Checkbox from '@mui/material/Checkbox'; import FormControlLabel from '@mui/material/FormControlLabel'; import { alpha, styled, darken, lighten, Theme } from '@mui/material/styles'; import { useMockServer } from '@mui/x-data-grid-generator'; -import LoadingSlate from './LoadingSlateNoSnap'; const pageSizeOptions = [5, 10, 50]; const serverOptions = { useCursorPagination: false }; @@ -51,7 +50,7 @@ export default function ServerSideErrorHandling() { const [error, setError] = React.useState(); const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); - const { isInitialized, fetchRows, ...props } = useMockServer( + const { fetchRows, ...props } = useMockServer( datasetOptions, serverOptions, shouldRequestsFail, @@ -114,21 +113,17 @@ export default function ServerSideErrorHandling() { />
- {isInitialized ? ( - setError(e.message)} - unstable_dataSourceCache={null} - apiRef={apiRef} - pagination - pageSizeOptions={pageSizeOptions} - initialState={initialState} - slots={{ toolbar: GridToolbar }} - /> - ) : ( - - )} + setError(e.message)} + unstable_dataSourceCache={null} + apiRef={apiRef} + pagination + pageSizeOptions={pageSizeOptions} + initialState={initialState} + slots={{ toolbar: GridToolbar }} + /> {error && }
diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.js b/docs/data/data-grid/server-side-data/ServerSideTreeData.js index 57458f9fcef0..53065ee4501e 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.js @@ -2,7 +2,6 @@ import * as React from 'react'; import { DataGridPro, useGridApiRef, GridToolbar } from '@mui/x-data-grid-pro'; import Button from '@mui/material/Button'; import { useMockServer } from '@mui/x-data-grid-generator'; -import LoadingSlate from './LoadingSlateNoSnap'; const pageSizeOptions = [5, 10, 50]; const dataSetOptions = { @@ -14,8 +13,7 @@ const dataSetOptions = { export default function ServerSideTreeData() { const apiRef = useGridApiRef(); - const { isInitialized, fetchRows, columns, initialState } = - useMockServer(dataSetOptions); + const { fetchRows, columns, initialState } = useMockServer(dataSetOptions); const initialStateWithPagination = React.useMemo( () => ({ @@ -59,21 +57,17 @@ export default function ServerSideTreeData() {
- {isInitialized ? ( - - ) : ( - - )} +
); diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx index a42512d4a549..37279f1e4cdd 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx @@ -8,7 +8,6 @@ import { } from '@mui/x-data-grid-pro'; import Button from '@mui/material/Button'; import { useMockServer } from '@mui/x-data-grid-generator'; -import LoadingSlate from './LoadingSlateNoSnap'; const pageSizeOptions = [5, 10, 50]; const dataSetOptions = { @@ -20,8 +19,7 @@ const dataSetOptions = { export default function ServerSideTreeData() { const apiRef = useGridApiRef(); - const { isInitialized, fetchRows, columns, initialState } = - useMockServer(dataSetOptions); + const { fetchRows, columns, initialState } = useMockServer(dataSetOptions); const initialStateWithPagination: GridInitialState = React.useMemo( () => ({ @@ -65,21 +63,17 @@ export default function ServerSideTreeData() {
- {isInitialized ? ( - - ) : ( - - )} +
); diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview new file mode 100644 index 000000000000..e077c278878c --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview @@ -0,0 +1,14 @@ + +
+ +
\ No newline at end of file diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js index c91e0d5e4b80..6d5bf26fca2e 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js @@ -3,7 +3,6 @@ import { DataGridPro, useGridApiRef, GridToolbar } from '@mui/x-data-grid-pro'; import Button from '@mui/material/Button'; import { useMockServer } from '@mui/x-data-grid-generator'; import { QueryClient } from '@tanstack/query-core'; -import LoadingSlate from './LoadingSlateNoSnap'; const queryClient = new QueryClient({ defaultOptions: { @@ -43,7 +42,7 @@ const dataSetOptions = { export default function ServerSideTreeDataCustomCache() { const apiRef = useGridApiRef(); - const { isInitialized, fetchRows, ...props } = useMockServer(dataSetOptions); + const { fetchRows, ...props } = useMockServer(dataSetOptions); const dataSource = React.useMemo( () => ({ @@ -87,22 +86,18 @@ export default function ServerSideTreeDataCustomCache() {
- {isInitialized ? ( - - ) : ( - - )} +
); diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx index 8ecb2009909d..8fc71af170be 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx @@ -10,7 +10,6 @@ import { import Button from '@mui/material/Button'; import { useMockServer } from '@mui/x-data-grid-generator'; import { QueryClient } from '@tanstack/query-core'; -import LoadingSlate from './LoadingSlateNoSnap'; const queryClient = new QueryClient({ defaultOptions: { @@ -50,7 +49,7 @@ const dataSetOptions = { export default function ServerSideTreeDataCustomCache() { const apiRef = useGridApiRef(); - const { isInitialized, fetchRows, ...props } = useMockServer(dataSetOptions); + const { fetchRows, ...props } = useMockServer(dataSetOptions); const dataSource: GridDataSource = React.useMemo( () => ({ @@ -94,22 +93,18 @@ export default function ServerSideTreeDataCustomCache() {
- {isInitialized ? ( - - ) : ( - - )} +
); diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx.preview new file mode 100644 index 000000000000..1c8c80d0e4b6 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx.preview @@ -0,0 +1,15 @@ + +
+ +
\ No newline at end of file diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js index 7039f821d14d..a83c20ee0528 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js @@ -6,7 +6,6 @@ import Checkbox from '@mui/material/Checkbox'; import FormControlLabel from '@mui/material/FormControlLabel'; import { alpha, styled, darken, lighten } from '@mui/material/styles'; import { useMockServer } from '@mui/x-data-grid-generator'; -import LoadingSlate from './LoadingSlateNoSnap'; const pageSizeOptions = [5, 10, 50]; const serverOptions = { useCursorPagination: false }; @@ -22,7 +21,7 @@ export default function ServerSideTreeDataErrorHandling() { const [childrenError, setChildrenError] = React.useState(); const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); - const { isInitialized, fetchRows, ...props } = useMockServer( + const { fetchRows, ...props } = useMockServer( dataSetOptions, serverOptions, shouldRequestsFail, @@ -88,38 +87,32 @@ export default function ServerSideTreeDataErrorHandling() { />
- {isInitialized ? ( - - { - if (!params.groupKeys || params.groupKeys.length === 0) { - setRootError(e.message); - } else { - setChildrenError( - `${e.message} (Requested level: ${params.groupKeys.join(', ')})`, - ); - } - }} - unstable_dataSourceCache={null} - apiRef={apiRef} - pagination - pageSizeOptions={pageSizeOptions} - initialState={initialState} - /> - {rootError && } - setChildrenError('')} - message={childrenError} - /> - - ) : ( - - )} + { + if (!params.groupKeys || params.groupKeys.length === 0) { + setRootError(e.message); + } else { + setChildrenError( + `${e.message} (Requested level: ${params.groupKeys.join(', ')})`, + ); + } + }} + unstable_dataSourceCache={null} + apiRef={apiRef} + pagination + pageSizeOptions={pageSizeOptions} + initialState={initialState} + /> + {rootError && } + setChildrenError('')} + message={childrenError} + />
); diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx index eb155464a782..f90bca5a6bc4 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx @@ -11,7 +11,6 @@ import Checkbox from '@mui/material/Checkbox'; import FormControlLabel from '@mui/material/FormControlLabel'; import { alpha, styled, darken, lighten, Theme } from '@mui/material/styles'; import { useMockServer } from '@mui/x-data-grid-generator'; -import LoadingSlate from './LoadingSlateNoSnap'; const pageSizeOptions = [5, 10, 50]; const serverOptions = { useCursorPagination: false }; @@ -27,7 +26,7 @@ export default function ServerSideTreeDataErrorHandling() { const [childrenError, setChildrenError] = React.useState(); const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); - const { isInitialized, fetchRows, ...props } = useMockServer( + const { fetchRows, ...props } = useMockServer( dataSetOptions, serverOptions, shouldRequestsFail, @@ -93,38 +92,32 @@ export default function ServerSideTreeDataErrorHandling() { />
- {isInitialized ? ( - - { - if (!params.groupKeys || params.groupKeys.length === 0) { - setRootError(e.message); - } else { - setChildrenError( - `${e.message} (Requested level: ${params.groupKeys.join(', ')})`, - ); - } - }} - unstable_dataSourceCache={null} - apiRef={apiRef} - pagination - pageSizeOptions={pageSizeOptions} - initialState={initialState} - /> - {rootError && } - setChildrenError('')} - message={childrenError} - /> - - ) : ( - - )} + { + if (!params.groupKeys || params.groupKeys.length === 0) { + setRootError(e.message); + } else { + setChildrenError( + `${e.message} (Requested level: ${params.groupKeys.join(', ')})`, + ); + } + }} + unstable_dataSourceCache={null} + apiRef={apiRef} + pagination + pageSizeOptions={pageSizeOptions} + initialState={initialState} + /> + {rootError && } + setChildrenError('')} + message={childrenError} + />
); diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index aadecc9cb82b..519ec2af6dc2 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -179,14 +179,14 @@ The `SimpleServerSideCache` is the cache used by default which is a simple in-me {{"demo": "ServerSideDataGrid.js", "bg": "inline"}} -### Customize the cache ttl +### Customize the cache lifetime -The `SimpleServerSideCache` has a default `ttl` of 5 minutes. You can customize this by passing the `ttl` option in milliseconds to the `SimpleServerSideCache` constructor, and then passing it to the `unstable_dataSourceCache` prop. +The `SimpleServerSideCache` has a default Time To Live (`ttl`) of 5 minutes. You can customize this by passing the `ttl` option in milliseconds to the `SimpleServerSideCache` constructor, and then passing it to the `unstable_dataSourceCache` prop. ```tsx import { SimpleServerSideCache } from '@mui/x-data-grid-pro'; -const lowTTLCache = new SimpleServerSideCache({ ttl: 1000 * 60 * 1 }); // 1 minute +const lowTTLCache = new SimpleServerSideCache({ ttl: 1000 * 10 }); // 10 seconds string; getChildrenCount?: (row: GridRowModel) => number; fetchRows: (url: string) => Promise; - isInitialized: boolean; loadNewData: () => void; }; @@ -82,7 +81,6 @@ export const useMockServer = ( serverOptions?: ServerOptions & { verbose?: boolean }, shouldRequestsFail?: boolean, ): UseMockServerResponse => { - const [isInitialized, setIsInitialized] = React.useState(false); const [data, setData] = React.useState(); const [index, setIndex] = React.useState(0); const shouldRequestsFailRef = React.useRef(shouldRequestsFail ?? false); @@ -267,19 +265,12 @@ export const useMockServer = ( ], ); - React.useEffect(() => { - if (data && !isInitialized) { - setIsInitialized(true); - } - }, [data, isInitialized]); - return { columns: columnsWithDefaultColDef, initialState, getGroupKey, getChildrenCount, fetchRows, - isInitialized, loadNewData: () => { setIndex((oldIndex) => oldIndex + 1); }, diff --git a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts index 2a7c934a6007..0d50f1b0a819 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts +++ b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts @@ -34,6 +34,7 @@ const getDataGridPremiumForcedProps: GetDataGridProForcedProps = (themedProps) = } : {}), }); + /** * The default values of `DataGridPremiumPropsWithDefaultValue` to inject in the props of DataGridPremium. */ @@ -55,15 +56,6 @@ export const DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES: DataGridPremiumPropsWithDef }, }; -const getDataGridPremiumDefaultProps: ( - themedProps: GetDataGridPremiumPropsDefaultValues, -) => DataGridPremiumPropsWithDefaultValue = (themedProps) => ({ - ...DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES, - filterDebounceMs: themedProps.unstable_dataSource - ? 1000 - : DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES.filterDebounceMs, -}); - const defaultSlots = DATA_GRID_PREMIUM_DEFAULT_SLOTS_COMPONENTS; export const useDataGridPremiumProps = (inProps: DataGridPremiumProps) => { @@ -91,7 +83,7 @@ export const useDataGridPremiumProps = (inProps: DataGridPremiumProps) => { return React.useMemo( () => ({ - ...getDataGridPremiumDefaultProps(themedProps), + ...DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES, ...themedProps, localeText, slots, diff --git a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts index b0d8257cf872..b970dfdd4645 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts +++ b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts @@ -52,15 +52,6 @@ export const DATA_GRID_PRO_PROPS_DEFAULT_VALUES: DataGridProPropsWithDefaultValu headerFilters: false, }; -const getDataGridProDefaultProps: ( - themedProps: GetDataGridProPropsDefaultValues, -) => DataGridProPropsWithDefaultValue = (themedProps) => ({ - ...DATA_GRID_PRO_PROPS_DEFAULT_VALUES, - filterDebounceMs: themedProps.unstable_dataSource - ? 1000 - : DATA_GRID_PROPS_DEFAULT_VALUES.filterDebounceMs, -}); - const defaultSlots = DATA_GRID_PRO_DEFAULT_SLOTS_COMPONENTS; export const useDataGridProProps = (inProps: DataGridProProps) => { @@ -88,7 +79,7 @@ export const useDataGridProProps = (inProps: DataGr return React.useMemo>( () => ({ - ...getDataGridProDefaultProps(themedProps), + ...DATA_GRID_PRO_PROPS_DEFAULT_VALUES, ...themedProps, localeText, slots, 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 8b586286dd74..789a688a977c 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 @@ -2,8 +2,9 @@ import { GridGetRowsParams, GridGetRowsResponse } from '../../../models'; type SimpleServerSideCacheConfig = { /** - * The ttl for each cache entry in milliseconds. - * @default 300000 (Five minutes) + * 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; }; From 6a5825979887126e514318e7fa33cd14b2baabd2 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Thu, 6 Jun 2024 20:49:36 +0500 Subject: [PATCH 60/90] Update filter condition --- packages/x-data-grid-generator/src/hooks/serverUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-data-grid-generator/src/hooks/serverUtils.ts b/packages/x-data-grid-generator/src/hooks/serverUtils.ts index b6a918a3b569..0caf1005e825 100644 --- a/packages/x-data-grid-generator/src/hooks/serverUtils.ts +++ b/packages/x-data-grid-generator/src/hooks/serverUtils.ts @@ -367,7 +367,7 @@ const getTreeDataFilteredRows: GetTreeDataFilteredRows = ( if (filterModel && filterModel.quickFilterValues?.length! > 0) { filteredRows = getQuicklyFilteredRows(rows, filterModel, columnsWithDefaultColDef); } - if (filterModel?.items.length === 0) { + if ((filterModel?.items.length ?? 0) > 0) { filteredRows = getFilteredRows(filteredRows, filterModel, columnsWithDefaultColDef); } From 39d241dc25fd4f064b2734240a21c25b596fbd3d Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Fri, 7 Jun 2024 13:57:36 +0500 Subject: [PATCH 61/90] Fix valueParser in serverUtils --- .../src/hooks/serverUtils.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/x-data-grid-generator/src/hooks/serverUtils.ts b/packages/x-data-grid-generator/src/hooks/serverUtils.ts index 0caf1005e825..2a901aa3aa1a 100644 --- a/packages/x-data-grid-generator/src/hooks/serverUtils.ts +++ b/packages/x-data-grid-generator/src/hooks/serverUtils.ts @@ -238,15 +238,14 @@ const getFilteredRows = ( ({ value }: GridFilterOperator) => operator === value, ); - const parsedValue = filterItem.value; - - // TODO: Fix value parser impl. - // if (colDef.valueParser) { - // const parser = colDef.valueParser; - // parsedValue = Array.isArray(filterItem.value) - // ? filterItem.value?.map((x) => parser(x)) - // : parser(filterItem.value); - // } + let parsedValue = filterItem.value; + + if (colDef.valueParser) { + const parser = colDef.valueParser; + parsedValue = Array.isArray(filterItem.value) + ? filterItem.value?.map((x) => parser(x, {}, colDef, apiRef)) + : parser(filterItem.value, {}, colDef, apiRef); + } return filterOperator.getApplyFilterFn({ filterItem, value: parsedValue }, colDef); }); From e3f77b88267bb0606c4340a00dcb1c2cd3e2155b Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Fri, 7 Jun 2024 15:09:12 +0500 Subject: [PATCH 62/90] Resolve a few comments --- .../server-side-data/ServerSideDataGridTTL.tsx | 4 ++-- .../server-side-data/ServerSideTreeData.tsx | 2 +- .../ServerSideTreeDataErrorHandling.tsx | 2 +- docs/data/data-grid/server-side-data/index.md | 18 ++++++++++-------- .../data-grid/server-side-data/tree-data.md | 9 +++++---- docs/package.json | 5 ----- .../x-data-grid-generator/src/hooks/index.ts | 2 +- .../src/hooks/features/dataSource/cache.ts | 6 +++--- .../dataSource/useGridDataSourceCache.ts | 6 +++--- packages/x-data-grid-pro/tsconfig.json | 5 ----- scripts/x-data-grid-generator.exports.json | 1 + scripts/x-data-grid-premium.exports.json | 2 +- scripts/x-data-grid-pro.exports.json | 2 +- 13 files changed, 29 insertions(+), 35 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.tsx index 0ccd22db4d74..1f0012917676 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.tsx @@ -2,11 +2,11 @@ import * as React from 'react'; import { DataGridPro, GridDataSource, - SimpleServerSideCache, + GridDataSourceDefaultCache, } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; -const lowTTLCache = new SimpleServerSideCache({ ttl: 1000 * 10 }); // 10 seconds +const lowTTLCache = new GridDataSourceDefaultCache({ ttl: 1000 * 10 }); // 10 seconds function ServerSideDataGridTTL() { const { columns, initialState, fetchRows } = useMockServer( diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx index 37279f1e4cdd..a13a976b2b0a 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx @@ -11,7 +11,7 @@ import { useMockServer } from '@mui/x-data-grid-generator'; const pageSizeOptions = [5, 10, 50]; const dataSetOptions = { - dataSet: 'Employee' as 'Employee', + dataSet: 'Employee' as const, rowLength: 1000, treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, }; diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx index f90bca5a6bc4..ba8341bf38e7 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx @@ -101,7 +101,7 @@ export default function ServerSideTreeDataErrorHandling() { setRootError(e.message); } else { setChildrenError( - `${e.message} (Requested level: ${params.groupKeys.join(', ')})`, + `${e.message} (Requested level: ${params.groupKeys.join(' > ')})`, ); } }} diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index 519ec2af6dc2..a54152bdf83f 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -10,7 +10,7 @@ title: React Data Grid - Server-side data Server-side data management in React can become complex with growing datasets. Challenges include manual data fetching, pagination, sorting, filtering, and performance optimization. A dedicated module can help abstract these complexities, improving user experience. -Consider a data grid displaying a list of users. It supports pagination, sorting by column headers, and filtering. The grid fetches data from the server when the user changes the page or updates filtering or sorting. +Consider a Data Grid displaying a list of users. It supports pagination, sorting by column headers, and filtering. The Data Grid fetches data from the server when the user changes the page or updates filtering or sorting. ```tsx const [rows, setRows] = React.useState([]); @@ -64,7 +64,9 @@ Trying to solve these problems one after the other can make the code complex and ## Data source -The idea for a centralized data source is to simplify server-side data fetching. It's an abstraction layer between the data grid and the server, providing a simple interface for interacting with server. Think of it like a middle-man handling the communication between the Data Grid (client) and the actual data source (server). +The idea for a centralized data source is to simplify server-side data fetching. +It's an abstraction layer between the Data Grid and the server, providing a simple interface for interacting with the server. +Think of it like a middleman handling the communication between the Data Grid (client) and the actual data source (server). :::warning @@ -173,20 +175,20 @@ Open info section of the browser console to see the requests being made and the ## Data caching -The Data source supports caching the data it receives from the server by default. This means that if the user navigates to a page or expands a node that has already been fetched, the grid will not call the `getRows` function again to avoid unnecessary calls to the server. +The data source caches fetched data by default. This means that if the user navigates to a page or expands a node that has already been fetched, the grid will not call the `getRows` function again to avoid unnecessary calls to the server. -The `SimpleServerSideCache` is the cache used by default which is a simple in-memory cache that stores the data in a plain object. It could be seen in action in the demo below. +The `GridDataSourceDefaultCache` is used by default which is a simple in-memory cache that stores the data in a plain object. It could be seen in action in the demo below. {{"demo": "ServerSideDataGrid.js", "bg": "inline"}} ### Customize the cache lifetime -The `SimpleServerSideCache` has a default Time To Live (`ttl`) of 5 minutes. You can customize this by passing the `ttl` option in milliseconds to the `SimpleServerSideCache` constructor, and then passing it to the `unstable_dataSourceCache` prop. +The `GridDataSourceDefaultCache` has a default Time To Live (`ttl`) of 5 minutes. You can customize this by passing the `ttl` option in milliseconds to the `GridDataSourceDefaultCache` constructor, and then passing it to the `unstable_dataSourceCache` prop. ```tsx -import { SimpleServerSideCache } from '@mui/x-data-grid-pro'; +import { GridDataSourceDefaultCache } from '@mui/x-data-grid-pro'; -const lowTTLCache = new SimpleServerSideCache({ ttl: 1000 * 10 }); // 10 seconds +const lowTTLCache = new GridDataSourceDefaultCache({ ttl: 1000 * 10 }); // 10 seconds ; private ttl: number; - constructor({ ttl = 300000 }: SimpleServerSideCacheConfig) { + constructor({ ttl = 300000 }: GridDataSourceDefaultCacheConfig) { this.cache = {}; this.ttl = ttl; } diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts index a0d37202842d..19c3346d5168 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts @@ -5,9 +5,9 @@ import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; import { GridGetRowsParams, GridGetRowsResponse, GridDataSourceCache } from '../../../models'; import { GridDataSourceCacheApi } from './interfaces'; -import { SimpleServerSideCache } from './cache'; +import { GridDataSourceDefaultCache } from './cache'; -const getDefaultCache = (cacheInstance: SimpleServerSideCache): GridDataSourceCache => ({ +const getDefaultCache = (cacheInstance: GridDataSourceDefaultCache): GridDataSourceCache => ({ getKey: cacheInstance.getKey, set: (key: string, value: GridGetRowsResponse) => cacheInstance.set(key as string, value as GridGetRowsResponse), @@ -20,7 +20,7 @@ export const useGridDataSourceCache = ( props: Pick, ): void => { const defaultCache = useLazyRef(() => - getDefaultCache(new SimpleServerSideCache({})), + getDefaultCache(new GridDataSourceDefaultCache({})), ); const cache = React.useRef( props.unstable_dataSourceCache || defaultCache.current, diff --git a/packages/x-data-grid-pro/tsconfig.json b/packages/x-data-grid-pro/tsconfig.json index 4915ae435f4e..aba8438e2441 100644 --- a/packages/x-data-grid-pro/tsconfig.json +++ b/packages/x-data-grid-pro/tsconfig.json @@ -1,11 +1,6 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "jsx": "react", - "jsx": "react", - "jsx": "react", - "jsx": "react", - "jsx": "react", "types": [ "@mui/internal-test-utils/initMatchers", "@mui/material/themeCssVarsAugmentation", diff --git a/scripts/x-data-grid-generator.exports.json b/scripts/x-data-grid-generator.exports.json index c2fc12a036aa..06a34db6fa23 100644 --- a/scripts/x-data-grid-generator.exports.json +++ b/scripts/x-data-grid-generator.exports.json @@ -21,6 +21,7 @@ { "name": "GridDemoData", "kind": "Interface" }, { "name": "loadServerRows", "kind": "Variable" }, { "name": "Movie", "kind": "TypeAlias" }, + { "name": "QueryOptions", "kind": "Interface" }, { "name": "random", "kind": "Variable" }, { "name": "randomAddress", "kind": "Variable" }, { "name": "randomArrayItem", "kind": "Variable" }, diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 17473ded010e..1ed2e58730bf 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -248,6 +248,7 @@ { "name": "GridDataSourceApi", "kind": "Interface" }, { "name": "GridDataSourceCache", "kind": "Interface" }, { "name": "GridDataSourceCacheApi", "kind": "Interface" }, + { "name": "GridDataSourceDefaultCache", "kind": "Class" }, { "name": "GridDataSourceInternalCache", "kind": "Interface" }, { "name": "GridDataSourcePrivateApi", "kind": "Interface" }, { "name": "GridDataSourceState", "kind": "Interface" }, @@ -645,7 +646,6 @@ { "name": "selectedGridRowsSelector", "kind": "Variable" }, { "name": "selectedIdsLookupSelector", "kind": "Variable" }, { "name": "setupExcelExportWebWorker", "kind": "Function" }, - { "name": "SimpleServerSideCache", "kind": "Class" }, { "name": "SkeletonCellPropsOverrides", "kind": "Interface" }, { "name": "ToolbarPropsOverrides", "kind": "Interface" }, { "name": "unstable_resetCleanupTracking", "kind": "Variable" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index 035fe2a8ebe3..5053555b54fc 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -222,6 +222,7 @@ { "name": "GridDataSourceApi", "kind": "Interface" }, { "name": "GridDataSourceCache", "kind": "Interface" }, { "name": "GridDataSourceCacheApi", "kind": "Interface" }, + { "name": "GridDataSourceDefaultCache", "kind": "Class" }, { "name": "GridDataSourceInternalCache", "kind": "Interface" }, { "name": "GridDataSourcePrivateApi", "kind": "Interface" }, { "name": "GridDataSourceState", "kind": "Interface" }, @@ -596,7 +597,6 @@ { "name": "selectedGridRowsCountSelector", "kind": "Variable" }, { "name": "selectedGridRowsSelector", "kind": "Variable" }, { "name": "selectedIdsLookupSelector", "kind": "Variable" }, - { "name": "SimpleServerSideCache", "kind": "Class" }, { "name": "SkeletonCellPropsOverrides", "kind": "Interface" }, { "name": "ToolbarPropsOverrides", "kind": "Interface" }, { "name": "unstable_resetCleanupTracking", "kind": "Variable" }, From c5e0cb36b972a2e2a750e331e9bfbf72a41bbc5f Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Fri, 7 Jun 2024 17:12:15 +0500 Subject: [PATCH 63/90] docs:typescript:formatted --- docs/data/data-grid/server-side-data/ServerSideDataGridTTL.js | 4 ++-- .../server-side-data/ServerSideTreeDataErrorHandling.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.js b/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.js index b81a60a0fe00..1d17e6f5de08 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.js @@ -1,8 +1,8 @@ import * as React from 'react'; -import { DataGridPro, SimpleServerSideCache } from '@mui/x-data-grid-pro'; +import { DataGridPro, GridDataSourceDefaultCache } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; -const lowTTLCache = new SimpleServerSideCache({ ttl: 1000 * 10 }); // 10 seconds +const lowTTLCache = new GridDataSourceDefaultCache({ ttl: 1000 * 10 }); // 10 seconds function ServerSideDataGridTTL() { const { columns, initialState, fetchRows } = useMockServer( diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js index a83c20ee0528..dd477861a145 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js @@ -96,7 +96,7 @@ export default function ServerSideTreeDataErrorHandling() { setRootError(e.message); } else { setChildrenError( - `${e.message} (Requested level: ${params.groupKeys.join(', ')})`, + `${e.message} (Requested level: ${params.groupKeys.join(' > ')})`, ); } }} From 4997e3fda1beea5d8498f98f0c8929db8b6d2e63 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 10 Jun 2024 10:09:39 +0500 Subject: [PATCH 64/90] Refactor cache API --- .../ServerSideDataGridWithSWR.js | 7 +-- .../ServerSideDataGridWithSWR.tsx | 11 ++-- .../server-side-data/ServerSideTreeData.js | 4 +- .../server-side-data/ServerSideTreeData.tsx | 4 +- .../ServerSideTreeData.tsx.preview | 4 +- .../ServerSideTreeDataCustomCache.js | 23 +++++---- .../ServerSideTreeDataCustomCache.tsx | 28 ++++++----- .../ServerSideTreeDataGroupExpansion.js | 4 +- .../ServerSideTreeDataGroupExpansion.tsx | 4 +- ...rverSideTreeDataGroupExpansion.tsx.preview | 15 ------ docs/data/data-grid/server-side-data/index.md | 10 ++-- docs/pages/x/api/data-grid/grid-api.json | 24 ++------- .../api-docs/data-grid/grid-api.json | 4 +- .../x-data-grid-generator/src/hooks/index.ts | 3 +- .../src/DataGridPremium/DataGridPremium.tsx | 1 - .../src/DataGridPro/DataGridPro.tsx | 1 - .../src/hooks/features/dataSource/cache.ts | 31 ++++++------ .../hooks/features/dataSource/interfaces.ts | 18 ++----- .../features/dataSource/useGridDataSource.ts | 10 ++-- .../dataSource/useGridDataSourceCache.ts | 50 ++++--------------- .../x-data-grid/src/models/gridDataSource.ts | 14 ++---- 21 files changed, 104 insertions(+), 166 deletions(-) delete mode 100644 docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx.preview diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js index 85db37d5f06e..6cee45d75169 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js @@ -38,12 +38,13 @@ function ServerSideDataGridWithSWR() { const cache = React.useMemo( () => ({ - getKey: (params) => JSON.stringify(params), set: (key, value) => { - swrCache.set(key, { data: value }); + const keyString = JSON.stringify(key); + swrCache.set(keyString, { data: value }); }, get: (key) => { - return swrCache.get(key)?.data; + const keyString = JSON.stringify(key); + return swrCache.get(keyString)?.data; }, clear: () => { const keys = swrCache.keys(); diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx index 8a324ca4d9b8..7e6a58b130f6 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx @@ -43,12 +43,13 @@ function ServerSideDataGridWithSWR() { const cache = React.useMemo( () => ({ - getKey: (params: GridGetRowsParams) => JSON.stringify(params), - set: (key: unknown, value: GridGetRowsResponse) => { - swrCache.set(key as string, { data: value }); + set: (key: GridGetRowsParams, value: GridGetRowsResponse) => { + const keyString = JSON.stringify(key); + swrCache.set(keyString, { data: value }); }, - get: (key: unknown) => { - return swrCache.get(key as string)?.data; + get: (key: GridGetRowsParams) => { + const keyString = JSON.stringify(key); + return swrCache.get(keyString)?.data; }, clear: () => { const keys = swrCache.keys(); diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.js b/docs/data/data-grid/server-side-data/ServerSideTreeData.js index 53065ee4501e..8fb80e43a297 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.js @@ -55,7 +55,9 @@ export default function ServerSideTreeData() { return (
- +
- +
apiRef.current?.clearCache()}>Reset cache +
{ - queryClient.setQueryData(key, value); + const queryKey = getKey(key); + queryClient.setQueryData(queryKey, value); }, get: (key) => { - return queryClient.getQueryData(key); + const queryKey = getKey(key); + return queryClient.getQueryData(queryKey); }, clear: () => { queryClient.clear(); }, - getKey: (params) => { - return [ - params.paginationModel, - params.sortModel, - params.filterModel, - params.groupKeys, - ]; - }, }; const pageSizeOptions = [5, 10, 50]; diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx index 8fc71af170be..a3f2961d549f 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx @@ -6,6 +6,7 @@ import { GridToolbar, GridDataSourceCache, GridDataSource, + GridGetRowsParams, } from '@mui/x-data-grid-pro'; import Button from '@mui/material/Button'; import { useMockServer } from '@mui/x-data-grid-generator'; @@ -19,24 +20,27 @@ const queryClient = new QueryClient({ }, }); +function getKey(params: GridGetRowsParams) { + return [ + params.paginationModel, + params.sortModel, + params.filterModel, + params.groupKeys, + ]; +} + const cache: GridDataSourceCache = { - set: (key: any[], value) => { - queryClient.setQueryData(key, value); + set: (key: GridGetRowsParams, value) => { + const queryKey = getKey(key); + queryClient.setQueryData(queryKey, value); }, - get: (key: any[]) => { - return queryClient.getQueryData(key); + get: (key: GridGetRowsParams) => { + const queryKey = getKey(key); + return queryClient.getQueryData(queryKey); }, clear: () => { queryClient.clear(); }, - getKey: (params) => { - return [ - params.paginationModel, - params.sortModel, - params.filterModel, - params.groupKeys, - ]; - }, }; const pageSizeOptions = [5, 10, 50]; diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js index 9e7703d58dd5..877a4d8a22ae 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js @@ -55,7 +55,9 @@ export default function ServerSideTreeDataGroupExpansion() { return (
- +
- +
apiRef.current.clearCache()}>Reset cache -
- -
\ No newline at end of file diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index a54152bdf83f..6cbb7832cd83 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -183,7 +183,7 @@ The `GridDataSourceDefaultCache` is used by default which is a simple in-memory ### Customize the cache lifetime -The `GridDataSourceDefaultCache` has a default Time To Live (`ttl`) of 5 minutes. You can customize this by passing the `ttl` option in milliseconds to the `GridDataSourceDefaultCache` constructor, and then passing it to the `unstable_dataSourceCache` prop. +The `GridDataSourceDefaultCache` has a default Time To Live (`ttl`) of 5 minutes. To customize it, pass the `ttl` option in milliseconds to the `GridDataSourceDefaultCache` constructor, and then pass it as the `unstable_dataSourceCache` prop. ```tsx import { GridDataSourceDefaultCache } from '@mui/x-data-grid-pro'; @@ -205,10 +205,10 @@ To provide a custom cache, use `unstable_dataSourceCache` prop, which could be e ```tsx export interface GridDataSourceCache { - getKey: (params: GridGetRowsParams) => any; - set: (key: any, value: GridGetRowsResponse) => void; - get: (key: any) => GridGetRowsResponse | undefined; + set: (key: GridGetRowsParams, value: GridGetRowsResponse) => void; + get: (key: GridGetRowsParams) => GridGetRowsResponse | undefined; clear: () => void; +} ``` The following demo uses cache used by a popular library [`swr`](https://github.com/vercel/swr) to cache the server-side data. @@ -249,7 +249,7 @@ The first argument of this function is the error object, and the second argument ## Updating data 🚧 -This feature is yet to be implemented, when completed, the method `dataSource.updateRow` will be called with the `GridRowModel` whenever the user edits a row. It will work in a similar way as the `processRowUpdate` prop. +This feature is yet to be implemented, when completed, the method `unstable_dataSource.updateRow` will be called with the `GridRowModel` whenever the user edits a row. It will work in a similar way as the `processRowUpdate` prop. Feel free to upvote the related GitHub [issue](https://github.com/mui/mui-x/issues/13261) to see this feature land faster. diff --git a/docs/pages/x/api/data-grid/grid-api.json b/docs/pages/x/api/data-grid/grid-api.json index dc73eff38a0d..d9f835c42315 100644 --- a/docs/pages/x/api/data-grid/grid-api.json +++ b/docs/pages/x/api/data-grid/grid-api.json @@ -19,11 +19,6 @@ "type": { "description": "(options?: GridAutosizeOptions) => Promise<void>" }, "required": true }, - "clearCache": { - "type": { "description": "() => void" }, - "required": true, - "isProPlan": true - }, "deleteFilterItem": { "type": { "description": "(item: GridFilterItem) => void" }, "required": true @@ -57,13 +52,6 @@ "required": true }, "getAllRowIds": { "type": { "description": "() => GridRowId[]" }, "required": true }, - "getCacheData": { - "type": { - "description": "(params: GridGetRowsParams) => GridGetRowsResponse | undefined" - }, - "required": true, - "isProPlan": true - }, "getCellElement": { "type": { "description": "(id: GridRowId, field: string) => HTMLDivElement | null" }, "required": true @@ -307,13 +295,6 @@ "required": true, "isPremiumPlan": true }, - "setCacheData": { - "type": { - "description": "(params: GridGetRowsParams, data: GridGetRowsResponse) => void" - }, - "required": true, - "isProPlan": true - }, "setCellFocus": { "type": { "description": "(id: GridRowId, field: string) => void" }, "required": true @@ -500,6 +481,11 @@ "required": true, "isProPlan": true }, + "unstable_dataSourceCache": { + "type": { "description": "GridDataSourceCache | null" }, + "required": true, + "isProPlan": true + }, "unstable_replaceRows": { "type": { "description": "(firstRowToReplace: number, newRows: GridRowModel[]) => void" }, "required": true diff --git a/docs/translations/api-docs/data-grid/grid-api.json b/docs/translations/api-docs/data-grid/grid-api.json index 2767dcf7cfee..91bf34b8b435 100644 --- a/docs/translations/api-docs/data-grid/grid-api.json +++ b/docs/translations/api-docs/data-grid/grid-api.json @@ -6,7 +6,6 @@ "autosizeColumns": { "description": "Auto-size the columns of the grid based on the cells' content and the space available." }, - "clearCache": { "description": "Clear the cache" }, "deleteFilterItem": { "description": "Deletes a GridFilterItem." }, @@ -27,7 +26,6 @@ }, "getAllGroupDetails": { "description": "Returns the column group lookup." }, "getAllRowIds": { "description": "Gets the list of row ids." }, - "getCacheData": { "description": "Get data from the cache" }, "getCellElement": { "description": "Gets the underlying DOM element for a cell at the given id and field." }, @@ -149,7 +147,6 @@ "setAggregationModel": { "description": "Sets the aggregation model to the one given by model." }, - "setCacheData": { "description": "Set data in the cache" }, "setCellFocus": { "description": "Sets the focus to the cell at the given id and field." }, @@ -250,6 +247,7 @@ }, "toggleDetailPanel": { "description": "Expands or collapses the detail panel of a row." }, "unpinColumn": { "description": "Unpins a column." }, + "unstable_dataSourceCache": { "description": "Data source cache object." }, "unstable_replaceRows": { "description": "Replace a set of rows with new rows." }, "unstable_setColumnVirtualization": { "description": "Enable/disable column virtualization." }, "unstable_setPinnedRows": { "description": "Changes the pinned rows." }, diff --git a/packages/x-data-grid-generator/src/hooks/index.ts b/packages/x-data-grid-generator/src/hooks/index.ts index 56092e1024b5..84dd7368aea4 100644 --- a/packages/x-data-grid-generator/src/hooks/index.ts +++ b/packages/x-data-grid-generator/src/hooks/index.ts @@ -3,4 +3,5 @@ export * from './useBasicDemoData'; export * from './useMovieData'; export * from './useQuery'; export * from './useMockServer'; -export { loadServerRows, QueryOptions } from './serverUtils'; +export { loadServerRows } from './serverUtils'; +export type { QueryOptions } from './serverUtils'; diff --git a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx index c3c56ea06916..2cba34da09e7 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx @@ -1053,7 +1053,6 @@ DataGridPremiumRaw.propTypes = { unstable_dataSourceCache: PropTypes.shape({ clear: PropTypes.func.isRequired, get: PropTypes.func.isRequired, - getKey: PropTypes.func.isRequired, set: PropTypes.func.isRequired, }), unstable_onDataSourceError: PropTypes.func, diff --git a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx index ce229cfcb815..7f7c45fa1d5e 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx @@ -952,7 +952,6 @@ DataGridProRaw.propTypes = { unstable_dataSourceCache: PropTypes.shape({ clear: PropTypes.func.isRequired, get: PropTypes.func.isRequired, - getKey: PropTypes.func.isRequired, set: PropTypes.func.isRequired, }), unstable_onDataSourceError: PropTypes.func, 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 b1777aecf70a..0f43ae84fe58 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 @@ -9,6 +9,15 @@ type GridDataSourceDefaultCacheConfig = { ttl?: number; }; +function getKey(params: GridGetRowsParams) { + return JSON.stringify([ + params.paginationModel, + params.filterModel, + params.sortModel, + params.groupKeys, + ]); +} + export class GridDataSourceDefaultCache { private cache: Record; @@ -19,28 +28,20 @@ export class GridDataSourceDefaultCache { this.ttl = ttl; } - // eslint-disable-next-line class-methods-use-this - getKey(params: GridGetRowsParams) { - return JSON.stringify([ - params.paginationModel, - params.filterModel, - params.sortModel, - params.groupKeys, - ]); - } - - set(key: string, value: GridGetRowsResponse) { + set(key: GridGetRowsParams, value: GridGetRowsResponse) { + const keyString = getKey(key); const expiry = Date.now() + this.ttl; - this.cache[key] = { value, expiry }; + this.cache[keyString] = { value, expiry }; } - get(key: string): GridGetRowsResponse | undefined { - const entry = this.cache[key]; + get(key: GridGetRowsParams): GridGetRowsResponse | undefined { + const keyString = getKey(key); + const entry = this.cache[keyString]; if (!entry) { return undefined; } if (Date.now() > entry.expiry) { - delete this.cache[key]; + delete this.cache[keyString]; return undefined; } return entry.value; 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 9b3d66da086e..6fde837ba392 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,5 +1,5 @@ import { GridRowId } from '@mui/x-data-grid'; -import { GridGetRowsParams, GridGetRowsResponse } from '../../../models'; +import { GridDataSourceCache } from '../../../models'; export interface GridDataSourceInternalCache { groupKeys: any[]; @@ -54,19 +54,7 @@ export interface GridDataSourcePrivateApi { */ export interface GridDataSourceCacheApi { /** - * Get data from the cache - * @param {GridGetRowsParams} params The params of type `GridGetRowsParams`. - * @returns {GridGetRowsResponse | undefined} The data of type `GridGetRowsResponse` or `undefined` for cache miss. + * Data source cache object. */ - getCacheData: (params: GridGetRowsParams) => GridGetRowsResponse | undefined; - /** - * Set data in the cache - * @param {GridGetRowsParams} params The params of type [[GridGetRowsParams]]. - * @param {GridGetRowsResponse} data The data of type [[GridGetRowsResponse]]. - */ - setCacheData: (params: GridGetRowsParams, data: GridGetRowsResponse) => void; - /** - * Clear the cache - */ - clearCache: () => void; + unstable_dataSourceCache: GridDataSourceCache | null; } 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 6ec4485fde5a..b8643ee2c799 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 @@ -69,7 +69,7 @@ export const useGridDataSource = ( const fetchParams = gridGetRowsParamsSelector(privateApiRef); - const cachedData = privateApiRef.current.getCacheData(fetchParams); + const cachedData = privateApiRef.current.unstable_dataSourceCache?.get(fetchParams); if (cachedData != null) { const rows = cachedData.rows; @@ -88,7 +88,7 @@ export const useGridDataSource = ( try { const getRowsResponse = await getRows(fetchParams); - privateApiRef.current.setCacheData(fetchParams, getRowsResponse); + privateApiRef.current.unstable_dataSourceCache?.set(fetchParams, getRowsResponse); if (getRowsResponse.rowCount) { privateApiRef.current.setRowCount(getRowsResponse.rowCount); } @@ -130,7 +130,7 @@ export const useGridDataSource = ( const fetchParams = { ...gridGetRowsParamsSelector(privateApiRef), groupKeys: rowNode.path }; - const cachedData = privateApiRef.current.getCacheData(fetchParams); + const cachedData = privateApiRef.current.unstable_dataSourceCache?.get(fetchParams); const isLoading = gridDataSourceLoadingSelector(privateApiRef)[id] ?? false; if (cachedData != null) { @@ -170,7 +170,7 @@ export const useGridDataSource = ( return; } nestedDataManager.setRequestSettled(id); - privateApiRef.current.setCacheData(fetchParams, getRowsResponse); + privateApiRef.current.unstable_dataSourceCache?.set(fetchParams, getRowsResponse); if (getRowsResponse.rowCount) { privateApiRef.current.setRowCount(getRowsResponse.rowCount); } @@ -267,7 +267,7 @@ export const useGridDataSource = ( */ React.useEffect(() => { if (props.unstable_dataSource) { - privateApiRef.current.clearCache(); + privateApiRef.current.unstable_dataSourceCache?.clear(); privateApiRef.current.fetchTopLevelRows(); } }, [privateApiRef, props.unstable_dataSource]); diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts index 19c3346d5168..c461494b0ebb 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts @@ -3,56 +3,24 @@ import useLazyRef from '@mui/utils/useLazyRef'; import { useGridApiMethod } from '@mui/x-data-grid'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; -import { GridGetRowsParams, GridGetRowsResponse, GridDataSourceCache } from '../../../models'; +import { GridDataSourceCache } from '../../../models'; import { GridDataSourceCacheApi } from './interfaces'; import { GridDataSourceDefaultCache } from './cache'; -const getDefaultCache = (cacheInstance: GridDataSourceDefaultCache): GridDataSourceCache => ({ - getKey: cacheInstance.getKey, - set: (key: string, value: GridGetRowsResponse) => - cacheInstance.set(key as string, value as GridGetRowsResponse), - get: (key: string) => cacheInstance.get(key as string), - clear: () => cacheInstance.clear(), -}); - export const useGridDataSourceCache = ( privateApiRef: React.MutableRefObject, props: Pick, ): void => { - const defaultCache = useLazyRef(() => - getDefaultCache(new GridDataSourceDefaultCache({})), - ); - const cache = React.useRef( - props.unstable_dataSourceCache || defaultCache.current, - ); + const defaultCache = useLazyRef( + () => new GridDataSourceDefaultCache({}), + ).current; - const getCacheData = React.useCallback((params: GridGetRowsParams) => { - if (!cache.current) { - return undefined; - } - const key = cache.current.getKey(params); - return cache.current.get(key); - }, []); - - const setCacheData = React.useCallback((params: GridGetRowsParams, data: GridGetRowsResponse) => { - if (!cache.current) { - return; - } - const key = cache.current.getKey(params); - cache.current.set(key, data); - }, []); - - const clearCache = React.useCallback(() => { - if (!cache.current) { - return; - } - cache.current.clear(); - }, []); + const [cache, setCache] = React.useState( + props.unstable_dataSourceCache !== undefined ? props.unstable_dataSourceCache : defaultCache, + ); const dataSourceCacheApi: GridDataSourceCacheApi = { - getCacheData, - setCacheData, - clearCache, + unstable_dataSourceCache: cache, }; useGridApiMethod(privateApiRef, dataSourceCacheApi, 'public'); @@ -64,7 +32,7 @@ export const useGridDataSourceCache = ( return; } if (props.unstable_dataSourceCache !== undefined) { - cache.current = props.unstable_dataSourceCache; + setCache(props.unstable_dataSourceCache); } }, [props.unstable_dataSourceCache]); }; diff --git a/packages/x-data-grid/src/models/gridDataSource.ts b/packages/x-data-grid/src/models/gridDataSource.ts index 88bc17786a7b..58038fe0e290 100644 --- a/packages/x-data-grid/src/models/gridDataSource.ts +++ b/packages/x-data-grid/src/models/gridDataSource.ts @@ -78,24 +78,18 @@ export interface GridDataSource { } export interface GridDataSourceCache { - /** - * Provide a key for the cache to be used in `set` and `get` - * @param {GridGetRowsParams} params The parameters required to fetch the rows - * @returns {any} The key for the cache to be used in `set` and `get` - */ - getKey: (params: GridGetRowsParams) => any; /** * Set the cache entry for the given key - * @param {any} key The key for the cache + * @param {GridGetRowsParams} key The key of type `GridGetRowsParams` * @param {GridGetRowsResponse} value The value to be stored in the cache */ - set: (key: any, value: GridGetRowsResponse) => void; + set: (key: GridGetRowsParams, value: GridGetRowsResponse) => void; /** * Get the cache entry for the given key - * @param {any} key The key for the cache + * @param {GridGetRowsParams} key The key of type `GridGetRowsParams` * @returns {GridGetRowsResponse} The value stored in the cache */ - get: (key: any) => GridGetRowsResponse | undefined; + get: (key: GridGetRowsParams) => GridGetRowsResponse | undefined; /** * Clear the cache */ From ffa8b79270c48581bdf27d576b20a108fc31449a Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 10 Jun 2024 10:13:50 +0500 Subject: [PATCH 65/90] GridDataSourceDefaultCache -> GridDataSourceCacheDefault --- .../data-grid/server-side-data/ServerSideDataGridTTL.js | 4 ++-- .../data-grid/server-side-data/ServerSideDataGridTTL.tsx | 4 ++-- docs/data/data-grid/server-side-data/index.md | 8 ++++---- .../src/hooks/features/dataSource/cache.ts | 6 +++--- .../hooks/features/dataSource/useGridDataSourceCache.ts | 4 ++-- scripts/x-data-grid-premium.exports.json | 2 +- scripts/x-data-grid-pro.exports.json | 2 +- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.js b/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.js index 1d17e6f5de08..615cf4072a62 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.js @@ -1,8 +1,8 @@ import * as React from 'react'; -import { DataGridPro, GridDataSourceDefaultCache } from '@mui/x-data-grid-pro'; +import { DataGridPro, GridDataSourceCacheDefault } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; -const lowTTLCache = new GridDataSourceDefaultCache({ ttl: 1000 * 10 }); // 10 seconds +const lowTTLCache = new GridDataSourceCacheDefault({ ttl: 1000 * 10 }); // 10 seconds function ServerSideDataGridTTL() { const { columns, initialState, fetchRows } = useMockServer( diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.tsx index 1f0012917676..3f8f3525e78c 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.tsx @@ -2,11 +2,11 @@ import * as React from 'react'; import { DataGridPro, GridDataSource, - GridDataSourceDefaultCache, + GridDataSourceCacheDefault, } from '@mui/x-data-grid-pro'; import { useMockServer } from '@mui/x-data-grid-generator'; -const lowTTLCache = new GridDataSourceDefaultCache({ ttl: 1000 * 10 }); // 10 seconds +const lowTTLCache = new GridDataSourceCacheDefault({ ttl: 1000 * 10 }); // 10 seconds function ServerSideDataGridTTL() { const { columns, initialState, fetchRows } = useMockServer( diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index 6cbb7832cd83..6359f9e3e4b7 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -177,18 +177,18 @@ Open info section of the browser console to see the requests being made and the The data source caches fetched data by default. This means that if the user navigates to a page or expands a node that has already been fetched, the grid will not call the `getRows` function again to avoid unnecessary calls to the server. -The `GridDataSourceDefaultCache` is used by default which is a simple in-memory cache that stores the data in a plain object. It could be seen in action in the demo below. +The `GridDataSourceCacheDefault` is used by default which is a simple in-memory cache that stores the data in a plain object. It could be seen in action in the demo below. {{"demo": "ServerSideDataGrid.js", "bg": "inline"}} ### Customize the cache lifetime -The `GridDataSourceDefaultCache` has a default Time To Live (`ttl`) of 5 minutes. To customize it, pass the `ttl` option in milliseconds to the `GridDataSourceDefaultCache` constructor, and then pass it as the `unstable_dataSourceCache` prop. +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. ```tsx -import { GridDataSourceDefaultCache } from '@mui/x-data-grid-pro'; +import { GridDataSourceCacheDefault } from '@mui/x-data-grid-pro'; -const lowTTLCache = new GridDataSourceDefaultCache({ ttl: 1000 * 10 }); // 10 seconds +const lowTTLCache = new GridDataSourceCacheDefault({ ttl: 1000 * 10 }); // 10 seconds ; private ttl: number; - constructor({ ttl = 300000 }: GridDataSourceDefaultCacheConfig) { + constructor({ ttl = 300000 }: GridDataSourceCacheDefaultConfig) { this.cache = {}; this.ttl = ttl; } diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts index c461494b0ebb..a3aa3f043b70 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts @@ -5,14 +5,14 @@ import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; import { GridDataSourceCache } from '../../../models'; import { GridDataSourceCacheApi } from './interfaces'; -import { GridDataSourceDefaultCache } from './cache'; +import { GridDataSourceCacheDefault } from './cache'; export const useGridDataSourceCache = ( privateApiRef: React.MutableRefObject, props: Pick, ): void => { const defaultCache = useLazyRef( - () => new GridDataSourceDefaultCache({}), + () => new GridDataSourceCacheDefault({}), ).current; const [cache, setCache] = React.useState( diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 1ed2e58730bf..06098194e63c 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -248,7 +248,7 @@ { "name": "GridDataSourceApi", "kind": "Interface" }, { "name": "GridDataSourceCache", "kind": "Interface" }, { "name": "GridDataSourceCacheApi", "kind": "Interface" }, - { "name": "GridDataSourceDefaultCache", "kind": "Class" }, + { "name": "GridDataSourceCacheDefault", "kind": "Class" }, { "name": "GridDataSourceInternalCache", "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 5053555b54fc..9fd3e6275c29 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -222,7 +222,7 @@ { "name": "GridDataSourceApi", "kind": "Interface" }, { "name": "GridDataSourceCache", "kind": "Interface" }, { "name": "GridDataSourceCacheApi", "kind": "Interface" }, - { "name": "GridDataSourceDefaultCache", "kind": "Class" }, + { "name": "GridDataSourceCacheDefault", "kind": "Class" }, { "name": "GridDataSourceInternalCache", "kind": "Interface" }, { "name": "GridDataSourcePrivateApi", "kind": "Interface" }, { "name": "GridDataSourceState", "kind": "Interface" }, From 8b1ced8e43fc789cc07b992beae3651329e36274 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 10 Jun 2024 10:28:57 +0500 Subject: [PATCH 66/90] Make behavior of isLoading consistent --- .../hooks/features/dataSource/useGridDataSource.ts | 11 ++--------- .../src/hooks/features/dataSource/utils.ts | 1 + 2 files changed, 3 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 b8643ee2c799..9c0896f6a0d9 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 @@ -103,11 +103,8 @@ export const useGridDataSource = ( }, [nestedDataManager, privateApiRef, props.unstable_dataSource?.getRows, onError]); const queueChildrenFetch = React.useCallback( - (id: GridRowId) => { - privateApiRef.current.setChildrenLoading(id, true); - nestedDataManager.enqueue([id]); - }, - [privateApiRef, nestedDataManager], + (id: GridRowId) => nestedDataManager.enqueue([id]), + [nestedDataManager], ); const fetchRowChildren = React.useCallback( @@ -148,10 +145,6 @@ export const useGridDataSource = ( return; } - if (!isLoading) { - privateApiRef.current.setChildrenLoading(id, true); - } - const existingError = gridDataSourceErrorsSelector(privateApiRef)[id] ?? null; if (existingError) { privateApiRef.current.setChildrenFetchError(id, null); 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 8fcdccb03c4f..764538a0b46c 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 @@ -64,6 +64,7 @@ export class NestedDataManager { public enqueue = async (ids: GridRowId[]) => { ids.forEach((id) => { this.queuedRequests.add(id); + this.api.setChildrenLoading(id, true); }); this.processQueue(); }; From f4ad39cb233d9e0d89b648c1adee1485a4f5d33a Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 10 Jun 2024 10:43:14 +0500 Subject: [PATCH 67/90] Combine API methods into one --- docs/pages/x/api/data-grid/grid-api.json | 9 +- .../api-docs/data-grid/grid-api.json | 7 +- .../GridServerSideTreeDataGroupingCell.tsx | 2 +- .../hooks/features/dataSource/interfaces.ts | 15 ++- .../features/dataSource/useGridDataSource.ts | 102 +++++++++--------- .../features/treeData/useGridTreeData.tsx | 2 +- 6 files changed, 66 insertions(+), 71 deletions(-) diff --git a/docs/pages/x/api/data-grid/grid-api.json b/docs/pages/x/api/data-grid/grid-api.json index d9f835c42315..95b2d8791522 100644 --- a/docs/pages/x/api/data-grid/grid-api.json +++ b/docs/pages/x/api/data-grid/grid-api.json @@ -40,8 +40,8 @@ "type": { "description": "(params?: GridExportStateParams) => InitialState" }, "required": true }, - "fetchTopLevelRows": { - "type": { "description": "() => void" }, + "fetchRows": { + "type": { "description": "(parentId?: GridRowId) => void" }, "required": true, "isProPlan": true }, @@ -239,11 +239,6 @@ "isProPlan": true }, "publishEvent": { "type": { "description": "GridEventPublisher" }, "required": true }, - "queueChildrenFetch": { - "type": { "description": "(id: GridRowId) => void" }, - "required": true, - "isProPlan": true - }, "removeRowGroupingCriteria": { "type": { "description": "(groupingCriteriaField: string) => void" }, "required": true, diff --git a/docs/translations/api-docs/data-grid/grid-api.json b/docs/translations/api-docs/data-grid/grid-api.json index 91bf34b8b435..e3f1d2d4e307 100644 --- a/docs/translations/api-docs/data-grid/grid-api.json +++ b/docs/translations/api-docs/data-grid/grid-api.json @@ -17,7 +17,9 @@ "exportState": { "description": "Generates a serializable object containing the exportable parts of the DataGrid state.
These values can then be passed to the initialState prop or injected using the restoreState method." }, - "fetchTopLevelRows": { "description": "Fetch/refetch the top level rows." }, + "fetchRows": { + "description": "Fetches the rows from the server for a given parentId.
If no parentId is provided, it fetches the root rows." + }, "forceUpdate": { "description": "Forces the grid to rerender. It's often used after a state update." }, @@ -123,7 +125,6 @@ "isRowSelected": { "description": "Determines if a row is selected or not." }, "pinColumn": { "description": "Pins a column to the left or right side of the grid." }, "publishEvent": { "description": "Emits an event." }, - "queueChildrenFetch": { "description": "Adds the fetch of the children of a row to queue." }, "removeRowGroupingCriteria": { "description": "Remove the field from the row grouping model." }, "resetRowHeights": { "description": "Forces the recalculation of the heights of all rows." }, "resize": { @@ -247,7 +248,7 @@ }, "toggleDetailPanel": { "description": "Expands or collapses the detail panel of a row." }, "unpinColumn": { "description": "Unpins a column." }, - "unstable_dataSourceCache": { "description": "Data source cache object." }, + "unstable_dataSourceCache": { "description": "The data source cache object." }, "unstable_replaceRows": { "description": "Replace a set of rows with new rows." }, "unstable_setColumnVirtualization": { "description": "Enable/disable column virtualization." }, "unstable_setPinnedRows": { "description": "Changes the pinned rows." }, diff --git a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx b/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx index f0215b17c37f..52a668cfcbc5 100644 --- a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx +++ b/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.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.queueChildrenFetch(id); + apiRef.current.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 6fde837ba392..970d2925815a 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 @@ -27,20 +27,17 @@ export interface GridDataSourceApi { */ setChildrenFetchError: (parentId: GridRowId, error: Error | null) => void; /** - * Fetch/refetch the top level rows. + * 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. */ - fetchTopLevelRows: () => void; - /** - * Adds the fetch of the children of a row to queue. - * @param {GridRowId} id The id of the rowNode belonging to the group to be fetched. - */ - queueChildrenFetch: (id: GridRowId) => void; + fetchRows: (parentId?: GridRowId) => void; } export interface GridDataSourcePrivateApi { /** * Initiates the fetch of the children of a row. - * @param {string} id The id of the rowNode belonging to the group to be fetched. + * @param {string} id The id of the group to be fetched. */ fetchRowChildren: (id: GridRowId) => void; /** @@ -54,7 +51,7 @@ export interface GridDataSourcePrivateApi { */ export interface GridDataSourceCacheApi { /** - * Data source cache object. + * The data source cache object. */ unstable_dataSourceCache: GridDataSourceCache | null; } 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 9c0896f6a0d9..2b1618ebd950 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 @@ -5,8 +5,8 @@ import { gridRowsLoadingSelector, useGridApiMethod, GridServerSideGroupNode, - GridRowId, useGridSelector, + GridRowId, } from '@mui/x-data-grid'; import { gridRowGroupsToFetchSelector, GridStateInitializer } from '@mui/x-data-grid/internals'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; @@ -54,57 +54,60 @@ export const useGridDataSource = ( const scheduledGroups = React.useRef(0); const onError = props.unstable_onDataSourceError; - const fetchTopLevelRows = React.useCallback(async () => { - const getRows = props.unstable_dataSource?.getRows; - if (!getRows) { - return; - } + const fetchRows = React.useCallback( + async (parentId?: GridRowId) => { + const getRows = props.unstable_dataSource?.getRows; + if (!getRows) { + return; + } - nestedDataManager.clear(); - scheduledGroups.current = 0; - const dataSourceState = privateApiRef.current.state.dataSource; - if (dataSourceState !== INITIAL_STATE) { - privateApiRef.current.resetDataSourceState(); - } + if (parentId) { + nestedDataManager.enqueue([parentId]); + return; + } - const fetchParams = gridGetRowsParamsSelector(privateApiRef); + nestedDataManager.clear(); + scheduledGroups.current = 0; + const dataSourceState = privateApiRef.current.state.dataSource; + if (dataSourceState !== INITIAL_STATE) { + privateApiRef.current.resetDataSourceState(); + } - const cachedData = privateApiRef.current.unstable_dataSourceCache?.get(fetchParams); + const fetchParams = gridGetRowsParamsSelector(privateApiRef); - if (cachedData != null) { - const rows = cachedData.rows; - privateApiRef.current.caches.dataSource.groupKeys = []; - privateApiRef.current.setRows(rows); - if (cachedData.rowCount) { - privateApiRef.current.setRowCount(cachedData.rowCount); - } - return; - } + const cachedData = privateApiRef.current.unstable_dataSourceCache?.get(fetchParams); - const isLoading = gridRowsLoadingSelector(privateApiRef); - if (!isLoading) { - privateApiRef.current.setLoading(true); - } + if (cachedData != null) { + const rows = cachedData.rows; + privateApiRef.current.caches.dataSource.groupKeys = []; + privateApiRef.current.setRows(rows); + if (cachedData.rowCount) { + privateApiRef.current.setRowCount(cachedData.rowCount); + } + return; + } - try { - const getRowsResponse = await getRows(fetchParams); - privateApiRef.current.unstable_dataSourceCache?.set(fetchParams, getRowsResponse); - if (getRowsResponse.rowCount) { - privateApiRef.current.setRowCount(getRowsResponse.rowCount); + const isLoading = gridRowsLoadingSelector(privateApiRef); + if (!isLoading) { + privateApiRef.current.setLoading(true); } - privateApiRef.current.caches.dataSource.groupKeys = []; - privateApiRef.current.setRows(getRowsResponse.rows); - privateApiRef.current.setLoading(false); - } catch (error) { - privateApiRef.current.setRows([]); - privateApiRef.current.setLoading(false); - onError?.(error as Error, fetchParams); - } - }, [nestedDataManager, privateApiRef, props.unstable_dataSource?.getRows, onError]); - const queueChildrenFetch = React.useCallback( - (id: GridRowId) => nestedDataManager.enqueue([id]), - [nestedDataManager], + try { + const getRowsResponse = await getRows(fetchParams); + privateApiRef.current.unstable_dataSourceCache?.set(fetchParams, getRowsResponse); + if (getRowsResponse.rowCount) { + privateApiRef.current.setRowCount(getRowsResponse.rowCount); + } + privateApiRef.current.caches.dataSource.groupKeys = []; + privateApiRef.current.setRows(getRowsResponse.rows); + privateApiRef.current.setLoading(false); + } catch (error) { + privateApiRef.current.setRows([]); + privateApiRef.current.setLoading(false); + onError?.(error as Error, fetchParams); + } + }, + [nestedDataManager, privateApiRef, props.unstable_dataSource?.getRows, onError], ); const fetchRowChildren = React.useCallback( @@ -222,10 +225,9 @@ export const useGridDataSource = ( }, [privateApiRef]); const dataSourceApi: GridDataSourceApi = { - queueChildrenFetch, setChildrenLoading, setChildrenFetchError, - fetchTopLevelRows, + fetchRows, }; const dataSourcePrivateApi: GridDataSourcePrivateApi = { @@ -242,17 +244,17 @@ export const useGridDataSource = ( useGridApiEventHandler( privateApiRef, 'sortModelChange', - runIfServerMode(props.sortingMode, fetchTopLevelRows), + runIfServerMode(props.sortingMode, fetchRows), ); useGridApiEventHandler( privateApiRef, 'filterModelChange', - runIfServerMode(props.filterMode, fetchTopLevelRows), + runIfServerMode(props.filterMode, fetchRows), ); useGridApiEventHandler( privateApiRef, 'paginationModelChange', - runIfServerMode(props.paginationMode, fetchTopLevelRows), + runIfServerMode(props.paginationMode, fetchRows), ); /* @@ -261,7 +263,7 @@ export const useGridDataSource = ( React.useEffect(() => { if (props.unstable_dataSource) { privateApiRef.current.unstable_dataSourceCache?.clear(); - privateApiRef.current.fetchTopLevelRows(); + privateApiRef.current.fetchRows(); } }, [privateApiRef, props.unstable_dataSource]); 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 c56bc146a015..96e9d973e795 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.queueChildrenFetch(params.id); + apiRef.current.fetchRows(params.id); return; } From 2bb357d8116dd2bd5120393a9a9cf39331666cf1 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 10 Jun 2024 10:59:43 +0500 Subject: [PATCH 68/90] Group pubic methods under unstable_dataSource namespace --- docs/pages/x/api/data-grid/grid-api.json | 20 ++++--------- .../api-docs/data-grid/grid-api.json | 8 +----- .../GridServerSideTreeDataGroupingCell.tsx | 2 +- .../hooks/features/dataSource/interfaces.ts | 7 +++-- .../features/dataSource/useGridDataSource.ts | 28 ++++++++++--------- .../src/hooks/features/dataSource/utils.ts | 4 +-- .../features/treeData/useGridTreeData.tsx | 2 +- scripts/x-data-grid-premium.exports.json | 1 + scripts/x-data-grid-pro.exports.json | 1 + 9 files changed, 32 insertions(+), 41 deletions(-) diff --git a/docs/pages/x/api/data-grid/grid-api.json b/docs/pages/x/api/data-grid/grid-api.json index 95b2d8791522..56fffa6e8c48 100644 --- a/docs/pages/x/api/data-grid/grid-api.json +++ b/docs/pages/x/api/data-grid/grid-api.json @@ -40,11 +40,6 @@ "type": { "description": "(params?: GridExportStateParams) => InitialState" }, "required": true }, - "fetchRows": { - "type": { "description": "(parentId?: GridRowId) => void" }, - "required": true, - "isProPlan": true - }, "forceUpdate": { "type": { "description": "() => void" }, "required": true }, "getAllColumns": { "type": { "description": "() => GridStateColDef[]" }, "required": true }, "getAllGroupDetails": { @@ -299,16 +294,6 @@ "required": true, "isPremiumPlan": true }, - "setChildrenFetchError": { - "type": { "description": "(parentId: GridRowId, error: Error | null) => void" }, - "required": true, - "isProPlan": true - }, - "setChildrenLoading": { - "type": { "description": "(parentId: GridRowId, loading: boolean) => void" }, - "required": true, - "isProPlan": true - }, "setColumnHeaderFilterFocus": { "type": { "description": "(field: string, event?: MuiBaseEvent) => void" }, "required": true @@ -476,6 +461,11 @@ "required": true, "isProPlan": true }, + "unstable_dataSource": { + "type": { "description": "GridDataSourceApiBase" }, + "required": true, + "isProPlan": true + }, "unstable_dataSourceCache": { "type": { "description": "GridDataSourceCache | null" }, "required": true, diff --git a/docs/translations/api-docs/data-grid/grid-api.json b/docs/translations/api-docs/data-grid/grid-api.json index e3f1d2d4e307..e5b6521944df 100644 --- a/docs/translations/api-docs/data-grid/grid-api.json +++ b/docs/translations/api-docs/data-grid/grid-api.json @@ -17,9 +17,6 @@ "exportState": { "description": "Generates a serializable object containing the exportable parts of the DataGrid state.
These values can then be passed to the initialState prop or injected using the restoreState method." }, - "fetchRows": { - "description": "Fetches the rows from the server for a given parentId.
If no parentId is provided, it fetches the root rows." - }, "forceUpdate": { "description": "Forces the grid to rerender. It's often used after a state update." }, @@ -154,10 +151,6 @@ "setCellSelectionModel": { "description": "Updates the selected cells to be those passed to the newModel argument.
Any cell already selected will be unselected." }, - "setChildrenFetchError": { - "description": "Set error occured while fetching the children of a row." - }, - "setChildrenLoading": { "description": "Set the loading state of a parent row." }, "setColumnHeaderFilterFocus": { "description": "Sets the focus to the column header filter at the given field." }, @@ -248,6 +241,7 @@ }, "toggleDetailPanel": { "description": "Expands or collapses the detail panel of a row." }, "unpinColumn": { "description": "Unpins a column." }, + "unstable_dataSource": { "description": "" }, "unstable_dataSourceCache": { "description": "The data source cache object." }, "unstable_replaceRows": { "description": "Replace a set of rows with new rows." }, "unstable_setColumnVirtualization": { "description": "Enable/disable column virtualization." }, diff --git a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx b/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx index 52a668cfcbc5..fdf5d46caa5f 100644 --- a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx +++ b/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.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.fetchRows(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 970d2925815a..aedc88d2f687 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 @@ -11,9 +11,9 @@ export interface GridDataSourceState { } /** - * The dataSource API interface that is available in the grid [[apiRef]]. + * The base data source API interface that is available in the grid [[apiRef]]. */ -export interface GridDataSourceApi { +export interface GridDataSourceApiBase { /** * Set the loading state of a parent row. * @param {string} parentId The id of the parent node. @@ -34,6 +34,9 @@ export interface GridDataSourceApi { fetchRows: (parentId?: GridRowId) => void; } +export interface GridDataSourceApi { + unstable_dataSource: GridDataSourceApiBase; +} export interface GridDataSourcePrivateApi { /** * Initiates the fetch of the children of a row. 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 2b1618ebd950..85c2b984c018 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -16,7 +16,7 @@ import { gridDataSourceLoadingSelector, gridDataSourceErrorsSelector, } from './gridDataSourceSelector'; -import { GridDataSourceApi, GridDataSourcePrivateApi } from './interfaces'; +import { GridDataSourceApi, GridDataSourceApiBase, GridDataSourcePrivateApi } from './interfaces'; import { runIfServerMode, NestedDataManager, RequestStatus } from './utils'; const INITIAL_STATE = { @@ -143,14 +143,14 @@ export const useGridDataSource = ( } privateApiRef.current.setRowChildrenExpansion(id, true); if (isLoading) { - privateApiRef.current.setChildrenLoading(id, false); + privateApiRef.current.unstable_dataSource.setChildrenLoading(id, false); } return; } const existingError = gridDataSourceErrorsSelector(privateApiRef)[id] ?? null; if (existingError) { - privateApiRef.current.setChildrenFetchError(id, null); + privateApiRef.current.unstable_dataSource.setChildrenFetchError(id, null); } try { @@ -158,11 +158,11 @@ export const useGridDataSource = ( if (!privateApiRef.current.getRowNode(id)) { // The row has been removed from the grid nestedDataManager.clearPendingRequest(id); - privateApiRef.current.setChildrenLoading(id, false); + privateApiRef.current.unstable_dataSource.setChildrenLoading(id, false); return; } if (nestedDataManager.getRequestStatus(id) === RequestStatus.UNKNOWN) { - privateApiRef.current.setChildrenLoading(id, false); + privateApiRef.current.unstable_dataSource.setChildrenLoading(id, false); return; } nestedDataManager.setRequestSettled(id); @@ -175,17 +175,17 @@ export const useGridDataSource = ( privateApiRef.current.setRowChildrenExpansion(id, true); } catch (error) { const e = error as Error; - privateApiRef.current.setChildrenFetchError(id, e); + privateApiRef.current.unstable_dataSource.setChildrenFetchError(id, e); onError?.(e, fetchParams); } finally { - privateApiRef.current.setChildrenLoading(id, false); + privateApiRef.current.unstable_dataSource.setChildrenLoading(id, false); nestedDataManager.setRequestSettled(id); } }, [nestedDataManager, onError, privateApiRef, props.treeData, props.unstable_dataSource?.getRows], ); - const setChildrenLoading = React.useCallback( + const setChildrenLoading = React.useCallback( (parentId, isLoading) => { privateApiRef.current.setState((state) => { return { @@ -200,7 +200,7 @@ export const useGridDataSource = ( [privateApiRef], ); - const setChildrenFetchError = React.useCallback( + const setChildrenFetchError = React.useCallback( (parentId, error) => { privateApiRef.current.setState((state) => { return { @@ -225,9 +225,11 @@ export const useGridDataSource = ( }, [privateApiRef]); const dataSourceApi: GridDataSourceApi = { - setChildrenLoading, - setChildrenFetchError, - fetchRows, + unstable_dataSource: { + setChildrenLoading, + setChildrenFetchError, + fetchRows, + }, }; const dataSourcePrivateApi: GridDataSourcePrivateApi = { @@ -263,7 +265,7 @@ export const useGridDataSource = ( React.useEffect(() => { if (props.unstable_dataSource) { privateApiRef.current.unstable_dataSourceCache?.clear(); - privateApiRef.current.fetchRows(); + privateApiRef.current.unstable_dataSource.fetchRows(); } }, [privateApiRef, props.unstable_dataSource]); 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 764538a0b46c..bbdc4624fd27 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 @@ -64,7 +64,7 @@ export class NestedDataManager { public enqueue = async (ids: GridRowId[]) => { ids.forEach((id) => { this.queuedRequests.add(id); - this.api.setChildrenLoading(id, true); + this.api.unstable_dataSource.setChildrenLoading(id, true); }); this.processQueue(); }; @@ -81,7 +81,7 @@ export class NestedDataManager { }; public clearPendingRequest = (id: GridRowId) => { - this.api.setChildrenLoading(id, false); + this.api.unstable_dataSource.setChildrenLoading(id, false); this.pendingRequests.delete(id); this.processQueue(); }; 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 96e9d973e795..8f09f695627d 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.fetchRows(params.id); + apiRef.current.unstable_dataSource.fetchRows(params.id); return; } diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 06098194e63c..707a70c7ec22 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -246,6 +246,7 @@ { "name": "gridDataRowIdsSelector", "kind": "Variable" }, { "name": "GridDataSource", "kind": "Interface" }, { "name": "GridDataSourceApi", "kind": "Interface" }, + { "name": "GridDataSourceApiBase", "kind": "Interface" }, { "name": "GridDataSourceCache", "kind": "Interface" }, { "name": "GridDataSourceCacheApi", "kind": "Interface" }, { "name": "GridDataSourceCacheDefault", "kind": "Class" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index 9fd3e6275c29..a67f34d8e5d5 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -220,6 +220,7 @@ { "name": "gridDataRowIdsSelector", "kind": "Variable" }, { "name": "GridDataSource", "kind": "Interface" }, { "name": "GridDataSourceApi", "kind": "Interface" }, + { "name": "GridDataSourceApiBase", "kind": "Interface" }, { "name": "GridDataSourceCache", "kind": "Interface" }, { "name": "GridDataSourceCacheApi", "kind": "Interface" }, { "name": "GridDataSourceCacheDefault", "kind": "Class" }, From e54602afb5a49ed9a45820725e509e70c4106827 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 10 Jun 2024 11:00:00 +0500 Subject: [PATCH 69/90] Cleanup --- .../server-side-data/ServerSideTreeDataErrorHandling.js | 2 +- .../server-side-data/ServerSideTreeDataErrorHandling.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js index dd477861a145..44da5bf0cab3 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js @@ -71,7 +71,7 @@ export default function ServerSideTreeDataErrorHandling() {
diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx index 6dd0311fe7dc..c2c0caa249e4 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx @@ -61,7 +61,7 @@ export default function ServerSideTreeData() { return (
-
diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview index a6735aa88c54..6b8c501d8da3 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview @@ -1,4 +1,4 @@ -
diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js index 877a4d8a22ae..d79fcfd5026b 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js @@ -55,7 +55,7 @@ export default function ServerSideTreeDataGroupExpansion() { return (
-
diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx index 52783bbcbd03..dc063e38b96c 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx @@ -63,7 +63,7 @@ export default function ServerSideTreeDataGroupExpansion() { return (
-
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 8393e5e7fe5e..46683a3da061 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 @@ -52,5 +52,5 @@ export interface GridDataSourceCacheApi { /** * The data source cache object. */ - unstable_dataSourceCache: GridDataSourceCache | null; + unstable_dataSourceCache: GridDataSourceCache; } 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 c8a1ae536fc5..e31b50fb053f 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 @@ -28,7 +28,7 @@ export const dataSourceStateInitializer: GridStateInitializer = (state) => { }; export const useGridDataSource = ( - privateApiRef: React.MutableRefObject, + apiRef: React.MutableRefObject, props: Pick< DataGridProProcessedProps, | 'unstable_dataSource' @@ -43,9 +43,9 @@ export const useGridDataSource = ( >, ) => { const nestedDataManager = useLazyRef( - () => new NestedDataManager(privateApiRef), + () => new NestedDataManager(apiRef), ).current; - const groupsToAutoFetch = useGridSelector(privateApiRef, gridRowGroupsToFetchSelector); + const groupsToAutoFetch = useGridSelector(apiRef, gridRowGroupsToFetchSelector); const scheduledGroups = React.useRef(0); const onError = props.unstable_onDataSourceError; @@ -57,50 +57,50 @@ export const useGridDataSource = ( } if (parentId) { - nestedDataManager.enqueue([parentId]); + nestedDataManager.queue([parentId]); return; } nestedDataManager.clear(); scheduledGroups.current = 0; - const dataSourceState = privateApiRef.current.state.dataSource; + const dataSourceState = apiRef.current.state.dataSource; if (dataSourceState !== INITIAL_STATE) { - privateApiRef.current.resetDataSourceState(); + apiRef.current.resetDataSourceState(); } - const fetchParams = gridGetRowsParamsSelector(privateApiRef); + const fetchParams = gridGetRowsParamsSelector(apiRef); - const cachedData = privateApiRef.current.unstable_dataSourceCache?.get(fetchParams); + const cachedData = apiRef.current.unstable_dataSourceCache.get(fetchParams); - if (cachedData != null) { + if (cachedData !== undefined) { const rows = cachedData.rows; - privateApiRef.current.setRows(rows); + apiRef.current.setRows(rows); if (cachedData.rowCount) { - privateApiRef.current.setRowCount(cachedData.rowCount); + apiRef.current.setRowCount(cachedData.rowCount); } return; } - const isLoading = gridRowsLoadingSelector(privateApiRef); + const isLoading = gridRowsLoadingSelector(apiRef); if (!isLoading) { - privateApiRef.current.setLoading(true); + apiRef.current.setLoading(true); } try { const getRowsResponse = await getRows(fetchParams); - privateApiRef.current.unstable_dataSourceCache?.set(fetchParams, getRowsResponse); + apiRef.current.unstable_dataSourceCache.set(fetchParams, getRowsResponse); if (getRowsResponse.rowCount) { - privateApiRef.current.setRowCount(getRowsResponse.rowCount); + apiRef.current.setRowCount(getRowsResponse.rowCount); } - privateApiRef.current.setRows(getRowsResponse.rows); - privateApiRef.current.setLoading(false); + apiRef.current.setRows(getRowsResponse.rows); + apiRef.current.setLoading(false); } catch (error) { - privateApiRef.current.setRows([]); - privateApiRef.current.setLoading(false); + apiRef.current.setRows([]); + apiRef.current.setLoading(false); onError?.(error as Error, fetchParams); } }, - [nestedDataManager, privateApiRef, props.unstable_dataSource?.getRows, onError], + [nestedDataManager, apiRef, props.unstable_dataSource?.getRows, onError], ); const fetchRowChildren = React.useCallback( @@ -115,67 +115,66 @@ export const useGridDataSource = ( return; } - const rowNode = privateApiRef.current.getRowNode(id) as GridServerSideGroupNode; + const rowNode = apiRef.current.getRowNode(id); if (!rowNode) { nestedDataManager.clearPendingRequest(id); return; } - const fetchParams = { ...gridGetRowsParamsSelector(privateApiRef), groupKeys: rowNode.path }; + const fetchParams = { ...gridGetRowsParamsSelector(apiRef), groupKeys: rowNode.path }; - const cachedData = privateApiRef.current.unstable_dataSourceCache?.get(fetchParams); + const cachedData = apiRef.current.unstable_dataSourceCache.get(fetchParams); - if (cachedData != null) { + if (cachedData !== undefined) { const rows = cachedData.rows; nestedDataManager.setRequestSettled(id); - privateApiRef.current.updateServerRows(rows, rowNode.path); + apiRef.current.updateServerRows(rows, rowNode.path); if (cachedData.rowCount) { - privateApiRef.current.setRowCount(cachedData.rowCount); + apiRef.current.setRowCount(cachedData.rowCount); } - privateApiRef.current.setRowChildrenExpansion(id, true); - privateApiRef.current.unstable_dataSource.setChildrenLoading(id, false); + apiRef.current.setRowChildrenExpansion(id, true); + apiRef.current.unstable_dataSource.setChildrenLoading(id, false); return; } - const existingError = gridDataSourceErrorsSelector(privateApiRef)[id] ?? null; + const existingError = gridDataSourceErrorsSelector(apiRef)[id] ?? null; if (existingError) { - privateApiRef.current.unstable_dataSource.setChildrenFetchError(id, null); + apiRef.current.unstable_dataSource.setChildrenFetchError(id, null); } try { const getRowsResponse = await getRows(fetchParams); - if (!privateApiRef.current.getRowNode(id)) { + if (!apiRef.current.getRowNode(id)) { // The row has been removed from the grid nestedDataManager.clearPendingRequest(id); - privateApiRef.current.unstable_dataSource.setChildrenLoading(id, false); return; } if (nestedDataManager.getRequestStatus(id) === RequestStatus.UNKNOWN) { - privateApiRef.current.unstable_dataSource.setChildrenLoading(id, false); + apiRef.current.unstable_dataSource.setChildrenLoading(id, false); return; } nestedDataManager.setRequestSettled(id); - privateApiRef.current.unstable_dataSourceCache?.set(fetchParams, getRowsResponse); + apiRef.current.unstable_dataSourceCache.set(fetchParams, getRowsResponse); if (getRowsResponse.rowCount) { - privateApiRef.current.setRowCount(getRowsResponse.rowCount); + apiRef.current.setRowCount(getRowsResponse.rowCount); } - privateApiRef.current.updateServerRows(getRowsResponse.rows, rowNode.path); - privateApiRef.current.setRowChildrenExpansion(id, true); + apiRef.current.updateServerRows(getRowsResponse.rows, rowNode.path); + apiRef.current.setRowChildrenExpansion(id, true); } catch (error) { const e = error as Error; - privateApiRef.current.unstable_dataSource.setChildrenFetchError(id, e); + apiRef.current.unstable_dataSource.setChildrenFetchError(id, e); onError?.(e, fetchParams); } finally { - privateApiRef.current.unstable_dataSource.setChildrenLoading(id, false); + apiRef.current.unstable_dataSource.setChildrenLoading(id, false); nestedDataManager.setRequestSettled(id); } }, - [nestedDataManager, onError, privateApiRef, props.treeData, props.unstable_dataSource?.getRows], + [nestedDataManager, onError, apiRef, props.treeData, props.unstable_dataSource?.getRows], ); const setChildrenLoading = React.useCallback( (parentId, isLoading) => { - privateApiRef.current.setState((state) => { + apiRef.current.setState((state) => { return { ...state, dataSource: { @@ -185,12 +184,12 @@ export const useGridDataSource = ( }; }); }, - [privateApiRef], + [apiRef], ); const setChildrenFetchError = React.useCallback( (parentId, error) => { - privateApiRef.current.setState((state) => { + apiRef.current.setState((state) => { return { ...state, dataSource: { @@ -200,17 +199,17 @@ export const useGridDataSource = ( }; }); }, - [privateApiRef], + [apiRef], ); const resetDataSourceState = React.useCallback(() => { - privateApiRef.current.setState((state) => { + apiRef.current.setState((state) => { return { ...state, dataSource: INITIAL_STATE, }; }); - }, [privateApiRef]); + }, [apiRef]); const dataSourceApi: GridDataSourceApi = { unstable_dataSource: { @@ -225,24 +224,16 @@ export const useGridDataSource = ( resetDataSourceState, }; - useGridApiMethod(privateApiRef, dataSourceApi, 'public'); - useGridApiMethod(privateApiRef, dataSourcePrivateApi, 'private'); + useGridApiMethod(apiRef, dataSourceApi, 'public'); + useGridApiMethod(apiRef, dataSourcePrivateApi, 'private'); /* * EVENTS */ + useGridApiEventHandler(apiRef, 'sortModelChange', runIfServerMode(props.sortingMode, fetchRows)); + useGridApiEventHandler(apiRef, 'filterModelChange', runIfServerMode(props.filterMode, fetchRows)); useGridApiEventHandler( - privateApiRef, - 'sortModelChange', - runIfServerMode(props.sortingMode, fetchRows), - ); - useGridApiEventHandler( - privateApiRef, - 'filterModelChange', - runIfServerMode(props.filterMode, fetchRows), - ); - useGridApiEventHandler( - privateApiRef, + apiRef, 'paginationModelChange', runIfServerMode(props.paginationMode, fetchRows), ); @@ -252,10 +243,10 @@ export const useGridDataSource = ( */ React.useEffect(() => { if (props.unstable_dataSource) { - privateApiRef.current.unstable_dataSourceCache?.clear(); - privateApiRef.current.unstable_dataSource.fetchRows(); + apiRef.current.unstable_dataSourceCache.clear(); + apiRef.current.unstable_dataSource.fetchRows(); } - }, [privateApiRef, props.unstable_dataSource]); + }, [apiRef, props.unstable_dataSource]); React.useEffect(() => { if ( @@ -264,8 +255,8 @@ export const useGridDataSource = ( scheduledGroups.current < groupsToAutoFetch.length ) { const groupsToSchedule = groupsToAutoFetch.slice(scheduledGroups.current); - nestedDataManager.enqueue(groupsToSchedule); + nestedDataManager.queue(groupsToSchedule); scheduledGroups.current = groupsToAutoFetch.length; } - }, [privateApiRef, nestedDataManager, groupsToAutoFetch]); + }, [apiRef, nestedDataManager, groupsToAutoFetch]); }; diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts index a3aa3f043b70..09dfbfd0d290 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts @@ -7,8 +7,14 @@ import { GridDataSourceCache } from '../../../models'; import { GridDataSourceCacheApi } from './interfaces'; import { GridDataSourceCacheDefault } from './cache'; +const noopCache = { + clear: () => {}, + get: () => undefined, + set: () => {}, +}; + export const useGridDataSourceCache = ( - privateApiRef: React.MutableRefObject, + apiRef: React.MutableRefObject, props: Pick, ): void => { const defaultCache = useLazyRef( @@ -20,10 +26,10 @@ export const useGridDataSourceCache = ( ); const dataSourceCacheApi: GridDataSourceCacheApi = { - unstable_dataSourceCache: cache, + unstable_dataSourceCache: cache ?? noopCache, }; - useGridApiMethod(privateApiRef, dataSourceCacheApi, 'public'); + useGridApiMethod(apiRef, dataSourceCacheApi, 'public'); 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 bbdc4624fd27..f3a6c1e94fb0 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 @@ -61,7 +61,7 @@ export class NestedDataManager { } }; - public enqueue = async (ids: GridRowId[]) => { + public queue = async (ids: GridRowId[]) => { ids.forEach((id) => { this.queuedRequests.add(id); this.api.unstable_dataSource.setChildrenLoading(id, true); From 655cc09cf072ec7057ae87760855709f0d40e108 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Tue, 11 Jun 2024 12:48:30 +0500 Subject: [PATCH 74/90] Move cache to data source hook --- .../server-side-data/ServerSideTreeData.js | 2 +- .../server-side-data/ServerSideTreeData.tsx | 2 +- .../ServerSideTreeData.tsx.preview | 2 +- .../ServerSideTreeDataGroupExpansion.js | 2 +- .../ServerSideTreeDataGroupExpansion.tsx | 2 +- .../useDataGridPremiumComponent.tsx | 2 - .../DataGridPro/useDataGridProComponent.tsx | 2 - .../hooks/features/dataSource/interfaces.ts | 14 ++---- .../features/dataSource/useGridDataSource.ts | 47 +++++++++++++------ .../dataSource/useGridDataSourceCache.ts | 44 ----------------- .../x-data-grid-pro/src/internals/index.ts | 1 - 11 files changed, 42 insertions(+), 78 deletions(-) delete mode 100644 packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.js b/docs/data/data-grid/server-side-data/ServerSideTreeData.js index a6d6f131581d..ed0d0bc8e093 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.js @@ -55,7 +55,7 @@ export default function ServerSideTreeData() { return (
-
diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx index c2c0caa249e4..86a2e47fd1ef 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx @@ -61,7 +61,7 @@ export default function ServerSideTreeData() { return (
-
diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview index 6b8c501d8da3..4a508197000b 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview @@ -1,4 +1,4 @@ -
diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js index d79fcfd5026b..9b61ceacdcf8 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js @@ -55,7 +55,7 @@ export default function ServerSideTreeDataGroupExpansion() { return (
-
diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx index dc063e38b96c..9eb7fdd75ba4 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx @@ -63,7 +63,7 @@ export default function ServerSideTreeDataGroupExpansion() { return (
-
diff --git a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx index 07bccdb92c87..0359345cf3f6 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx @@ -68,7 +68,6 @@ import { useGridServerSideTreeDataPreProcessors, useGridDataSource, dataSourceStateInitializer, - useGridDataSourceCache, } from '@mui/x-data-grid-pro/internals'; import { GridApiPremium, GridPrivateApiPremium } from '../models/gridApiPremium'; import { DataGridPremiumProcessedProps } from '../models/dataGridPremiumProps'; @@ -181,7 +180,6 @@ export const useDataGridPremiumComponent = ( useGridEvents(apiRef, props); useGridStatePersistence(apiRef); useGridDataSource(apiRef, props); - useGridDataSourceCache(apiRef, props); useGridVirtualization(apiRef, props); return apiRef; diff --git a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx index 8ab0846c7dcf..40b4ab8ab108 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx @@ -82,7 +82,6 @@ import { useGridDataSource, dataSourceStateInitializer, } from '../hooks/features/dataSource/useGridDataSource'; -import { useGridDataSourceCache } from '../hooks/features/dataSource/useGridDataSourceCache'; export const useDataGridProComponent = ( inputApiRef: React.MutableRefObject | undefined, @@ -166,7 +165,6 @@ export const useDataGridProComponent = ( useGridStatePersistence(apiRef); useGridVirtualization(apiRef, props); useGridDataSource(apiRef, props); - useGridDataSourceCache(apiRef, props); return apiRef; }; 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 46683a3da061..37b8586d0308 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 @@ -28,6 +28,10 @@ export interface GridDataSourceApiBase { * @param {string} parentId The id of the group to be fetched. */ fetchRows: (parentId?: GridRowId) => void; + /** + * The data source cache object. + */ + cache: GridDataSourceCache; } export interface GridDataSourceApi { @@ -44,13 +48,3 @@ export interface GridDataSourcePrivateApi { */ resetDataSourceState: () => void; } - -/** - * The data source cache API interface that is available in the grid [[apiRef]]. - */ -export interface GridDataSourceCacheApi { - /** - * The data source cache object. - */ - unstable_dataSourceCache: GridDataSourceCache; -} 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 e31b50fb053f..23c913d9fced 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,12 +14,20 @@ import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; import { gridGetRowsParamsSelector, gridDataSourceErrorsSelector } from './gridDataSourceSelector'; import { GridDataSourceApi, GridDataSourceApiBase, GridDataSourcePrivateApi } from './interfaces'; import { runIfServerMode, NestedDataManager, RequestStatus } from './utils'; +import { GridDataSourceCache } from '../../../models'; +import { GridDataSourceCacheDefault } from './cache'; const INITIAL_STATE = { loading: {}, errors: {}, }; +const noopCache = { + clear: () => {}, + get: () => undefined, + set: () => {}, +}; + export const dataSourceStateInitializer: GridStateInitializer = (state) => { return { ...state, @@ -32,14 +40,12 @@ export const useGridDataSource = ( props: Pick< DataGridProProcessedProps, | 'unstable_dataSource' + | 'unstable_dataSourceCache' | 'unstable_onDataSourceError' | 'sortingMode' | 'filterMode' | 'paginationMode' | 'treeData' - | 'getRowId' - | 'loading' - | 'rowCount' >, ) => { const nestedDataManager = useLazyRef( @@ -49,6 +55,13 @@ export const useGridDataSource = ( const scheduledGroups = React.useRef(0); const onError = props.unstable_onDataSourceError; + const [cache, setCache] = React.useState(() => { + if (props.unstable_dataSourceCache !== undefined) { + return props.unstable_dataSourceCache; + } + return new GridDataSourceCacheDefault({}); + }); + const fetchRows = React.useCallback( async (parentId?: GridRowId) => { const getRows = props.unstable_dataSource?.getRows; @@ -70,7 +83,7 @@ export const useGridDataSource = ( const fetchParams = gridGetRowsParamsSelector(apiRef); - const cachedData = apiRef.current.unstable_dataSourceCache.get(fetchParams); + const cachedData = apiRef.current.unstable_dataSource.cache.get(fetchParams); if (cachedData !== undefined) { const rows = cachedData.rows; @@ -88,7 +101,7 @@ export const useGridDataSource = ( try { const getRowsResponse = await getRows(fetchParams); - apiRef.current.unstable_dataSourceCache.set(fetchParams, getRowsResponse); + apiRef.current.unstable_dataSource.cache.set(fetchParams, getRowsResponse); if (getRowsResponse.rowCount) { apiRef.current.setRowCount(getRowsResponse.rowCount); } @@ -123,7 +136,7 @@ export const useGridDataSource = ( const fetchParams = { ...gridGetRowsParamsSelector(apiRef), groupKeys: rowNode.path }; - const cachedData = apiRef.current.unstable_dataSourceCache.get(fetchParams); + const cachedData = apiRef.current.unstable_dataSource.cache.get(fetchParams); if (cachedData !== undefined) { const rows = cachedData.rows; @@ -154,7 +167,7 @@ export const useGridDataSource = ( return; } nestedDataManager.setRequestSettled(id); - apiRef.current.unstable_dataSourceCache.set(fetchParams, getRowsResponse); + apiRef.current.unstable_dataSource.cache.set(fetchParams, getRowsResponse); if (getRowsResponse.rowCount) { apiRef.current.setRowCount(getRowsResponse.rowCount); } @@ -216,6 +229,7 @@ export const useGridDataSource = ( setChildrenLoading, setChildrenFetchError, fetchRows, + cache: cache ?? noopCache, }, }; @@ -227,9 +241,6 @@ export const useGridDataSource = ( useGridApiMethod(apiRef, dataSourceApi, 'public'); useGridApiMethod(apiRef, dataSourcePrivateApi, 'private'); - /* - * EVENTS - */ useGridApiEventHandler(apiRef, 'sortModelChange', runIfServerMode(props.sortingMode, fetchRows)); useGridApiEventHandler(apiRef, 'filterModelChange', runIfServerMode(props.filterMode, fetchRows)); useGridApiEventHandler( @@ -238,12 +249,20 @@ export const useGridDataSource = ( runIfServerMode(props.paginationMode, fetchRows), ); - /* - * EFFECTS - */ + const isFirstRender = React.useRef(true); + React.useEffect(() => { + if (isFirstRender.current) { + isFirstRender.current = false; + return; + } + if (props.unstable_dataSourceCache !== undefined) { + setCache(props.unstable_dataSourceCache); + } + }, [props.unstable_dataSourceCache]); + React.useEffect(() => { if (props.unstable_dataSource) { - apiRef.current.unstable_dataSourceCache.clear(); + apiRef.current.unstable_dataSource.cache.clear(); apiRef.current.unstable_dataSource.fetchRows(); } }, [apiRef, props.unstable_dataSource]); diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts deleted file mode 100644 index 09dfbfd0d290..000000000000 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceCache.ts +++ /dev/null @@ -1,44 +0,0 @@ -import * as React from 'react'; -import useLazyRef from '@mui/utils/useLazyRef'; -import { useGridApiMethod } from '@mui/x-data-grid'; -import { GridPrivateApiPro } from '../../../models/gridApiPro'; -import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; -import { GridDataSourceCache } from '../../../models'; -import { GridDataSourceCacheApi } from './interfaces'; -import { GridDataSourceCacheDefault } from './cache'; - -const noopCache = { - clear: () => {}, - get: () => undefined, - set: () => {}, -}; - -export const useGridDataSourceCache = ( - apiRef: React.MutableRefObject, - props: Pick, -): void => { - const defaultCache = useLazyRef( - () => new GridDataSourceCacheDefault({}), - ).current; - - const [cache, setCache] = React.useState( - props.unstable_dataSourceCache !== undefined ? props.unstable_dataSourceCache : defaultCache, - ); - - const dataSourceCacheApi: GridDataSourceCacheApi = { - unstable_dataSourceCache: cache ?? noopCache, - }; - - useGridApiMethod(apiRef, dataSourceCacheApi, 'public'); - - const isFirstRender = React.useRef(true); - React.useEffect(() => { - if (isFirstRender.current) { - isFirstRender.current = false; - return; - } - if (props.unstable_dataSourceCache !== undefined) { - setCache(props.unstable_dataSourceCache); - } - }, [props.unstable_dataSourceCache]); -}; diff --git a/packages/x-data-grid-pro/src/internals/index.ts b/packages/x-data-grid-pro/src/internals/index.ts index c33c90fb441b..bccf1b41bfea 100644 --- a/packages/x-data-grid-pro/src/internals/index.ts +++ b/packages/x-data-grid-pro/src/internals/index.ts @@ -41,7 +41,6 @@ export { useGridDataSource, dataSourceStateInitializer, } from '../hooks/features/dataSource/useGridDataSource'; -export { useGridDataSourceCache } from '../hooks/features/dataSource/useGridDataSourceCache'; export type { GridExperimentalProFeatures, From f304ca5ce1e9c8556ba30ea0bbb0a3a7329b3c78 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Tue, 11 Jun 2024 12:55:49 +0500 Subject: [PATCH 75/90] Add comment --- .../src/hooks/features/dataSource/interfaces.ts | 3 +++ 1 file changed, 3 insertions(+) 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 37b8586d0308..90bfc4ed39de 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 @@ -35,6 +35,9 @@ export interface GridDataSourceApiBase { } export interface GridDataSourceApi { + /** + * The data source API. + */ unstable_dataSource: GridDataSourceApiBase; } export interface GridDataSourcePrivateApi { From 957c1f3453601784a005326b19fbcc4f0f5e7cd2 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Tue, 11 Jun 2024 13:26:15 +0500 Subject: [PATCH 76/90] Make the CI happy --- docs/pages/x/api/data-grid/grid-api.json | 5 ----- docs/translations/api-docs/data-grid/grid-api.json | 3 +-- packages/x-data-grid-premium/src/models/gridApiPremium.ts | 2 -- packages/x-data-grid-pro/src/models/gridApiPro.ts | 2 -- scripts/x-data-grid-premium.exports.json | 1 - scripts/x-data-grid-pro.exports.json | 1 - 6 files changed, 1 insertion(+), 13 deletions(-) diff --git a/docs/pages/x/api/data-grid/grid-api.json b/docs/pages/x/api/data-grid/grid-api.json index 1d83c6b9a812..7e4134bede6f 100644 --- a/docs/pages/x/api/data-grid/grid-api.json +++ b/docs/pages/x/api/data-grid/grid-api.json @@ -466,11 +466,6 @@ "required": true, "isProPlan": true }, - "unstable_dataSourceCache": { - "type": { "description": "GridDataSourceCache | null" }, - "required": true, - "isProPlan": true - }, "unstable_replaceRows": { "type": { "description": "(firstRowToReplace: number, newRows: GridRowModel[]) => void" }, "required": true diff --git a/docs/translations/api-docs/data-grid/grid-api.json b/docs/translations/api-docs/data-grid/grid-api.json index 0e4ab032ce80..3529a583500b 100644 --- a/docs/translations/api-docs/data-grid/grid-api.json +++ b/docs/translations/api-docs/data-grid/grid-api.json @@ -241,8 +241,7 @@ }, "toggleDetailPanel": { "description": "Expands or collapses the detail panel of a row." }, "unpinColumn": { "description": "Unpins a column." }, - "unstable_dataSource": { "description": "" }, - "unstable_dataSourceCache": { "description": "The data source cache object." }, + "unstable_dataSource": { "description": "The data source API." }, "unstable_replaceRows": { "description": "Replace a set of rows with new rows." }, "unstable_setColumnVirtualization": { "description": "Enable/disable column virtualization." }, "unstable_setPinnedRows": { "description": "Changes the pinned rows." }, diff --git a/packages/x-data-grid-premium/src/models/gridApiPremium.ts b/packages/x-data-grid-premium/src/models/gridApiPremium.ts index 063b21dc6718..999a16685997 100644 --- a/packages/x-data-grid-premium/src/models/gridApiPremium.ts +++ b/packages/x-data-grid-premium/src/models/gridApiPremium.ts @@ -9,7 +9,6 @@ import { GridColumnReorderApi, GridRowProApi, GridDataSourceApi, - GridDataSourceCacheApi, GridDataSourcePrivateApi, } from '@mui/x-data-grid-pro'; import { GridInitialStatePremium, GridStatePremium } from './gridStatePremium'; @@ -31,7 +30,6 @@ export interface GridApiPremium GridAggregationApi, GridRowPinningApi, GridDataSourceApi, - GridDataSourceCacheApi, GridCellSelectionApi, // APIs that are private in Community plan, but public in Pro and Premium plans GridRowMultiSelectionApi, diff --git a/packages/x-data-grid-pro/src/models/gridApiPro.ts b/packages/x-data-grid-pro/src/models/gridApiPro.ts index db3ddac8907a..0f0f56261ef5 100644 --- a/packages/x-data-grid-pro/src/models/gridApiPro.ts +++ b/packages/x-data-grid-pro/src/models/gridApiPro.ts @@ -13,7 +13,6 @@ import type { GridDetailPanelPrivateApi, GridDataSourceApi, GridDataSourcePrivateApi, - GridDataSourceCacheApi, } from '../hooks'; import type { DataGridProProcessedProps } from './dataGridProProps'; @@ -27,7 +26,6 @@ export interface GridApiPro GridDetailPanelApi, GridRowPinningApi, GridDataSourceApi, - GridDataSourceCacheApi, // APIs that are private in Community plan, but public in Pro and Premium plans GridRowMultiSelectionApi, GridColumnReorderApi {} diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 9f52f541bca9..4c72721220e1 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -248,7 +248,6 @@ { "name": "GridDataSourceApi", "kind": "Interface" }, { "name": "GridDataSourceApiBase", "kind": "Interface" }, { "name": "GridDataSourceCache", "kind": "Interface" }, - { "name": "GridDataSourceCacheApi", "kind": "Interface" }, { "name": "GridDataSourceCacheDefault", "kind": "Class" }, { "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 2678c6e54cdc..e9964a65e4d9 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -222,7 +222,6 @@ { "name": "GridDataSourceApi", "kind": "Interface" }, { "name": "GridDataSourceApiBase", "kind": "Interface" }, { "name": "GridDataSourceCache", "kind": "Interface" }, - { "name": "GridDataSourceCacheApi", "kind": "Interface" }, { "name": "GridDataSourceCacheDefault", "kind": "Class" }, { "name": "GridDataSourcePrivateApi", "kind": "Interface" }, { "name": "GridDataSourceState", "kind": "Interface" }, From 35b221c8687526504b083abfcb6d63cae6069946 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Tue, 11 Jun 2024 17:25:54 +0500 Subject: [PATCH 77/90] State update optimization --- .../src/hooks/features/dataSource/utils.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) 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 f3a6c1e94fb0..dafc6d9783f2 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 @@ -62,10 +62,21 @@ export class NestedDataManager { }; public queue = async (ids: GridRowId[]) => { + const loadingIds: Record = {}; ids.forEach((id) => { this.queuedRequests.add(id); - this.api.unstable_dataSource.setChildrenLoading(id, true); + loadingIds[id] = true; }); + this.api.setState((state) => ({ + ...state, + dataSource: { + ...state.dataSource, + loading: { + ...state.dataSource.loading, + ...loadingIds, + }, + }, + })); this.processQueue(); }; From f8f680d6271569114671ce70255dc7068faf307f Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Tue, 11 Jun 2024 18:59:02 +0500 Subject: [PATCH 78/90] Memory optimization --- .../src/hooks/features/dataSource/useGridDataSource.ts | 8 +++++++- 1 file changed, 7 insertions(+), 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 23c913d9fced..cbb93ff95711 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 @@ -203,11 +203,17 @@ export const useGridDataSource = ( const setChildrenFetchError = React.useCallback( (parentId, error) => { apiRef.current.setState((state) => { + const newErrorsState = { ...state.dataSource.errors }; + if (error === null && newErrorsState[parentId] !== undefined) { + delete newErrorsState[parentId]; + } else { + newErrorsState[parentId] = error; + } return { ...state, dataSource: { ...state.dataSource, - errors: { ...state.dataSource.errors, [parentId]: error }, + errors: newErrorsState, }, }; }); From c5e69bdae977c9a04d29b53ba0b1ce31b6e2a5f7 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Tue, 11 Jun 2024 19:18:38 +0500 Subject: [PATCH 79/90] Docs improvement --- docs/data/data-grid/server-side-data/index.md | 20 +++++++++++++------ .../data-grid/server-side-data/tree-data.md | 3 ++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index 6359f9e3e4b7..093f0b78df31 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -8,9 +8,13 @@ title: React Data Grid - Server-side data ## Introduction -Server-side data management in React can become complex with growing datasets. Challenges include manual data fetching, pagination, sorting, filtering, and performance optimization. A dedicated module can help abstract these complexities, improving user experience. +Server-side data management in React can become complex with growing datasets. +Challenges include manual data fetching, pagination, sorting, filtering, and performance optimization. +A dedicated module can help abstract these complexities, improving user experience. -Consider a Data Grid displaying a list of users. It supports pagination, sorting by column headers, and filtering. The Data Grid fetches data from the server when the user changes the page or updates filtering or sorting. +Consider a Data Grid displaying a list of users. +It supports pagination, sorting by column headers, and filtering. +The Data Grid fetches data from the server when the user changes the page or updates filtering or sorting. ```tsx const [rows, setRows] = React.useState([]); @@ -128,7 +132,8 @@ The data source changes how the existing server-side features like `filtering`, **Without data source** -When there's no data source, the features `filtering`, `sorting`, `pagination` work on `client` by default. In order for them to work with server-side data, you need to set them to `server` explicitly and provide the [`onFilterModelChange`](https://mui.com/x/react-data-grid/filtering/server-side/), [`onSortModelChange`](https://mui.com/x/react-data-grid/sorting/#server-side-sorting), [`onPaginationModelChange`](https://mui.com/x/react-data-grid/pagination/#server-side-pagination) event handlers to fetch the data from the server based on the updated variables. +When there's no data source, the features `filtering`, `sorting`, `pagination` work on `client` by default. +In order for them to work with server-side data, you need to set them to `server` explicitly and provide the [`onFilterModelChange`](https://mui.com/x/react-data-grid/filtering/server-side/), [`onSortModelChange`](https://mui.com/x/react-data-grid/sorting/#server-side-sorting), [`onPaginationModelChange`](https://mui.com/x/react-data-grid/pagination/#server-side-pagination) event handlers to fetch the data from the server based on the updated variables. ```tsx Date: Wed, 12 Jun 2024 00:23:14 +0500 Subject: [PATCH 80/90] Stop preserving null in cache state --- .../features/dataSource/useGridDataSource.ts | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 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 cbb93ff95711..f2b37bd9e20f 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 @@ -22,12 +22,19 @@ const INITIAL_STATE = { errors: {}, }; -const noopCache = { +const noopCache: GridDataSourceCache = { clear: () => {}, get: () => undefined, set: () => {}, }; +function getCache(cacheProp?: GridDataSourceCache | null) { + if (cacheProp === null) { + return noopCache; + } + return cacheProp ?? new GridDataSourceCacheDefault({}); +} + export const dataSourceStateInitializer: GridStateInitializer = (state) => { return { ...state, @@ -55,12 +62,9 @@ export const useGridDataSource = ( const scheduledGroups = React.useRef(0); const onError = props.unstable_onDataSourceError; - const [cache, setCache] = React.useState(() => { - if (props.unstable_dataSourceCache !== undefined) { - return props.unstable_dataSourceCache; - } - return new GridDataSourceCacheDefault({}); - }); + const [cache, setCache] = React.useState(() => + getCache(props.unstable_dataSourceCache), + ); const fetchRows = React.useCallback( async (parentId?: GridRowId) => { @@ -235,7 +239,7 @@ export const useGridDataSource = ( setChildrenLoading, setChildrenFetchError, fetchRows, - cache: cache ?? noopCache, + cache, }, }; @@ -261,9 +265,8 @@ export const useGridDataSource = ( isFirstRender.current = false; return; } - if (props.unstable_dataSourceCache !== undefined) { - setCache(props.unstable_dataSourceCache); - } + const newCache = getCache(props.unstable_dataSourceCache); + setCache((prevCache) => (prevCache !== newCache ? newCache : prevCache)); }, [props.unstable_dataSourceCache]); React.useEffect(() => { From c240bafcee18a0cef6ece04b26038f14f28ec1bb Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Thu, 13 Jun 2024 13:59:47 +0500 Subject: [PATCH 81/90] Remove redundant condition --- .../src/components/GridServerSideTreeDataGroupingCell.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx b/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx index fdf5d46caa5f..9c834bdd8f71 100644 --- a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx +++ b/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx @@ -55,8 +55,6 @@ function GridTreeDataGroupingCellIcon(props: GridTreeDataGroupingCellIconProps) const isDataLoading = useGridSelector(apiRef, loadingSelector); const error = useGridSelector(apiRef, errorSelector); - const hasServerChildren = rowNode.hasServerChildren; - const handleClick = (event: React.MouseEvent) => { if (!rowNode.childrenExpanded) { // always fetch/get from cache the children when the node is expanded @@ -79,7 +77,7 @@ function GridTreeDataGroupingCellIcon(props: GridTreeDataGroupingCellIconProps)
); } - return descendantCount > 0 || hasServerChildren ? ( + return descendantCount > 0 ? ( Date: Thu, 13 Jun 2024 14:22:23 +0500 Subject: [PATCH 82/90] Improve comment --- packages/x-data-grid/src/models/api/gridRowApi.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-data-grid/src/models/api/gridRowApi.ts b/packages/x-data-grid/src/models/api/gridRowApi.ts index d0b6a0663cdc..855188d9a7fb 100644 --- a/packages/x-data-grid/src/models/api/gridRowApi.ts +++ b/packages/x-data-grid/src/models/api/gridRowApi.ts @@ -50,7 +50,7 @@ export interface GridRowApi { getAllRowIds: () => GridRowId[]; /** * Sets the internal loading state. - * @param {boolean} loading The new rows. + * @param {boolean} loading If `true` the loading indicator will be shown over the Data Grid. */ setLoading: (loading: boolean) => void; /** From a022d5461e58dd4588769d161e474050c1a5f8c8 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Thu, 13 Jun 2024 14:22:54 +0500 Subject: [PATCH 83/90] Loading state optimization --- .../hooks/features/dataSource/useGridDataSource.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 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 f2b37bd9e20f..92fafd448d07 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 @@ -192,11 +192,20 @@ export const useGridDataSource = ( const setChildrenLoading = React.useCallback( (parentId, isLoading) => { apiRef.current.setState((state) => { + if (!state.dataSource.loading[parentId] && isLoading === false) { + return state; + } + const newLoadingState = { ...state.dataSource.loading }; + if (isLoading === false) { + delete newLoadingState[parentId]; + } else { + newLoadingState[parentId] = isLoading; + } return { ...state, dataSource: { ...state.dataSource, - loading: { ...state.dataSource.loading, [parentId]: isLoading }, + loading: newLoadingState, }, }; }); From 6676357e140eba12e3530f770ee27191f099fd3c Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Thu, 13 Jun 2024 23:51:04 +0500 Subject: [PATCH 84/90] Get rid of the swr demo --- .../ServerSideDataGridWithSWR.js | 83 ----------------- .../ServerSideDataGridWithSWR.tsx | 88 ------------------- docs/data/data-grid/server-side-data/index.md | 4 - docs/package.json | 1 - pnpm-lock.yaml | 53 ++++------- 5 files changed, 16 insertions(+), 213 deletions(-) delete mode 100644 docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js delete mode 100644 docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js deleted file mode 100644 index 6cee45d75169..000000000000 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.js +++ /dev/null @@ -1,83 +0,0 @@ -import * as React from 'react'; -import { DataGridPro } from '@mui/x-data-grid-pro'; -import { useMockServer } from '@mui/x-data-grid-generator'; -import { useSWRConfig } from 'swr'; - -const serverOptions = { useCursorPagination: false }; -const dataSetOptions = {}; - -function ServerSideDataGridWithSWR() { - const { fetchRows, columns, initialState } = useMockServer( - dataSetOptions, - serverOptions, - ); - - const dataSource = React.useMemo( - () => ({ - getRows: async (params) => { - const urlParams = new URLSearchParams({ - paginationModel: encodeURIComponent( - JSON.stringify(params.paginationModel), - ), - filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), - sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), - }); - const getRowsResponse = await fetchRows( - `https://mui.com/x/api/data-grid?${urlParams.toString()}`, - ); - return { - rows: getRowsResponse.rows, - rowCount: getRowsResponse.rowCount, - }; - }, - }), - [fetchRows], - ); - - const { cache: swrCache } = useSWRConfig(); - - const cache = React.useMemo( - () => ({ - set: (key, value) => { - const keyString = JSON.stringify(key); - swrCache.set(keyString, { data: value }); - }, - get: (key) => { - const keyString = JSON.stringify(key); - return swrCache.get(keyString)?.data; - }, - clear: () => { - const keys = swrCache.keys(); - Array.from(keys).forEach((key) => { - swrCache.delete(key); - }); - }, - }), - [swrCache], - ); - - const initialStateWithPagination = React.useMemo( - () => ({ - ...initialState, - pagination: { - paginationModel: { pageSize: 10, page: 0 }, - }, - }), - [initialState], - ); - - return ( -
- -
- ); -} - -export default ServerSideDataGridWithSWR; diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx deleted file mode 100644 index 7e6a58b130f6..000000000000 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridWithSWR.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import * as React from 'react'; -import { - DataGridPro, - GridGetRowsParams, - GridGetRowsResponse, - GridDataSource, -} from '@mui/x-data-grid-pro'; -import { useMockServer } from '@mui/x-data-grid-generator'; -import { useSWRConfig } from 'swr'; - -const serverOptions = { useCursorPagination: false }; -const dataSetOptions = {}; - -function ServerSideDataGridWithSWR() { - const { fetchRows, columns, initialState } = useMockServer( - dataSetOptions, - serverOptions, - ); - - const dataSource: GridDataSource = React.useMemo( - () => ({ - getRows: async (params) => { - const urlParams = new URLSearchParams({ - paginationModel: encodeURIComponent( - JSON.stringify(params.paginationModel), - ), - filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), - sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), - }); - const getRowsResponse = await fetchRows( - `https://mui.com/x/api/data-grid?${urlParams.toString()}`, - ); - return { - rows: getRowsResponse.rows, - rowCount: getRowsResponse.rowCount, - }; - }, - }), - [fetchRows], - ); - - const { cache: swrCache } = useSWRConfig(); - - const cache = React.useMemo( - () => ({ - set: (key: GridGetRowsParams, value: GridGetRowsResponse) => { - const keyString = JSON.stringify(key); - swrCache.set(keyString, { data: value }); - }, - get: (key: GridGetRowsParams) => { - const keyString = JSON.stringify(key); - return swrCache.get(keyString)?.data; - }, - clear: () => { - const keys = swrCache.keys(); - Array.from(keys).forEach((key) => { - swrCache.delete(key); - }); - }, - }), - [swrCache], - ); - - const initialStateWithPagination = React.useMemo( - () => ({ - ...initialState, - pagination: { - paginationModel: { pageSize: 10, page: 0 }, - }, - }), - [initialState], - ); - - return ( -
- -
- ); -} - -export default ServerSideDataGridWithSWR; diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index 093f0b78df31..897727c3fbac 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -217,10 +217,6 @@ export interface GridDataSourceCache { } ``` -The following demo uses cache used by a popular library [`swr`](https://github.com/vercel/swr) to cache the server-side data. - -{{"demo": "ServerSideDataGridWithSWR.js", "bg": "inline"}} - ### Disable cache To disable the data source cache, pass `null` to the `unstable_dataSourceCache` prop. diff --git a/docs/package.json b/docs/package.json index 7bac5c142b87..4c3600abf211 100644 --- a/docs/package.json +++ b/docs/package.json @@ -94,7 +94,6 @@ "styled-components": "^6.1.11", "stylis": "^4.3.2", "stylis-plugin-rtl": "^2.1.1", - "swr": "^2.2.5", "webpack-bundle-analyzer": "^4.10.2" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0c778fce4c0a..440113228b11 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -623,9 +623,6 @@ importers: stylis-plugin-rtl: specifier: ^2.1.1 version: 2.1.1(stylis@4.3.2) - swr: - specifier: ^2.2.5 - version: 2.2.5(react@18.2.0) webpack-bundle-analyzer: specifier: ^4.10.2 version: 4.10.2 @@ -674,7 +671,7 @@ importers: version: 4.2.6 '@types/webpack-bundle-analyzer': specifier: ^4.7.0 - version: 4.7.0(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.91.0)) + version: 4.7.0 gm: specifier: ^1.25.0 version: 1.25.0 @@ -9069,11 +9066,6 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - swr@2.2.5: - resolution: {integrity: sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==} - peerDependencies: - react: ^16.11.0 || ^17.0.0 || ^18.0.0 - symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -9458,11 +9450,6 @@ packages: urlpattern-polyfill@8.0.2: resolution: {integrity: sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==} - use-sync-external-store@1.2.2: - resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - use@3.1.1: resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==} engines: {node: '>=0.10.0'} @@ -9956,7 +9943,7 @@ snapshots: '@babel/traverse': 7.24.6 '@babel/types': 7.24.6 convert-source-map: 2.0.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -10011,7 +9998,7 @@ snapshots: '@babel/core': 7.24.6 '@babel/helper-compilation-targets': 7.24.6 '@babel/helper-plugin-utils': 7.24.6 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: @@ -10771,7 +10758,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.6 '@babel/parser': 7.24.6 '@babel/types': 7.24.6 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -12449,11 +12436,11 @@ snapshots: '@types/unist@2.0.10': {} - '@types/webpack-bundle-analyzer@4.7.0(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.91.0))': + '@types/webpack-bundle-analyzer@4.7.0': dependencies: '@types/node': 18.19.33 tapable: 2.2.1 - webpack: 5.91.0(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.91.0)) + webpack: 5.91.0 transitivePeerDependencies: - '@swc/core' - esbuild @@ -14031,6 +14018,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.3.4: + dependencies: + ms: 2.1.2 + debug@4.3.4(supports-color@8.1.1): dependencies: ms: 2.1.2 @@ -19017,12 +19008,6 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - swr@2.2.5(react@18.2.0): - dependencies: - client-only: 0.0.1 - react: 18.2.0 - use-sync-external-store: 1.2.2(react@18.2.0) - symbol-tree@3.2.4: {} synckit@0.8.8: @@ -19076,23 +19061,23 @@ snapshots: dependencies: rimraf: 2.6.3 - terser-webpack-plugin@5.3.10(webpack@5.91.0(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.91.0))): + terser-webpack-plugin@5.3.10(webpack@5.91.0(webpack-cli@5.1.4)): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 terser: 5.27.0 - webpack: 5.91.0(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.91.0)) + webpack: 5.91.0(webpack-cli@5.1.4) - terser-webpack-plugin@5.3.10(webpack@5.91.0(webpack-cli@5.1.4)): + terser-webpack-plugin@5.3.10(webpack@5.91.0): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 terser: 5.27.0 - webpack: 5.91.0(webpack-cli@5.1.4) + webpack: 5.91.0 terser@5.27.0: dependencies: @@ -19436,10 +19421,6 @@ snapshots: urlpattern-polyfill@8.0.2: {} - use-sync-external-store@1.2.2(react@18.2.0): - dependencies: - react: 18.2.0 - use@3.1.1: {} util-deprecate@1.0.2: {} @@ -19567,7 +19548,7 @@ snapshots: webpack-sources@3.2.3: {} - webpack@5.91.0(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.91.0)): + webpack@5.91.0: dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.5 @@ -19590,11 +19571,9 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(webpack@5.91.0(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.91.0))) + terser-webpack-plugin: 5.3.10(webpack@5.91.0) watchpack: 2.4.1 webpack-sources: 3.2.3 - optionalDependencies: - webpack-cli: 5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.91.0) transitivePeerDependencies: - '@swc/core' - esbuild From 17acfc16fd287deaf9f3ac7ab55d00a1fac5b8b0 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Fri, 14 Jun 2024 02:50:10 +0500 Subject: [PATCH 85/90] Move updateServerRows to private --- docs/pages/x/api/data-grid/grid-api.json | 5 ----- docs/translations/api-docs/data-grid/grid-api.json | 3 --- .../x-data-grid/src/hooks/features/rows/useGridRows.ts | 8 ++++++-- packages/x-data-grid/src/models/api/gridApiCommon.ts | 5 +++-- packages/x-data-grid/src/models/api/gridRowApi.ts | 3 +++ scripts/x-data-grid-premium.exports.json | 1 + scripts/x-data-grid-pro.exports.json | 1 + scripts/x-data-grid.exports.json | 1 + 8 files changed, 15 insertions(+), 12 deletions(-) diff --git a/docs/pages/x/api/data-grid/grid-api.json b/docs/pages/x/api/data-grid/grid-api.json index 7e4134bede6f..c44683c1e213 100644 --- a/docs/pages/x/api/data-grid/grid-api.json +++ b/docs/pages/x/api/data-grid/grid-api.json @@ -491,11 +491,6 @@ "type": { "description": "(updates: GridRowModelUpdate[]) => void" }, "required": true }, - "updateServerRows": { - "type": { "description": "(updates: GridRowModelUpdate[], groupKeys?: string[]) => void" }, - "required": true, - "isProPlan": true - }, "upsertFilterItem": { "type": { "description": "(item: GridFilterItem) => void" }, "required": true diff --git a/docs/translations/api-docs/data-grid/grid-api.json b/docs/translations/api-docs/data-grid/grid-api.json index 3529a583500b..a4fb64608ca4 100644 --- a/docs/translations/api-docs/data-grid/grid-api.json +++ b/docs/translations/api-docs/data-grid/grid-api.json @@ -250,9 +250,6 @@ "description": "Updates the definition of multiple columns at the same time." }, "updateRows": { "description": "Allows to update, insert and delete rows." }, - "updateServerRows": { - "description": "Allows to update, insert and delete rows at a specific nested level." - }, "upsertFilterItem": { "description": "Updates or inserts a GridFilterItem." }, 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 6aab1acdb31a..2737b07ce94c 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts @@ -2,7 +2,7 @@ import * as React from 'react'; import { GridEventListener } from '../../../models/events'; import { DataGridProcessedProps } from '../../../models/props/DataGridProps'; import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity'; -import { GridRowApi, GridRowProApi } from '../../../models/api/gridRowApi'; +import { GridRowApi, GridRowProApi, GridRowProPrivateApi } from '../../../models/api/gridRowApi'; import { GridRowId, GridGroupNode, GridLeafNode } from '../../../models/gridRows'; import { useGridApiMethod } from '../../utils/useGridApiMethod'; import { useGridLogger } from '../../utils/useGridLogger'; @@ -215,7 +215,7 @@ export const useGridRows = ( [props.signature, props.getRowId, throttledRowsChange, apiRef], ); - const updateServerRows = React.useCallback( + const updateServerRows = React.useCallback( (updates, groupKeys) => { const nonPinnedRowsUpdates = computeRowsUpdates(apiRef, updates, props.getRowId); @@ -491,6 +491,9 @@ export const useGridRows = ( setRowIndex, setRowChildrenExpansion, getRowGroupChildren, + }; + + const rowProPrivateApi: GridRowProPrivateApi = { updateServerRows, }; @@ -594,6 +597,7 @@ export const useGridRows = ( rowProApi, props.signature === GridSignature.DataGrid ? 'private' : 'public', ); + useGridApiMethod(apiRef, rowProPrivateApi, 'private'); // The effect do not track any value defined synchronously during the 1st render by hooks called after `useGridRows` // As a consequence, the state generated by the 1st run of this useEffect will always be equal to the initialization one diff --git a/packages/x-data-grid/src/models/api/gridApiCommon.ts b/packages/x-data-grid/src/models/api/gridApiCommon.ts index 1e59d123c8a5..f5ba4a40faf7 100644 --- a/packages/x-data-grid/src/models/api/gridApiCommon.ts +++ b/packages/x-data-grid/src/models/api/gridApiCommon.ts @@ -10,7 +10,7 @@ import type { GridLocaleTextApi } from './gridLocaleTextApi'; import type { GridParamsApi } from './gridParamsApi'; import { GridPreferencesPanelApi } from './gridPreferencesPanelApi'; import { GridPrintExportApi } from './gridPrintExportApi'; -import { GridRowApi } from './gridRowApi'; +import { GridRowApi, GridRowProPrivateApi } from './gridRowApi'; import { GridRowsMetaApi, GridRowsMetaPrivateApi } from './gridRowsMetaApi'; import { GridRowSelectionApi } from './gridRowSelectionApi'; import { GridSortApi } from './gridSortApi'; @@ -82,7 +82,8 @@ export interface GridPrivateOnlyApiCommon< GridLoggerApi, GridFocusPrivateApi, GridHeaderFilteringPrivateApi, - GridVirtualizationPrivateApi {} + GridVirtualizationPrivateApi, + GridRowProPrivateApi {} export interface GridPrivateApiCommon extends GridApiCommon, diff --git a/packages/x-data-grid/src/models/api/gridRowApi.ts b/packages/x-data-grid/src/models/api/gridRowApi.ts index 855188d9a7fb..b54617e856a4 100644 --- a/packages/x-data-grid/src/models/api/gridRowApi.ts +++ b/packages/x-data-grid/src/models/api/gridRowApi.ts @@ -116,6 +116,9 @@ export interface GridRowProApi { * @param {boolean} isExpanded A boolean indicating if the row must be expanded or collapsed. */ setRowChildrenExpansion: (id: GridRowId, isExpanded: boolean) => void; +} + +export interface GridRowProPrivateApi { /** * Allows to update, insert and delete rows at a specific nested level. * @param {GridRowModelUpdate[]} updates An array of rows with an `action` specifying what to do. diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 4c72721220e1..759145ddd785 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -519,6 +519,7 @@ { "name": "GridRowPinningApi", "kind": "Interface" }, { "name": "GridRowPinningInternalCache", "kind": "Interface" }, { "name": "GridRowProApi", "kind": "Interface" }, + { "name": "GridRowProPrivateApi", "kind": "Interface" }, { "name": "GridRowProps", "kind": "Interface" }, { "name": "GridRowScrollEndParams", "kind": "Interface" }, { "name": "gridRowsDataRowIdToIdLookupSelector", "kind": "Variable" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index e9964a65e4d9..b8592ea03c0b 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -473,6 +473,7 @@ { "name": "GridRowPinningApi", "kind": "Interface" }, { "name": "GridRowPinningInternalCache", "kind": "Interface" }, { "name": "GridRowProApi", "kind": "Interface" }, + { "name": "GridRowProPrivateApi", "kind": "Interface" }, { "name": "GridRowProps", "kind": "Interface" }, { "name": "GridRowScrollEndParams", "kind": "Interface" }, { "name": "gridRowsDataRowIdToIdLookupSelector", "kind": "Variable" }, diff --git a/scripts/x-data-grid.exports.json b/scripts/x-data-grid.exports.json index 6de7387bd7c3..4ecff991f482 100644 --- a/scripts/x-data-grid.exports.json +++ b/scripts/x-data-grid.exports.json @@ -425,6 +425,7 @@ { "name": "GridRowMultiSelectionApi", "kind": "Interface" }, { "name": "GridRowParams", "kind": "Interface" }, { "name": "GridRowProApi", "kind": "Interface" }, + { "name": "GridRowProPrivateApi", "kind": "Interface" }, { "name": "GridRowProps", "kind": "Interface" }, { "name": "gridRowsDataRowIdToIdLookupSelector", "kind": "Variable" }, { "name": "GridRowSelectionApi", "kind": "Interface" }, From b3d43eda247dc74ad4293d6b0c9e47f17d946f91 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Wed, 19 Jun 2024 22:27:27 +0500 Subject: [PATCH 86/90] Rename everything to use DataSource --- .../DataGridPremium/useDataGridPremiumComponent.tsx | 4 ++-- .../src/DataGridPro/useDataGridProComponent.tsx | 4 ++-- ...Cell.tsx => GridDataSourceTreeDataGroupingCell.tsx} | 6 +++--- .../src/hooks/features/dataSource/useGridDataSource.ts | 4 ++-- ....tsx => useGridDataSourceTreeDataPreProcessors.tsx} | 10 +++++----- packages/x-data-grid-pro/src/internals/index.ts | 2 +- .../src/utils/tree/insertDataRowInTree.ts | 4 ++-- packages/x-data-grid/src/models/gridRows.ts | 2 +- scripts/x-data-grid-premium.exports.json | 2 +- scripts/x-data-grid-pro.exports.json | 2 +- scripts/x-data-grid.exports.json | 2 +- 11 files changed, 21 insertions(+), 21 deletions(-) rename packages/x-data-grid-pro/src/components/{GridServerSideTreeDataGroupingCell.tsx => GridDataSourceTreeDataGroupingCell.tsx} (96%) rename packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/{useGridServerSideTreeDataPreProcessors.tsx => useGridDataSourceTreeDataPreProcessors.tsx} (97%) diff --git a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx index 0359345cf3f6..b61e63a1f927 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx @@ -65,7 +65,7 @@ import { useGridHeaderFiltering, virtualizationStateInitializer, useGridVirtualization, - useGridServerSideTreeDataPreProcessors, + useGridDataSourceTreeDataPreProcessors, useGridDataSource, dataSourceStateInitializer, } from '@mui/x-data-grid-pro/internals'; @@ -102,7 +102,7 @@ export const useDataGridPremiumComponent = ( useGridRowReorderPreProcessors(apiRef, props); useGridRowGroupingPreProcessors(apiRef, props); useGridTreeDataPreProcessors(apiRef, props); - useGridServerSideTreeDataPreProcessors(apiRef, props); + useGridDataSourceTreeDataPreProcessors(apiRef, props); useGridLazyLoaderPreProcessors(apiRef, props); useGridRowPinningPreProcessors(apiRef); useGridAggregationPreProcessors(apiRef, props); diff --git a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx index 40b4ab8ab108..d902aa413bb6 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx @@ -58,7 +58,7 @@ import { } from '../hooks/features/columnReorder/useGridColumnReorder'; import { useGridTreeData } from '../hooks/features/treeData/useGridTreeData'; import { useGridTreeDataPreProcessors } from '../hooks/features/treeData/useGridTreeDataPreProcessors'; -import { useGridServerSideTreeDataPreProcessors } from '../hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors'; +import { useGridDataSourceTreeDataPreProcessors } from '../hooks/features/serverSideTreeData/useGridDataSourceTreeDataPreProcessors'; import { useGridColumnPinning, columnPinningStateInitializer, @@ -95,7 +95,7 @@ export const useDataGridProComponent = ( useGridRowSelectionPreProcessors(apiRef, props); useGridRowReorderPreProcessors(apiRef, props); useGridTreeDataPreProcessors(apiRef, props); - useGridServerSideTreeDataPreProcessors(apiRef, props); + useGridDataSourceTreeDataPreProcessors(apiRef, props); useGridLazyLoaderPreProcessors(apiRef, props); useGridRowPinningPreProcessors(apiRef); useGridDetailPanelPreProcessors(apiRef, props); diff --git a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx b/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx similarity index 96% rename from packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx rename to packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx index 9c834bdd8f71..d03c28e675c4 100644 --- a/packages/x-data-grid-pro/src/components/GridServerSideTreeDataGroupingCell.tsx +++ b/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx @@ -5,7 +5,7 @@ import Badge from '@mui/material/Badge'; import { getDataGridUtilityClass, GridRenderCellParams, - GridServerSideGroupNode, + GridDataSourceGroupNode, useGridSelector, } from '@mui/x-data-grid'; import CircularProgress from '@mui/material/CircularProgress'; @@ -30,7 +30,7 @@ const useUtilityClasses = (ownerState: OwnerState) => { }; interface GridTreeDataGroupingCellProps - extends GridRenderCellParams { + extends GridRenderCellParams { hideDescendantCount?: boolean; /** * The cell offset multiplier used for calculating cell offset (`rowNode.depth * offsetMultiplier` px). @@ -98,7 +98,7 @@ function GridTreeDataGroupingCellIcon(props: GridTreeDataGroupingCellIconProps) ) : null; } -export function GridServerSideTreeDataGroupingCell(props: GridTreeDataGroupingCellProps) { +export function GridDataSourceTreeDataGroupingCell(props: GridTreeDataGroupingCellProps) { const { id, field, formattedValue, rowNode, hideDescendantCount, offsetMultiplier = 2 } = props; const rootProps = useGridRootProps(); 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 92fafd448d07..b948c0a48745 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 @@ -4,7 +4,7 @@ import { useGridApiEventHandler, gridRowsLoadingSelector, useGridApiMethod, - GridServerSideGroupNode, + GridDataSourceGroupNode, useGridSelector, GridRowId, } from '@mui/x-data-grid'; @@ -132,7 +132,7 @@ export const useGridDataSource = ( return; } - const rowNode = apiRef.current.getRowNode(id); + const rowNode = apiRef.current.getRowNode(id); if (!rowNode) { nestedDataManager.clearPendingRequest(id); return; diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridDataSourceTreeDataPreProcessors.tsx similarity index 97% rename from packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx rename to packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridDataSourceTreeDataPreProcessors.tsx index 6216d9c94244..457bdb6c0431 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridDataSourceTreeDataPreProcessors.tsx @@ -4,7 +4,7 @@ import { useFirstRender, GridColDef, GridRenderCellParams, - GridServerSideGroupNode, + GridDataSourceGroupNode, GridRowId, GRID_CHECKBOX_SELECTION_FIELD, } from '@mui/x-data-grid'; @@ -26,7 +26,7 @@ import { GridGroupingColDefOverride, GridGroupingColDefOverrideParams, } from '../../../models/gridGroupingColDefOverride'; -import { GridServerSideTreeDataGroupingCell } from '../../../components/GridServerSideTreeDataGroupingCell'; +import { GridDataSourceTreeDataGroupingCell } from '../../../components/GridDataSourceTreeDataGroupingCell'; import { createRowTree } from '../../../utils/tree/createRowTree'; import { GridTreePathDuplicateHandler, @@ -37,7 +37,7 @@ import { getVisibleRowsLookup } from '../../../utils/tree/utils'; const SERVER_SIDE_TREE_DATA_STRATEGY = 'serverSideTreeData'; -export const useGridServerSideTreeDataPreProcessors = ( +export const useGridDataSourceTreeDataPreProcessors = ( privateApiRef: React.MutableRefObject, props: Pick< DataGridProProcessedProps, @@ -78,8 +78,8 @@ export const useGridServerSideTreeDataPreProcessors = ( const commonProperties: Omit = { ...GRID_TREE_DATA_GROUPING_COL_DEF, renderCell: (params) => ( - )} + )} hideDescendantCount={hideDescendantCount} /> ), diff --git a/packages/x-data-grid-pro/src/internals/index.ts b/packages/x-data-grid-pro/src/internals/index.ts index bccf1b41bfea..28b184e2bd42 100644 --- a/packages/x-data-grid-pro/src/internals/index.ts +++ b/packages/x-data-grid-pro/src/internals/index.ts @@ -15,7 +15,7 @@ export { useGridColumnReorder, columnReorderStateInitializer, } from '../hooks/features/columnReorder/useGridColumnReorder'; -export { useGridServerSideTreeDataPreProcessors } from '../hooks/features/serverSideTreeData/useGridServerSideTreeDataPreProcessors'; +export { useGridDataSourceTreeDataPreProcessors } from '../hooks/features/serverSideTreeData/useGridDataSourceTreeDataPreProcessors'; export { useGridDetailPanel, detailPanelStateInitializer, diff --git a/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts b/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts index c8d565e9d459..86cd27e02026 100644 --- a/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts +++ b/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts @@ -4,7 +4,7 @@ import { GridLeafNode, GridRowId, GridRowTreeConfig, - GridServerSideGroupNode, + GridDataSourceGroupNode, } from '@mui/x-data-grid'; import { GridTreeDepths, GridRowTreeUpdatedGroupsManager } from '@mui/x-data-grid/internals'; import { @@ -98,7 +98,7 @@ export const insertDataRowInTree = ({ // If no node matches the full path, // We create a leaf node for the data row. if (existingNodeIdWithPartialPath == null) { - let node: GridLeafNode | GridServerSideGroupNode; + let node: GridLeafNode | GridDataSourceGroupNode; if (hasServerChildren) { node = { type: 'group', diff --git a/packages/x-data-grid/src/models/gridRows.ts b/packages/x-data-grid/src/models/gridRows.ts index b826d35b03a6..703433dc873c 100644 --- a/packages/x-data-grid/src/models/gridRows.ts +++ b/packages/x-data-grid/src/models/gridRows.ts @@ -114,7 +114,7 @@ export interface GridDataGroupNode extends GridBasicGroupNode { isAutoGenerated: false; } -export interface GridServerSideGroupNode extends GridDataGroupNode { +export interface GridDataSourceGroupNode extends GridDataGroupNode { /** * If true, this node has children on server. */ diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 759145ddd785..197aa13bbc7b 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -249,6 +249,7 @@ { "name": "GridDataSourceApiBase", "kind": "Interface" }, { "name": "GridDataSourceCache", "kind": "Interface" }, { "name": "GridDataSourceCacheDefault", "kind": "Class" }, + { "name": "GridDataSourceGroupNode", "kind": "Interface" }, { "name": "GridDataSourcePrivateApi", "kind": "Interface" }, { "name": "GridDataSourceState", "kind": "Interface" }, { "name": "gridDateComparator", "kind": "Variable" }, @@ -547,7 +548,6 @@ { "name": "GridSearchIcon", "kind": "Variable" }, { "name": "GridSelectedRowCount", "kind": "Variable" }, { "name": "GridSeparatorIcon", "kind": "Variable" }, - { "name": "GridServerSideGroupNode", "kind": "Interface" }, { "name": "GridSignature", "kind": "Enum" }, { "name": "GridSingleSelectColDef", "kind": "Interface" }, { "name": "GridSkeletonCell", "kind": "Variable" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index b8592ea03c0b..ee604c2345b5 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -223,6 +223,7 @@ { "name": "GridDataSourceApiBase", "kind": "Interface" }, { "name": "GridDataSourceCache", "kind": "Interface" }, { "name": "GridDataSourceCacheDefault", "kind": "Class" }, + { "name": "GridDataSourceGroupNode", "kind": "Interface" }, { "name": "GridDataSourcePrivateApi", "kind": "Interface" }, { "name": "GridDataSourceState", "kind": "Interface" }, { "name": "gridDateComparator", "kind": "Variable" }, @@ -501,7 +502,6 @@ { "name": "GridSearchIcon", "kind": "Variable" }, { "name": "GridSelectedRowCount", "kind": "Variable" }, { "name": "GridSeparatorIcon", "kind": "Variable" }, - { "name": "GridServerSideGroupNode", "kind": "Interface" }, { "name": "GridSignature", "kind": "Enum" }, { "name": "GridSingleSelectColDef", "kind": "Interface" }, { "name": "GridSkeletonCell", "kind": "Variable" }, diff --git a/scripts/x-data-grid.exports.json b/scripts/x-data-grid.exports.json index 4ecff991f482..4e09f7f16770 100644 --- a/scripts/x-data-grid.exports.json +++ b/scripts/x-data-grid.exports.json @@ -204,6 +204,7 @@ { "name": "GridDataGroupNode", "kind": "Interface" }, { "name": "GridDataPinnedRowNode", "kind": "Interface" }, { "name": "gridDataRowIdsSelector", "kind": "Variable" }, + { "name": "GridDataSourceGroupNode", "kind": "Interface" }, { "name": "gridDateComparator", "kind": "Variable" }, { "name": "gridDateFormatter", "kind": "Variable" }, { "name": "gridDateTimeFormatter", "kind": "Variable" }, @@ -452,7 +453,6 @@ { "name": "GridSearchIcon", "kind": "Variable" }, { "name": "GridSelectedRowCount", "kind": "Variable" }, { "name": "GridSeparatorIcon", "kind": "Variable" }, - { "name": "GridServerSideGroupNode", "kind": "Interface" }, { "name": "GridSignature", "kind": "Enum" }, { "name": "GridSingleSelectColDef", "kind": "Interface" }, { "name": "GridSkeletonCell", "kind": "Variable" }, From c13d7d47cb5d114ba2f2190587122570927d2691 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Wed, 19 Jun 2024 22:35:42 +0500 Subject: [PATCH 87/90] Update a leftover --- .../useGridDataSourceTreeDataPreProcessors.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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 457bdb6c0431..6c182e6c04d1 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 @@ -35,7 +35,7 @@ import { import { updateRowTree } from '../../../utils/tree/updateRowTree'; import { getVisibleRowsLookup } from '../../../utils/tree/utils'; -const SERVER_SIDE_TREE_DATA_STRATEGY = 'serverSideTreeData'; +const DATA_SOURCE_TREE_DATA_STRATEGY = 'dataSourceTreeData'; export const useGridDataSourceTreeDataPreProcessors = ( privateApiRef: React.MutableRefObject, @@ -53,7 +53,7 @@ export const useGridDataSourceTreeDataPreProcessors = ( const setStrategyAvailability = React.useCallback(() => { privateApiRef.current.setStrategyAvailability( 'rowTree', - SERVER_SIDE_TREE_DATA_STRATEGY, + DATA_SOURCE_TREE_DATA_STRATEGY, props.treeData && props.unstable_dataSource ? () => true : () => false, ); }, [privateApiRef, props.treeData, props.unstable_dataSource]); @@ -64,7 +64,7 @@ export const useGridDataSourceTreeDataPreProcessors = ( let colDefOverride: GridGroupingColDefOverride | null | undefined; if (typeof groupingColDefProp === 'function') { const params: GridGroupingColDefOverrideParams = { - groupingName: SERVER_SIDE_TREE_DATA_STRATEGY, + groupingName: DATA_SOURCE_TREE_DATA_STRATEGY, fields: [], }; @@ -171,7 +171,7 @@ export const useGridDataSourceTreeDataPreProcessors = ( nodes: params.updates.rows.map(getRowTreeBuilderNode), defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, isGroupExpandedByDefault: props.isGroupExpandedByDefault, - groupingName: SERVER_SIDE_TREE_DATA_STRATEGY, + groupingName: DATA_SOURCE_TREE_DATA_STRATEGY, onDuplicatePath, }); } @@ -187,7 +187,7 @@ export const useGridDataSourceTreeDataPreProcessors = ( previousTreeDepth: params.previousTreeDepths!, defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, isGroupExpandedByDefault: props.isGroupExpandedByDefault, - groupingName: SERVER_SIDE_TREE_DATA_STRATEGY, + groupingName: DATA_SOURCE_TREE_DATA_STRATEGY, }); }, [ @@ -212,25 +212,25 @@ export const useGridDataSourceTreeDataPreProcessors = ( useGridRegisterPipeProcessor(privateApiRef, 'hydrateColumns', updateGroupingColumn); useGridRegisterStrategyProcessor( privateApiRef, - SERVER_SIDE_TREE_DATA_STRATEGY, + DATA_SOURCE_TREE_DATA_STRATEGY, 'rowTreeCreation', createRowTreeForTreeData, ); useGridRegisterStrategyProcessor( privateApiRef, - SERVER_SIDE_TREE_DATA_STRATEGY, + DATA_SOURCE_TREE_DATA_STRATEGY, 'filtering', filterRows, ); useGridRegisterStrategyProcessor( privateApiRef, - SERVER_SIDE_TREE_DATA_STRATEGY, + DATA_SOURCE_TREE_DATA_STRATEGY, 'sorting', sortRows, ); useGridRegisterStrategyProcessor( privateApiRef, - SERVER_SIDE_TREE_DATA_STRATEGY, + DATA_SOURCE_TREE_DATA_STRATEGY, 'visibleRowsLookupCreation', getVisibleRowsLookup, ); From 7cdec257beaae700933f4389835c3e43672d9e78 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Wed, 19 Jun 2024 22:49:48 +0500 Subject: [PATCH 88/90] lint --- packages/x-data-grid-generator/src/hooks/serverUtils.ts | 2 +- packages/x-data-grid-generator/src/hooks/useMockServer.ts | 2 +- 2 files changed, 2 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 2a901aa3aa1a..4796f5a9e4c4 100644 --- a/packages/x-data-grid-generator/src/hooks/serverUtils.ts +++ b/packages/x-data-grid-generator/src/hooks/serverUtils.ts @@ -153,7 +153,7 @@ const buildQuickFilterApplier = (filterModel: GridFilterModel, columns: GridColD shouldApplyFilter?: (field: string) => boolean, ) { const result = {} as Record; - /* eslint-disable no-restricted-syntax, no-labels */ + outer: for (let v = 0; v < quickFilterValues.length; v += 1) { const filterValue = quickFilterValues[v]; diff --git a/packages/x-data-grid-generator/src/hooks/useMockServer.ts b/packages/x-data-grid-generator/src/hooks/useMockServer.ts index 655e123e0e79..36a4abde2402 100644 --- a/packages/x-data-grid-generator/src/hooks/useMockServer.ts +++ b/packages/x-data-grid-generator/src/hooks/useMockServer.ts @@ -47,7 +47,7 @@ function decodeParams(url: string): GridGetRowsParams { const params = new URL(url).searchParams; const decodedParams = {} as any; const array = Array.from(params.entries()); - // eslint-disable-next-line no-restricted-syntax + for (const [key, value] of array) { try { decodedParams[key] = JSON.parse(decodeURIComponent(value)); From 1304eb4ca133a1f1f7c9d8627438b72447a18c16 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Tue, 25 Jun 2024 12:08:58 +0500 Subject: [PATCH 89/90] Lint --- packages/x-data-grid-generator/src/hooks/serverUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/x-data-grid-generator/src/hooks/serverUtils.ts b/packages/x-data-grid-generator/src/hooks/serverUtils.ts index 4796f5a9e4c4..095893126643 100644 --- a/packages/x-data-grid-generator/src/hooks/serverUtils.ts +++ b/packages/x-data-grid-generator/src/hooks/serverUtils.ts @@ -154,6 +154,7 @@ const buildQuickFilterApplier = (filterModel: GridFilterModel, columns: GridColD ) { const result = {} as Record; + /* eslint-disable no-labels */ outer: for (let v = 0; v < quickFilterValues.length; v += 1) { const filterValue = quickFilterValues[v]; @@ -181,7 +182,7 @@ const buildQuickFilterApplier = (filterModel: GridFilterModel, columns: GridColD result[filterValue] = false; } - /* eslint-enable no-restricted-syntax, no-labels */ + /* eslint-enable no-labels */ return result; }; From 71c37a353303a49e0037939ca2004f663b192ace Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Fri, 28 Jun 2024 15:38:56 +0500 Subject: [PATCH 90/90] Some final docs improvements --- docs/data/data-grid/server-side-data/index.md | 27 +++++++++++-------- docs/data/pages.ts | 2 +- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index 897727c3fbac..38a003c98581 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -74,13 +74,15 @@ Think of it like a middleman handling the communication between the Data Grid (c :::warning -This feature is under development and is marked as **unstable**. The information shared on this page could change in future. Feel free to subscribe or comment on the official GitHub [umbrella issue](https://github.com/mui/mui-x/issues/8179). +This feature is under development and is marked as **unstable**. +The information shared on this page could change in future. +Feel free to subscribe or comment on the official GitHub [umbrella issue](https://github.com/mui/mui-x/issues/8179). ::: It has an initial set of required methods that you need to implement. The data grid will use these methods internally to fetch a sub-set of data when needed. -Let's take a look at the `GridDataSource` interface. +Let's take a look at the minimal `GridDataSource` interface configuration. ```tsx interface GridDataSource { @@ -90,15 +92,16 @@ interface GridDataSource { * @returns {Promise} A promise that resolves to the data of type [GridGetRowsResponse] */ getRows(params: GridGetRowsParams): Promise; - /** - * This method will be called when the user updates a row [Not yet implemented] - * @param {GridRowModel} updatedRow The updated row - * @returns {Promise} If resolved (synced on the backend), the grid will update the row and mutate the cache - */ - updateRow?(updatedRow: GridRowModel): Promise; } ``` +:::info + +The above interface is a minimal configuration required for a data source to work. +More specific properties like `getChildrenCount` and `getGroupKey` will be discussed in the corresponding sections. + +::: + Here's how the above mentioned example would look like when implemented with the data source: ```tsx @@ -164,7 +167,7 @@ When the corresponding models update, the data grid calls the `getRows` method w ```tsx ``` @@ -173,14 +176,16 @@ 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 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. ::: ## Data caching -The data source caches fetched data by default. This means that if the user navigates to a page or expands a node that has already been fetched, the grid will not call the `getRows` function again to avoid unnecessary calls to the server. +The data source caches fetched data by default. +This means that if the user navigates to a page or expands a node that has already been fetched, the grid will not call the `getRows` function again to avoid unnecessary calls to the server. The `GridDataSourceCacheDefault` is used by default which is a simple in-memory cache that stores the data in a plain object. It could be seen in action in the demo below. diff --git a/docs/data/pages.ts b/docs/data/pages.ts index 57e5e17ebd88..cf3462208a94 100644 --- a/docs/data/pages.ts +++ b/docs/data/pages.ts @@ -118,6 +118,7 @@ const pages: MuiPage[] = [ plan: 'pro', children: [ { pathname: '/x/react-data-grid/server-side-data', title: 'Overview' }, + { pathname: '/x/react-data-grid/server-side-data/tree-data', plan: 'pro' }, { pathname: '/x/react-data-grid/server-side-data/lazy-loading', plan: 'pro', @@ -128,7 +129,6 @@ const pages: MuiPage[] = [ plan: 'pro', planned: true, }, - { pathname: '/x/react-data-grid/server-side-data/tree-data', plan: 'pro' }, { pathname: '/x/react-data-grid/server-side-data/row-grouping', plan: 'pro',