@mui/x-data-grid-pro 8.27.2 → 8.27.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +89 -0
- package/DataGridPro/DataGridPro.js +8 -1
- package/DataGridPro/useDataGridProProps.js +1 -0
- package/esm/DataGridPro/DataGridPro.js +8 -1
- package/esm/DataGridPro/useDataGridProProps.js +1 -0
- package/esm/hooks/features/dataSource/useGridDataSourceBasePro.d.ts +1 -1
- package/esm/hooks/features/dataSource/useGridDataSourceBasePro.js +44 -30
- package/esm/hooks/features/dataSource/utils.d.ts +3 -1
- package/esm/hooks/features/dataSource/utils.js +14 -7
- package/esm/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.d.ts +1 -1
- package/esm/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.js +176 -65
- package/esm/index.js +1 -1
- package/hooks/features/dataSource/useGridDataSourceBasePro.d.ts +1 -1
- package/hooks/features/dataSource/useGridDataSourceBasePro.js +43 -29
- package/hooks/features/dataSource/utils.d.ts +3 -1
- package/hooks/features/dataSource/utils.js +14 -7
- package/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.d.ts +1 -1
- package/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.js +176 -65
- package/index.js +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,95 @@
|
|
|
5
5
|
All notable changes to this project will be documented in this file.
|
|
6
6
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
7
7
|
|
|
8
|
+
## 8.27.4
|
|
9
|
+
|
|
10
|
+
_Mar 5, 2026_
|
|
11
|
+
|
|
12
|
+
We'd like to extend a big thank you to the 6 contributors who made this release possible. Here are some highlights ✨:
|
|
13
|
+
|
|
14
|
+
- 🐞 Bugfixes
|
|
15
|
+
- 🌎 Improve Ukrainian (uk-UA) locale
|
|
16
|
+
|
|
17
|
+
Special thanks go out to this community member for their valuable contribution:
|
|
18
|
+
@lion1963
|
|
19
|
+
|
|
20
|
+
The following team members contributed to this release:
|
|
21
|
+
@arminmeh, @brijeshb42, @dav-is, @JCQuintas, @sai6855
|
|
22
|
+
|
|
23
|
+
### Data Grid
|
|
24
|
+
|
|
25
|
+
#### `@mui/x-data-grid@8.27.4`
|
|
26
|
+
|
|
27
|
+
- [DataGrid] Prevent unnecessary row selection checkbox rerendering (#21571) @arminmeh
|
|
28
|
+
- [DataGrid] Make `GridScrollArea` overrides resolver dynamic (#21612) @sai6855
|
|
29
|
+
- [l10n] Improve Ukrainian (uk-UA) locale (#21381) @lion1963
|
|
30
|
+
|
|
31
|
+
#### `@mui/x-data-grid-pro@8.27.4` [](https://mui.com/r/x-pro-svg-link 'Pro plan')
|
|
32
|
+
|
|
33
|
+
Same changes as in `@mui/x-data-grid@8.27.4`, plus:
|
|
34
|
+
|
|
35
|
+
- [DataGridPro] Use `getRowId` prop to calculate the tree data row update (#21544) @arminmeh
|
|
36
|
+
|
|
37
|
+
#### `@mui/x-data-grid-premium@8.27.4` [](https://mui.com/r/x-premium-svg-link 'Premium plan')
|
|
38
|
+
|
|
39
|
+
Same changes as in `@mui/x-data-grid-pro@8.27.4`.
|
|
40
|
+
|
|
41
|
+
### Charts
|
|
42
|
+
|
|
43
|
+
#### `@mui/x-charts@8.27.4`
|
|
44
|
+
|
|
45
|
+
- [charts] Deprecate `ChartDataProvider` in favour of `ChartsDataProvider` (#21549) @JCQuintas
|
|
46
|
+
- [charts] Rename `ChartContainer` to `ChartsContainer` (#21186) @JCQuintas
|
|
47
|
+
- [charts] Rename `ChartZoomSlider` to `ChartsZoomSlider` (#21572) @JCQuintas
|
|
48
|
+
|
|
49
|
+
#### `@mui/x-charts-pro@8.27.4` [](https://mui.com/r/x-pro-svg-link 'Pro plan')
|
|
50
|
+
|
|
51
|
+
Same changes as in `@mui/x-charts@8.27.4`.
|
|
52
|
+
|
|
53
|
+
#### `@mui/x-charts-premium@8.27.4` [](https://mui.com/r/x-premium-svg-link 'Premium plan')
|
|
54
|
+
|
|
55
|
+
Same changes as in `@mui/x-charts-pro@8.27.4`.
|
|
56
|
+
|
|
57
|
+
### Core
|
|
58
|
+
|
|
59
|
+
- [code-infra] Add eslint rule to prevent `Math.random` in docs (#21505) (#21563) @JCQuintas
|
|
60
|
+
- [code-infra] V8 Dedupe (#21561) @JCQuintas
|
|
61
|
+
- [docs-infra] Apply Cookie Banner to v8.x Branch (#21448) @dav-is
|
|
62
|
+
- [code-infra] Setup checkout to do full clone for non master branches (#21624) @brijeshb42
|
|
63
|
+
|
|
64
|
+
## 8.27.3
|
|
65
|
+
|
|
66
|
+
_Feb 25, 2026_
|
|
67
|
+
|
|
68
|
+
We'd like to extend a big thank you to the 4 contributors who made this release possible. Here are some highlights ✨:
|
|
69
|
+
|
|
70
|
+
- 🐞 Bugfixes
|
|
71
|
+
- ⚡️ Improved dynamic data support and cache invalidation in lazy loading for Data Grid Pro
|
|
72
|
+
|
|
73
|
+
The following team members contributed to this release:
|
|
74
|
+
@cherniavskii, @michelengelen, @MBilalShafi, @arminmeh
|
|
75
|
+
|
|
76
|
+
### Data Grid
|
|
77
|
+
|
|
78
|
+
#### `@mui/x-data-grid@8.27.3`
|
|
79
|
+
|
|
80
|
+
- [DataGrid] Preserve key input during row edit when using `rowModesModel` (#21457) @michelengelen
|
|
81
|
+
|
|
82
|
+
#### `@mui/x-data-grid-pro@8.27.3` [](https://mui.com/r/x-pro-svg-link 'Pro plan')
|
|
83
|
+
|
|
84
|
+
Same changes as in `@mui/x-data-grid@8.27.3`, plus:
|
|
85
|
+
|
|
86
|
+
- [DataGridPro] Improve dynamic data support and cache invalidation in lazy loading (#21465) @MBilalShafi
|
|
87
|
+
|
|
88
|
+
#### `@mui/x-data-grid-premium@8.27.3` [](https://mui.com/r/x-premium-svg-link 'Premium plan')
|
|
89
|
+
|
|
90
|
+
Same changes as in `@mui/x-data-grid-pro@8.27.3`.
|
|
91
|
+
|
|
92
|
+
### Core
|
|
93
|
+
|
|
94
|
+
- [code-infra] Do not append `x` to the last version for the compare API (#21422) @arminmeh
|
|
95
|
+
- [docs-infra] Fix current version detection logic (#21415) @cherniavskii
|
|
96
|
+
|
|
8
97
|
## 8.27.2
|
|
9
98
|
|
|
10
99
|
_Feb 20, 2026_
|
|
@@ -34,7 +34,7 @@ const configuration = {
|
|
|
34
34
|
useFilterValueGetter: apiRef => apiRef.current.getRowValue
|
|
35
35
|
}
|
|
36
36
|
};
|
|
37
|
-
const releaseInfo = "
|
|
37
|
+
const releaseInfo = "MTc3MjY2ODgwMDAwMA==";
|
|
38
38
|
const watermark = /*#__PURE__*/(0, _jsxRuntime.jsx)(_xLicense.Watermark, {
|
|
39
39
|
packageName: "x-data-grid-pro",
|
|
40
40
|
releaseInfo: releaseInfo
|
|
@@ -192,6 +192,13 @@ DataGridProRaw.propTypes = {
|
|
|
192
192
|
get: _propTypes.default.func.isRequired,
|
|
193
193
|
set: _propTypes.default.func.isRequired
|
|
194
194
|
}),
|
|
195
|
+
/**
|
|
196
|
+
* If positive, the Data Grid will periodically revalidate data source rows by re-fetching them from the server when the cache entry has expired.
|
|
197
|
+
* If the refetched rows are different from the current rows, the grid will update the rows.
|
|
198
|
+
* Set to `0` to disable polling.
|
|
199
|
+
* @default 0
|
|
200
|
+
*/
|
|
201
|
+
dataSourceRevalidateMs: _propTypes.default.number,
|
|
195
202
|
/**
|
|
196
203
|
* If above 0, the row children will be expanded up to this depth.
|
|
197
204
|
* If equal to -1, all the row children will be expanded.
|
|
@@ -41,6 +41,7 @@ const DATA_GRID_PRO_PROPS_DEFAULT_VALUES = exports.DATA_GRID_PRO_PROPS_DEFAULT_V
|
|
|
41
41
|
treeData: false,
|
|
42
42
|
lazyLoading: false,
|
|
43
43
|
lazyLoadingRequestThrottleMs: 500,
|
|
44
|
+
dataSourceRevalidateMs: 0,
|
|
44
45
|
listView: false,
|
|
45
46
|
multipleColumnsSortingMode: 'withModifierKey',
|
|
46
47
|
pinnedColumnsSectionSeparator: 'border-and-shadow',
|
|
@@ -27,7 +27,7 @@ const configuration = {
|
|
|
27
27
|
useFilterValueGetter: apiRef => apiRef.current.getRowValue
|
|
28
28
|
}
|
|
29
29
|
};
|
|
30
|
-
const releaseInfo = "
|
|
30
|
+
const releaseInfo = "MTc3MjY2ODgwMDAwMA==";
|
|
31
31
|
const watermark = /*#__PURE__*/_jsx(Watermark, {
|
|
32
32
|
packageName: "x-data-grid-pro",
|
|
33
33
|
releaseInfo: releaseInfo
|
|
@@ -185,6 +185,13 @@ DataGridProRaw.propTypes = {
|
|
|
185
185
|
get: PropTypes.func.isRequired,
|
|
186
186
|
set: PropTypes.func.isRequired
|
|
187
187
|
}),
|
|
188
|
+
/**
|
|
189
|
+
* If positive, the Data Grid will periodically revalidate data source rows by re-fetching them from the server when the cache entry has expired.
|
|
190
|
+
* If the refetched rows are different from the current rows, the grid will update the rows.
|
|
191
|
+
* Set to `0` to disable polling.
|
|
192
|
+
* @default 0
|
|
193
|
+
*/
|
|
194
|
+
dataSourceRevalidateMs: PropTypes.number,
|
|
188
195
|
/**
|
|
189
196
|
* If above 0, the row children will be expanded up to this depth.
|
|
190
197
|
* If equal to -1, all the row children will be expanded.
|
|
@@ -33,6 +33,7 @@ export const DATA_GRID_PRO_PROPS_DEFAULT_VALUES = _extends({}, DATA_GRID_PROPS_D
|
|
|
33
33
|
treeData: false,
|
|
34
34
|
lazyLoading: false,
|
|
35
35
|
lazyLoadingRequestThrottleMs: 500,
|
|
36
|
+
dataSourceRevalidateMs: 0,
|
|
36
37
|
listView: false,
|
|
37
38
|
multipleColumnsSortingMode: 'withModifierKey',
|
|
38
39
|
pinnedColumnsSectionSeparator: 'border-and-shadow',
|
|
@@ -13,7 +13,7 @@ export declare const useGridDataSourceBasePro: <Api extends GridPrivateApiPro>(a
|
|
|
13
13
|
public: GridDataSourceApiPro;
|
|
14
14
|
private: GridDataSourcePrivateApiPro;
|
|
15
15
|
};
|
|
16
|
-
debouncedFetchRows: ((parentId?:
|
|
16
|
+
debouncedFetchRows: ((parentId?: GridRowId, params?: import("@mui/x-data-grid/internals").GridDataSourceFetchRowsParams<import("@mui/x-data-grid").GridGetRowsParams>) => Promise<void>) & import("@mui/utils/debounce").Cancelable;
|
|
17
17
|
flatTreeStrategyProcessor: {
|
|
18
18
|
strategyName: DataSourceRowsUpdateStrategy;
|
|
19
19
|
group: "dataSourceRowsUpdate";
|
|
@@ -4,7 +4,7 @@ import _extends from "@babel/runtime/helpers/esm/extends";
|
|
|
4
4
|
import * as React from 'react';
|
|
5
5
|
import { isDeepEqual } from '@mui/x-internals/isDeepEqual';
|
|
6
6
|
import useLazyRef from '@mui/utils/useLazyRef';
|
|
7
|
-
import { useGridSelector, GridGetRowsError, gridRowIdSelector, gridRowNodeSelector, gridRowTreeSelector, GRID_ROOT_GROUP_ID } from '@mui/x-data-grid';
|
|
7
|
+
import { useGridSelector, GridGetRowsError, gridRowIdSelector, gridRowNodeSelector, gridRowTreeSelector, GRID_ROOT_GROUP_ID, gridRowsLookupSelector } from '@mui/x-data-grid';
|
|
8
8
|
import { gridRowGroupsToFetchSelector, useGridDataSourceBase, CacheChunkManager, gridGetRowsParamsSelector, DataSourceRowsUpdateStrategy, GridStrategyGroup, getTreeNodeDescendants } from '@mui/x-data-grid/internals';
|
|
9
9
|
import { warnOnce } from '@mui/x-internals/warning';
|
|
10
10
|
import { NestedDataManager, RequestStatus, getGroupKeys } from "./utils.js";
|
|
@@ -39,6 +39,7 @@ export const useGridDataSourceBasePro = (apiRef, props, options = {}) => {
|
|
|
39
39
|
debouncedFetchRows,
|
|
40
40
|
strategyProcessor: flatTreeStrategyProcessor,
|
|
41
41
|
events,
|
|
42
|
+
startPolling,
|
|
42
43
|
cacheChunkManager,
|
|
43
44
|
cache
|
|
44
45
|
} = useGridDataSourceBase(apiRef, props, _extends({
|
|
@@ -53,6 +54,35 @@ export const useGridDataSourceBasePro = (apiRef, props, options = {}) => {
|
|
|
53
54
|
apiRef.current.setStrategyAvailability(GridStrategyGroup.DataSource, currentStrategy, props.dataSource && !props.lazyLoading ? () => true : () => false);
|
|
54
55
|
}, [apiRef, props.dataSource, props.lazyLoading, props.treeData]);
|
|
55
56
|
const onDataSourceErrorProp = props.onDataSourceError;
|
|
57
|
+
const replaceGroupRows = React.useCallback((groupId, groupPath, rows) => {
|
|
58
|
+
const tree = gridRowTreeSelector(apiRef);
|
|
59
|
+
const rowsLookup = gridRowsLookupSelector(apiRef);
|
|
60
|
+
const fetchedRowIds = new Set(rows.map(row => gridRowIdSelector(apiRef, row)));
|
|
61
|
+
const currentGroupRows = getTreeNodeDescendants(tree, groupId, false, true);
|
|
62
|
+
const rowsToDelete = [];
|
|
63
|
+
currentGroupRows.forEach(rowId => {
|
|
64
|
+
if (fetchedRowIds.has(rowId)) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const descendants = getTreeNodeDescendants(tree, rowId, false, false);
|
|
68
|
+
for (let i = descendants.length - 1; i >= 0; i -= 1) {
|
|
69
|
+
const descendantId = descendants[i];
|
|
70
|
+
if (fetchedRowIds.has(descendantId)) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
rowsToDelete.push(_extends({}, rowsLookup[descendantId], {
|
|
74
|
+
_action: 'delete'
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
rowsToDelete.push(_extends({}, rowsLookup[rowId], {
|
|
78
|
+
_action: 'delete'
|
|
79
|
+
}));
|
|
80
|
+
});
|
|
81
|
+
if (rowsToDelete.length > 0) {
|
|
82
|
+
apiRef.current.updateNestedRows(rowsToDelete, groupPath);
|
|
83
|
+
}
|
|
84
|
+
apiRef.current.updateNestedRows(rows, groupPath);
|
|
85
|
+
}, [apiRef]);
|
|
56
86
|
const fetchRowChildren = React.useCallback(async id => {
|
|
57
87
|
const pipedParams = apiRef.current.unstable_applyPipeProcessors('getRowsParams', {});
|
|
58
88
|
if (!props.treeData && (pipedParams.groupFields?.length ?? 0) === 0) {
|
|
@@ -78,7 +108,7 @@ export const useGridDataSourceBasePro = (apiRef, props, options = {}) => {
|
|
|
78
108
|
if (cachedData !== undefined) {
|
|
79
109
|
const rows = cachedData.rows;
|
|
80
110
|
nestedDataManager.setRequestSettled(id);
|
|
81
|
-
|
|
111
|
+
replaceGroupRows(id, rowNode.path, rows);
|
|
82
112
|
if (cachedData.rowCount !== undefined) {
|
|
83
113
|
apiRef.current.setRowCount(cachedData.rowCount);
|
|
84
114
|
}
|
|
@@ -109,23 +139,7 @@ export const useGridDataSourceBasePro = (apiRef, props, options = {}) => {
|
|
|
109
139
|
if (getRowsResponse.rowCount !== undefined) {
|
|
110
140
|
apiRef.current.setRowCount(getRowsResponse.rowCount);
|
|
111
141
|
}
|
|
112
|
-
|
|
113
|
-
const rowsToDelete = [];
|
|
114
|
-
getRowsResponse.rows.forEach(row => {
|
|
115
|
-
const rowId = gridRowIdSelector(apiRef, row);
|
|
116
|
-
const treeNode = gridRowNodeSelector(apiRef, rowId);
|
|
117
|
-
if (treeNode) {
|
|
118
|
-
rowsToDelete.push({
|
|
119
|
-
id: rowId,
|
|
120
|
-
_action: 'delete'
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
if (rowsToDelete.length > 0) {
|
|
125
|
-
// TODO: Make this happen in a single pass by modifying the pre-processing of the rows
|
|
126
|
-
apiRef.current.updateNestedRows(rowsToDelete, rowNode.path);
|
|
127
|
-
}
|
|
128
|
-
apiRef.current.updateNestedRows(getRowsResponse.rows, rowNode.path);
|
|
142
|
+
replaceGroupRows(id, rowNode.path, getRowsResponse.rows);
|
|
129
143
|
apiRef.current.setRowChildrenExpansion(id, true);
|
|
130
144
|
} catch (error) {
|
|
131
145
|
const childrenFetchError = error;
|
|
@@ -143,7 +157,7 @@ export const useGridDataSourceBasePro = (apiRef, props, options = {}) => {
|
|
|
143
157
|
apiRef.current.dataSource.setChildrenLoading(id, false);
|
|
144
158
|
nestedDataManager.setRequestSettled(id);
|
|
145
159
|
}
|
|
146
|
-
}, [nestedDataManager, cacheChunkManager, cache, onDataSourceErrorProp, apiRef, props.treeData, props.dataSource?.getRows]);
|
|
160
|
+
}, [nestedDataManager, cacheChunkManager, cache, onDataSourceErrorProp, replaceGroupRows, apiRef, props.treeData, props.dataSource?.getRows]);
|
|
147
161
|
const setChildrenLoading = React.useCallback((parentId, isLoading) => {
|
|
148
162
|
apiRef.current.setState(state => {
|
|
149
163
|
if (!state.dataSource.loading[parentId] && isLoading === false) {
|
|
@@ -185,6 +199,7 @@ export const useGridDataSourceBasePro = (apiRef, props, options = {}) => {
|
|
|
185
199
|
});
|
|
186
200
|
}, [apiRef]);
|
|
187
201
|
const removeChildrenRows = React.useCallback(parentId => {
|
|
202
|
+
const rowsLookup = gridRowsLookupSelector(apiRef);
|
|
188
203
|
const rowNode = gridRowNodeSelector(apiRef, parentId);
|
|
189
204
|
if (!rowNode || rowNode.type !== 'group' || rowNode.children.length === 0) {
|
|
190
205
|
return;
|
|
@@ -198,10 +213,9 @@ export const useGridDataSourceBasePro = (apiRef, props, options = {}) => {
|
|
|
198
213
|
if (node.type === 'group' && node.children.length > 0) {
|
|
199
214
|
node.children.forEach(traverse);
|
|
200
215
|
}
|
|
201
|
-
removedRows.push({
|
|
202
|
-
id: nodeId,
|
|
216
|
+
removedRows.push(_extends({}, rowsLookup[nodeId], {
|
|
203
217
|
_action: 'delete'
|
|
204
|
-
});
|
|
218
|
+
}));
|
|
205
219
|
};
|
|
206
220
|
rowNode.children.forEach(traverse);
|
|
207
221
|
if (removedRows.length > 0) {
|
|
@@ -226,6 +240,7 @@ export const useGridDataSourceBasePro = (apiRef, props, options = {}) => {
|
|
|
226
240
|
apiRef.current.setRows(response.rows);
|
|
227
241
|
} else {
|
|
228
242
|
const tree = gridRowTreeSelector(apiRef);
|
|
243
|
+
const rowsLookup = gridRowsLookupSelector(apiRef);
|
|
229
244
|
// Remove existing outdated rows before setting the new ones
|
|
230
245
|
// Create a set of the current root rows
|
|
231
246
|
const parentRowsToDelete = new Set(getTreeNodeDescendants(tree, GRID_ROOT_GROUP_ID, false, true));
|
|
@@ -239,15 +254,13 @@ export const useGridDataSourceBasePro = (apiRef, props, options = {}) => {
|
|
|
239
254
|
const descendants = getTreeNodeDescendants(tree, parentRowId, false, false);
|
|
240
255
|
for (let i = descendants.length - 1; i >= 0; i -= 1) {
|
|
241
256
|
// delete deepest descendants first
|
|
242
|
-
rowsToDelete.push({
|
|
243
|
-
id: descendants[i],
|
|
257
|
+
rowsToDelete.push(_extends({}, rowsLookup[descendants[i]], {
|
|
244
258
|
_action: 'delete'
|
|
245
|
-
});
|
|
259
|
+
}));
|
|
246
260
|
}
|
|
247
|
-
rowsToDelete.push({
|
|
248
|
-
id: parentRowId,
|
|
261
|
+
rowsToDelete.push(_extends({}, rowsLookup[parentRowId], {
|
|
249
262
|
_action: 'delete'
|
|
250
|
-
});
|
|
263
|
+
}));
|
|
251
264
|
});
|
|
252
265
|
}
|
|
253
266
|
apiRef.current.updateRows(response.rows.concat(rowsToDelete));
|
|
@@ -256,7 +269,8 @@ export const useGridDataSourceBasePro = (apiRef, props, options = {}) => {
|
|
|
256
269
|
params: params.fetchParams,
|
|
257
270
|
response
|
|
258
271
|
}, true);
|
|
259
|
-
|
|
272
|
+
startPolling();
|
|
273
|
+
}, [apiRef, startPolling]);
|
|
260
274
|
const dataSourceApi = {
|
|
261
275
|
dataSource: _extends({}, api.public.dataSource, {
|
|
262
276
|
setChildrenLoading,
|
|
@@ -20,7 +20,9 @@ export declare class NestedDataManager {
|
|
|
20
20
|
private maxConcurrentRequests;
|
|
21
21
|
constructor(privateApiRef: RefObject<GridPrivateApiPro>, maxConcurrentRequests?: number);
|
|
22
22
|
private processQueue;
|
|
23
|
-
queue: (ids: GridRowId[]
|
|
23
|
+
queue: (ids: GridRowId[], options?: {
|
|
24
|
+
showChildrenLoading?: boolean;
|
|
25
|
+
}) => Promise<void>;
|
|
24
26
|
setRequestSettled: (id: GridRowId) => void;
|
|
25
27
|
clear: () => void;
|
|
26
28
|
clearPendingRequest: (id: GridRowId) => void;
|
|
@@ -38,17 +38,24 @@ export class NestedDataManager {
|
|
|
38
38
|
this.api.fetchRowChildren(id);
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
|
-
queue = async ids => {
|
|
41
|
+
queue = async (ids, options = {}) => {
|
|
42
|
+
const {
|
|
43
|
+
showChildrenLoading = true
|
|
44
|
+
} = options;
|
|
42
45
|
const loadingIds = {};
|
|
43
46
|
ids.forEach(id => {
|
|
44
47
|
this.queuedRequests.add(id);
|
|
45
|
-
|
|
48
|
+
if (showChildrenLoading) {
|
|
49
|
+
loadingIds[id] = true;
|
|
50
|
+
}
|
|
46
51
|
});
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
if (showChildrenLoading) {
|
|
53
|
+
this.api.setState(state => _extends({}, state, {
|
|
54
|
+
dataSource: _extends({}, state.dataSource, {
|
|
55
|
+
loading: _extends({}, state.dataSource.loading, loadingIds)
|
|
56
|
+
})
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
52
59
|
this.processQueue();
|
|
53
60
|
};
|
|
54
61
|
setRequestSettled = id => {
|
|
@@ -6,4 +6,4 @@ import type { DataGridProProcessedProps } from "../../../models/dataGridProProps
|
|
|
6
6
|
* @requires useGridPagination (state)
|
|
7
7
|
* @requires useGridScroll (method
|
|
8
8
|
*/
|
|
9
|
-
export declare const useGridDataSourceLazyLoader: (privateApiRef: RefObject<GridPrivateApiPro>, props: Pick<DataGridProProcessedProps, "dataSource" | "lazyLoading" | "lazyLoadingRequestThrottleMs">) => void;
|
|
9
|
+
export declare const useGridDataSourceLazyLoader: (privateApiRef: RefObject<GridPrivateApiPro>, props: Pick<DataGridProProcessedProps, "dataSource" | "lazyLoading" | "lazyLoadingRequestThrottleMs" | "dataSourceRevalidateMs">) => void;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import _extends from "@babel/runtime/helpers/esm/extends";
|
|
4
4
|
import * as React from 'react';
|
|
5
5
|
import { throttle } from '@mui/x-internals/throttle';
|
|
6
|
+
import { isDeepEqual } from '@mui/x-internals/isDeepEqual';
|
|
6
7
|
import useEventCallback from '@mui/utils/useEventCallback';
|
|
7
8
|
import debounce from '@mui/utils/debounce';
|
|
8
9
|
import { useGridEvent, gridSortModelSelector, gridFilterModelSelector, GRID_ROOT_GROUP_ID, gridPaginationModelSelector, gridFilteredSortedRowIdsSelector, gridRowIdSelector } from '@mui/x-data-grid';
|
|
@@ -35,10 +36,42 @@ export const useGridDataSourceLazyLoader = (privateApiRef, props) => {
|
|
|
35
36
|
const loadingTrigger = React.useRef(null);
|
|
36
37
|
const rowsStale = React.useRef(false);
|
|
37
38
|
const draggedRowId = React.useRef(null);
|
|
39
|
+
const pollingIntervalRef = React.useRef(null);
|
|
38
40
|
const fetchRows = React.useCallback(params => {
|
|
39
41
|
privateApiRef.current.dataSource.fetchRows(GRID_ROOT_GROUP_ID, params);
|
|
40
42
|
}, [privateApiRef]);
|
|
41
43
|
const debouncedFetchRows = React.useMemo(() => debounce(fetchRows, 0), [fetchRows]);
|
|
44
|
+
const revalidate = useEventCallback(params => {
|
|
45
|
+
if (rowsStale.current) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Check cache first — if data is still cached, skip entirely
|
|
50
|
+
// (no backend call, no diffing needed)
|
|
51
|
+
const cache = privateApiRef.current.dataSource.cache;
|
|
52
|
+
const cachedResponse = cache.get(params);
|
|
53
|
+
if (cachedResponse !== undefined) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Cache is stale/expired — fetch in background (no loading indicator)
|
|
58
|
+
debouncedFetchRows(params);
|
|
59
|
+
});
|
|
60
|
+
const stopPolling = React.useCallback(() => {
|
|
61
|
+
if (pollingIntervalRef.current !== null) {
|
|
62
|
+
clearInterval(pollingIntervalRef.current);
|
|
63
|
+
pollingIntervalRef.current = null;
|
|
64
|
+
}
|
|
65
|
+
}, []);
|
|
66
|
+
const startPolling = useEventCallback(params => {
|
|
67
|
+
stopPolling();
|
|
68
|
+
if (props.dataSourceRevalidateMs <= 0) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
pollingIntervalRef.current = setInterval(() => {
|
|
72
|
+
revalidate(params);
|
|
73
|
+
}, props.dataSourceRevalidateMs);
|
|
74
|
+
});
|
|
42
75
|
const resetGrid = React.useCallback(() => {
|
|
43
76
|
privateApiRef.current.setLoading(true);
|
|
44
77
|
privateApiRef.current.dataSource.cache.clear();
|
|
@@ -89,38 +122,10 @@ export const useGridDataSourceLazyLoader = (privateApiRef, props) => {
|
|
|
89
122
|
if (rootChildrenCount === 0) {
|
|
90
123
|
return;
|
|
91
124
|
}
|
|
92
|
-
const paginationModel = gridPaginationModelSelector(privateApiRef);
|
|
93
|
-
const pageToSkip = adjustRowParams({
|
|
94
|
-
start: renderedRowsIntervalCache.current.firstRowToRender,
|
|
95
|
-
end: renderedRowsIntervalCache.current.lastRowToRender
|
|
96
|
-
}, {
|
|
97
|
-
pageSize: paginationModel.pageSize,
|
|
98
|
-
rowCount: pageRowCount
|
|
99
|
-
});
|
|
100
125
|
let hasChanged = false;
|
|
101
|
-
const isInitialPage = renderedRowsIntervalCache.current.firstRowToRender === 0 && renderedRowsIntervalCache.current.lastRowToRender === 0;
|
|
102
|
-
for (let i = 0; i < rootChildrenCount; i += 1) {
|
|
103
|
-
if (isInitialPage) {
|
|
104
|
-
break;
|
|
105
|
-
}
|
|
106
|
-
// replace the rows not in the viewport with skeleton rows
|
|
107
|
-
if (pageToSkip.start <= i && i <= pageToSkip.end || tree[rootGroupChildren[i]]?.type === 'skeletonRow' ||
|
|
108
|
-
// ignore rows that are already skeleton rows
|
|
109
|
-
tree[rootGroupChildren[i]]?.id === draggedRowId.current // ignore row that is being dragged (https://github.com/mui/mui-x/issues/17854)
|
|
110
|
-
) {
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
const rowId = tree[rootGroupChildren[i]].id; // keep the id, so that row related state is maintained
|
|
114
|
-
const skeletonRowNode = {
|
|
115
|
-
type: 'skeletonRow',
|
|
116
|
-
id: rowId,
|
|
117
|
-
parent: GRID_ROOT_GROUP_ID,
|
|
118
|
-
depth: 0
|
|
119
|
-
};
|
|
120
|
-
tree[rowId] = skeletonRowNode;
|
|
121
|
-
hasChanged = true;
|
|
122
|
-
}
|
|
123
126
|
|
|
127
|
+
// SWR: Only add skeleton padding for never-fetched positions beyond current data.
|
|
128
|
+
// Previously fetched rows are kept in place (not skeletonized) to avoid flicker on scroll-back.
|
|
124
129
|
// Should only happen with VIEWPORT loading trigger
|
|
125
130
|
if (loadingTrigger.current === LoadingTrigger.VIEWPORT) {
|
|
126
131
|
// fill the grid with skeleton rows
|
|
@@ -186,39 +191,121 @@ export const useGridDataSourceLazyLoader = (privateApiRef, props) => {
|
|
|
186
191
|
const filteredSortedRowIds = gridFilteredSortedRowIdsSelector(privateApiRef);
|
|
187
192
|
const startingIndex = typeof fetchParams.start === 'string' ? Math.max(filteredSortedRowIds.indexOf(fetchParams.start), 0) : fetchParams.start;
|
|
188
193
|
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
194
|
+
// Determine if this is a background revalidation (target rows are real, not skeletons)
|
|
195
|
+
const firstTargetRow = rootGroupChildren[startingIndex];
|
|
196
|
+
const isRevalidation = firstTargetRow && tree[firstTargetRow]?.type !== 'skeletonRow';
|
|
197
|
+
if (isRevalidation) {
|
|
198
|
+
// --- SWR PATH ---
|
|
199
|
+
// Compare response row IDs with existing row IDs at target positions
|
|
200
|
+
const newRowIds = response.rows.map(row => gridRowIdSelector(privateApiRef, row));
|
|
201
|
+
const existingRowIds = rootGroupChildren.slice(startingIndex, startingIndex + response.rows.length);
|
|
202
|
+
const sameRowIds = existingRowIds.length === newRowIds.length && existingRowIds.every((id, i) => id === newRowIds[i]);
|
|
203
|
+
if (sameRowIds) {
|
|
204
|
+
// SAME ROW IDs — check for data changes only
|
|
205
|
+
const changedRows = response.rows.filter((newRow, i) => {
|
|
206
|
+
const existingRow = dataRowIdToModelLookup[existingRowIds[i]];
|
|
207
|
+
return !isDeepEqual(newRow, existingRow);
|
|
208
|
+
});
|
|
209
|
+
if (changedRows.length === 0) {
|
|
210
|
+
// No changes — skip update entirely. Cache already refreshed by fetchRows.
|
|
211
|
+
privateApiRef.current.setLoading(false);
|
|
212
|
+
return;
|
|
204
213
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
214
|
+
|
|
215
|
+
// Efficient data-only update — no tree restructuring needed
|
|
216
|
+
privateApiRef.current.updateRows(changedRows);
|
|
217
|
+
// Cache is already updated by fetchRows in useGridDataSourceBase
|
|
218
|
+
} else {
|
|
219
|
+
// DIFFERENT ROW IDs — server returned new rows for this range
|
|
220
|
+
// 1. Remove old rows at target positions
|
|
221
|
+
for (let i = startingIndex; i < startingIndex + response.rows.length && i < rootGroupChildren.length; i += 1) {
|
|
222
|
+
const oldRowId = rootGroupChildren[i];
|
|
223
|
+
if (oldRowId && tree[oldRowId]?.type !== 'skeletonRow') {
|
|
224
|
+
delete tree[oldRowId];
|
|
225
|
+
delete dataRowIdToModelLookup[oldRowId];
|
|
226
|
+
const skeletonId = getSkeletonRowId(i);
|
|
227
|
+
rootGroupChildren[i] = skeletonId;
|
|
228
|
+
tree[skeletonId] = {
|
|
229
|
+
type: 'skeletonRow',
|
|
230
|
+
id: skeletonId,
|
|
231
|
+
parent: GRID_ROOT_GROUP_ID,
|
|
232
|
+
depth: 0
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// 2. Duplicate detection for incoming rows
|
|
238
|
+
let duplicateRowCount = 0;
|
|
239
|
+
response.rows.forEach(row => {
|
|
240
|
+
const rowId = gridRowIdSelector(privateApiRef, row);
|
|
241
|
+
if (tree[rowId] || dataRowIdToModelLookup[rowId]) {
|
|
242
|
+
const index = rootGroupChildren.indexOf(rowId);
|
|
243
|
+
if (index !== -1) {
|
|
244
|
+
const skeletonId = getSkeletonRowId(index);
|
|
245
|
+
rootGroupChildren[index] = skeletonId;
|
|
246
|
+
tree[skeletonId] = {
|
|
247
|
+
type: 'skeletonRow',
|
|
248
|
+
id: skeletonId,
|
|
249
|
+
parent: GRID_ROOT_GROUP_ID,
|
|
250
|
+
depth: 0
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
delete tree[rowId];
|
|
254
|
+
delete dataRowIdToModelLookup[rowId];
|
|
255
|
+
duplicateRowCount += 1;
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
if (duplicateRowCount > 0) {
|
|
259
|
+
tree[GRID_ROOT_GROUP_ID] = _extends({}, rootGroup, {
|
|
260
|
+
children: rootGroupChildren
|
|
261
|
+
});
|
|
262
|
+
privateApiRef.current.setState(state => _extends({}, state, {
|
|
263
|
+
rows: _extends({}, state.rows, {
|
|
264
|
+
tree,
|
|
265
|
+
dataRowIdToModelLookup
|
|
266
|
+
})
|
|
267
|
+
}));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// 3. Replace rows
|
|
271
|
+
privateApiRef.current.unstable_replaceRows(startingIndex, response.rows);
|
|
208
272
|
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
273
|
+
} else {
|
|
274
|
+
// --- ORIGINAL PATH (skeleton → real row replacement) ---
|
|
275
|
+
// Check for duplicate rows
|
|
276
|
+
let duplicateRowCount = 0;
|
|
277
|
+
response.rows.forEach(row => {
|
|
278
|
+
const rowId = gridRowIdSelector(privateApiRef, row);
|
|
279
|
+
if (tree[rowId] || dataRowIdToModelLookup[rowId]) {
|
|
280
|
+
const index = rootGroupChildren.indexOf(rowId);
|
|
281
|
+
if (index !== -1) {
|
|
282
|
+
const skeletonId = getSkeletonRowId(index);
|
|
283
|
+
rootGroupChildren[index] = skeletonId;
|
|
284
|
+
tree[skeletonId] = {
|
|
285
|
+
type: 'skeletonRow',
|
|
286
|
+
id: skeletonId,
|
|
287
|
+
parent: GRID_ROOT_GROUP_ID,
|
|
288
|
+
depth: 0
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
delete tree[rowId];
|
|
292
|
+
delete dataRowIdToModelLookup[rowId];
|
|
293
|
+
duplicateRowCount += 1;
|
|
294
|
+
}
|
|
213
295
|
});
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
|
|
296
|
+
if (duplicateRowCount > 0) {
|
|
297
|
+
tree[GRID_ROOT_GROUP_ID] = _extends({}, rootGroup, {
|
|
298
|
+
children: rootGroupChildren
|
|
299
|
+
});
|
|
300
|
+
privateApiRef.current.setState(state => _extends({}, state, {
|
|
301
|
+
rows: _extends({}, state.rows, {
|
|
302
|
+
tree,
|
|
303
|
+
dataRowIdToModelLookup
|
|
304
|
+
})
|
|
305
|
+
}));
|
|
306
|
+
}
|
|
307
|
+
privateApiRef.current.unstable_replaceRows(startingIndex, response.rows);
|
|
220
308
|
}
|
|
221
|
-
privateApiRef.current.unstable_replaceRows(startingIndex, response.rows);
|
|
222
309
|
}
|
|
223
310
|
rowsStale.current = false;
|
|
224
311
|
if (loadingTrigger.current === null) {
|
|
@@ -230,8 +317,11 @@ export const useGridDataSourceLazyLoader = (privateApiRef, props) => {
|
|
|
230
317
|
params: params.fetchParams,
|
|
231
318
|
response
|
|
232
319
|
}, false);
|
|
320
|
+
if (loadingTrigger.current === LoadingTrigger.VIEWPORT) {
|
|
321
|
+
startPolling(params.fetchParams);
|
|
322
|
+
}
|
|
233
323
|
privateApiRef.current.requestPipeProcessorsApplication('hydrateRows');
|
|
234
|
-
}, [privateApiRef, updateLoadingTrigger, addSkeletonRows]);
|
|
324
|
+
}, [privateApiRef, updateLoadingTrigger, addSkeletonRows, startPolling]);
|
|
235
325
|
const handleRowCountChange = React.useCallback(() => {
|
|
236
326
|
if (rowsStale.current || loadingTrigger.current === null) {
|
|
237
327
|
return;
|
|
@@ -289,26 +379,46 @@ export const useGridDataSourceLazyLoader = (privateApiRef, props) => {
|
|
|
289
379
|
visibleRows: currentVisibleRows.rows,
|
|
290
380
|
range: renderContext
|
|
291
381
|
});
|
|
382
|
+
const paginationModel = gridPaginationModelSelector(privateApiRef);
|
|
292
383
|
if (!skeletonRowsSection) {
|
|
384
|
+
// SWR: No skeleton rows in viewport — all visible rows have real data.
|
|
385
|
+
// Schedule background revalidation if cache has expired for this range.
|
|
386
|
+
if (loadingTrigger.current === LoadingTrigger.VIEWPORT) {
|
|
387
|
+
const adjustedParams = adjustRowParams(getRowsParams, {
|
|
388
|
+
pageSize: paginationModel.pageSize,
|
|
389
|
+
rowCount: privateApiRef.current.state.pagination.rowCount
|
|
390
|
+
});
|
|
391
|
+
revalidate(adjustedParams);
|
|
392
|
+
startPolling(adjustedParams);
|
|
393
|
+
}
|
|
293
394
|
return;
|
|
294
395
|
}
|
|
295
396
|
getRowsParams.start = skeletonRowsSection.firstRowIndex;
|
|
296
397
|
getRowsParams.end = skeletonRowsSection.lastRowIndex;
|
|
297
|
-
const paginationModel = gridPaginationModelSelector(privateApiRef);
|
|
298
398
|
fetchRows(adjustRowParams(getRowsParams, {
|
|
299
399
|
pageSize: paginationModel.pageSize,
|
|
300
400
|
rowCount: privateApiRef.current.state.pagination.rowCount
|
|
301
401
|
}));
|
|
302
|
-
}, [privateApiRef, fetchRows]);
|
|
402
|
+
}, [privateApiRef, fetchRows, revalidate, startPolling]);
|
|
303
403
|
const throttledHandleRenderedRowsIntervalChange = React.useMemo(() => throttle(handleRenderedRowsIntervalChange, props.lazyLoadingRequestThrottleMs), [props.lazyLoadingRequestThrottleMs, handleRenderedRowsIntervalChange]);
|
|
304
404
|
React.useEffect(() => {
|
|
305
405
|
return () => {
|
|
306
406
|
throttledHandleRenderedRowsIntervalChange.clear();
|
|
407
|
+
stopPolling();
|
|
307
408
|
};
|
|
308
|
-
}, [throttledHandleRenderedRowsIntervalChange]);
|
|
409
|
+
}, [throttledHandleRenderedRowsIntervalChange, stopPolling]);
|
|
410
|
+
|
|
411
|
+
// Stop polling when dataSourceRevalidateMs is set to 0
|
|
412
|
+
React.useEffect(() => {
|
|
413
|
+
if (props.dataSourceRevalidateMs <= 0) {
|
|
414
|
+
stopPolling();
|
|
415
|
+
}
|
|
416
|
+
}, [props.dataSourceRevalidateMs, stopPolling]);
|
|
417
|
+
React.useEffect(() => stopPolling, [stopPolling]);
|
|
309
418
|
const handleGridSortModelChange = React.useCallback(newSortModel => {
|
|
310
419
|
rowsStale.current = true;
|
|
311
420
|
throttledHandleRenderedRowsIntervalChange.clear();
|
|
421
|
+
stopPolling();
|
|
312
422
|
previousLastRowIndex.current = 0;
|
|
313
423
|
const paginationModel = gridPaginationModelSelector(privateApiRef);
|
|
314
424
|
const filterModel = gridFilterModelSelector(privateApiRef);
|
|
@@ -320,10 +430,11 @@ export const useGridDataSourceLazyLoader = (privateApiRef, props) => {
|
|
|
320
430
|
};
|
|
321
431
|
privateApiRef.current.setLoading(true);
|
|
322
432
|
debouncedFetchRows(getRowsParams);
|
|
323
|
-
}, [privateApiRef, debouncedFetchRows, throttledHandleRenderedRowsIntervalChange]);
|
|
433
|
+
}, [privateApiRef, debouncedFetchRows, throttledHandleRenderedRowsIntervalChange, stopPolling]);
|
|
324
434
|
const handleGridFilterModelChange = React.useCallback(newFilterModel => {
|
|
325
435
|
rowsStale.current = true;
|
|
326
436
|
throttledHandleRenderedRowsIntervalChange.clear();
|
|
437
|
+
stopPolling();
|
|
327
438
|
previousLastRowIndex.current = 0;
|
|
328
439
|
const paginationModel = gridPaginationModelSelector(privateApiRef);
|
|
329
440
|
const sortModel = gridSortModelSelector(privateApiRef);
|
|
@@ -335,7 +446,7 @@ export const useGridDataSourceLazyLoader = (privateApiRef, props) => {
|
|
|
335
446
|
};
|
|
336
447
|
privateApiRef.current.setLoading(true);
|
|
337
448
|
debouncedFetchRows(getRowsParams);
|
|
338
|
-
}, [privateApiRef, debouncedFetchRows, throttledHandleRenderedRowsIntervalChange]);
|
|
449
|
+
}, [privateApiRef, debouncedFetchRows, throttledHandleRenderedRowsIntervalChange, stopPolling]);
|
|
339
450
|
const handleDragStart = React.useCallback(row => {
|
|
340
451
|
draggedRowId.current = row.id;
|
|
341
452
|
}, []);
|
package/esm/index.js
CHANGED
|
@@ -13,7 +13,7 @@ export declare const useGridDataSourceBasePro: <Api extends GridPrivateApiPro>(a
|
|
|
13
13
|
public: GridDataSourceApiPro;
|
|
14
14
|
private: GridDataSourcePrivateApiPro;
|
|
15
15
|
};
|
|
16
|
-
debouncedFetchRows: ((parentId?:
|
|
16
|
+
debouncedFetchRows: ((parentId?: GridRowId, params?: import("@mui/x-data-grid/internals").GridDataSourceFetchRowsParams<import("@mui/x-data-grid").GridGetRowsParams>) => Promise<void>) & import("@mui/utils/debounce").Cancelable;
|
|
17
17
|
flatTreeStrategyProcessor: {
|
|
18
18
|
strategyName: DataSourceRowsUpdateStrategy;
|
|
19
19
|
group: "dataSourceRowsUpdate";
|
|
@@ -46,6 +46,7 @@ const useGridDataSourceBasePro = (apiRef, props, options = {}) => {
|
|
|
46
46
|
debouncedFetchRows,
|
|
47
47
|
strategyProcessor: flatTreeStrategyProcessor,
|
|
48
48
|
events,
|
|
49
|
+
startPolling,
|
|
49
50
|
cacheChunkManager,
|
|
50
51
|
cache
|
|
51
52
|
} = (0, _internals.useGridDataSourceBase)(apiRef, props, (0, _extends2.default)({
|
|
@@ -60,6 +61,35 @@ const useGridDataSourceBasePro = (apiRef, props, options = {}) => {
|
|
|
60
61
|
apiRef.current.setStrategyAvailability(_internals.GridStrategyGroup.DataSource, currentStrategy, props.dataSource && !props.lazyLoading ? () => true : () => false);
|
|
61
62
|
}, [apiRef, props.dataSource, props.lazyLoading, props.treeData]);
|
|
62
63
|
const onDataSourceErrorProp = props.onDataSourceError;
|
|
64
|
+
const replaceGroupRows = React.useCallback((groupId, groupPath, rows) => {
|
|
65
|
+
const tree = (0, _xDataGrid.gridRowTreeSelector)(apiRef);
|
|
66
|
+
const rowsLookup = (0, _xDataGrid.gridRowsLookupSelector)(apiRef);
|
|
67
|
+
const fetchedRowIds = new Set(rows.map(row => (0, _xDataGrid.gridRowIdSelector)(apiRef, row)));
|
|
68
|
+
const currentGroupRows = (0, _internals.getTreeNodeDescendants)(tree, groupId, false, true);
|
|
69
|
+
const rowsToDelete = [];
|
|
70
|
+
currentGroupRows.forEach(rowId => {
|
|
71
|
+
if (fetchedRowIds.has(rowId)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const descendants = (0, _internals.getTreeNodeDescendants)(tree, rowId, false, false);
|
|
75
|
+
for (let i = descendants.length - 1; i >= 0; i -= 1) {
|
|
76
|
+
const descendantId = descendants[i];
|
|
77
|
+
if (fetchedRowIds.has(descendantId)) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
rowsToDelete.push((0, _extends2.default)({}, rowsLookup[descendantId], {
|
|
81
|
+
_action: 'delete'
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
rowsToDelete.push((0, _extends2.default)({}, rowsLookup[rowId], {
|
|
85
|
+
_action: 'delete'
|
|
86
|
+
}));
|
|
87
|
+
});
|
|
88
|
+
if (rowsToDelete.length > 0) {
|
|
89
|
+
apiRef.current.updateNestedRows(rowsToDelete, groupPath);
|
|
90
|
+
}
|
|
91
|
+
apiRef.current.updateNestedRows(rows, groupPath);
|
|
92
|
+
}, [apiRef]);
|
|
63
93
|
const fetchRowChildren = React.useCallback(async id => {
|
|
64
94
|
const pipedParams = apiRef.current.unstable_applyPipeProcessors('getRowsParams', {});
|
|
65
95
|
if (!props.treeData && (pipedParams.groupFields?.length ?? 0) === 0) {
|
|
@@ -85,7 +115,7 @@ const useGridDataSourceBasePro = (apiRef, props, options = {}) => {
|
|
|
85
115
|
if (cachedData !== undefined) {
|
|
86
116
|
const rows = cachedData.rows;
|
|
87
117
|
nestedDataManager.setRequestSettled(id);
|
|
88
|
-
|
|
118
|
+
replaceGroupRows(id, rowNode.path, rows);
|
|
89
119
|
if (cachedData.rowCount !== undefined) {
|
|
90
120
|
apiRef.current.setRowCount(cachedData.rowCount);
|
|
91
121
|
}
|
|
@@ -116,23 +146,7 @@ const useGridDataSourceBasePro = (apiRef, props, options = {}) => {
|
|
|
116
146
|
if (getRowsResponse.rowCount !== undefined) {
|
|
117
147
|
apiRef.current.setRowCount(getRowsResponse.rowCount);
|
|
118
148
|
}
|
|
119
|
-
|
|
120
|
-
const rowsToDelete = [];
|
|
121
|
-
getRowsResponse.rows.forEach(row => {
|
|
122
|
-
const rowId = (0, _xDataGrid.gridRowIdSelector)(apiRef, row);
|
|
123
|
-
const treeNode = (0, _xDataGrid.gridRowNodeSelector)(apiRef, rowId);
|
|
124
|
-
if (treeNode) {
|
|
125
|
-
rowsToDelete.push({
|
|
126
|
-
id: rowId,
|
|
127
|
-
_action: 'delete'
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
if (rowsToDelete.length > 0) {
|
|
132
|
-
// TODO: Make this happen in a single pass by modifying the pre-processing of the rows
|
|
133
|
-
apiRef.current.updateNestedRows(rowsToDelete, rowNode.path);
|
|
134
|
-
}
|
|
135
|
-
apiRef.current.updateNestedRows(getRowsResponse.rows, rowNode.path);
|
|
149
|
+
replaceGroupRows(id, rowNode.path, getRowsResponse.rows);
|
|
136
150
|
apiRef.current.setRowChildrenExpansion(id, true);
|
|
137
151
|
} catch (error) {
|
|
138
152
|
const childrenFetchError = error;
|
|
@@ -150,7 +164,7 @@ const useGridDataSourceBasePro = (apiRef, props, options = {}) => {
|
|
|
150
164
|
apiRef.current.dataSource.setChildrenLoading(id, false);
|
|
151
165
|
nestedDataManager.setRequestSettled(id);
|
|
152
166
|
}
|
|
153
|
-
}, [nestedDataManager, cacheChunkManager, cache, onDataSourceErrorProp, apiRef, props.treeData, props.dataSource?.getRows]);
|
|
167
|
+
}, [nestedDataManager, cacheChunkManager, cache, onDataSourceErrorProp, replaceGroupRows, apiRef, props.treeData, props.dataSource?.getRows]);
|
|
154
168
|
const setChildrenLoading = React.useCallback((parentId, isLoading) => {
|
|
155
169
|
apiRef.current.setState(state => {
|
|
156
170
|
if (!state.dataSource.loading[parentId] && isLoading === false) {
|
|
@@ -192,6 +206,7 @@ const useGridDataSourceBasePro = (apiRef, props, options = {}) => {
|
|
|
192
206
|
});
|
|
193
207
|
}, [apiRef]);
|
|
194
208
|
const removeChildrenRows = React.useCallback(parentId => {
|
|
209
|
+
const rowsLookup = (0, _xDataGrid.gridRowsLookupSelector)(apiRef);
|
|
195
210
|
const rowNode = (0, _xDataGrid.gridRowNodeSelector)(apiRef, parentId);
|
|
196
211
|
if (!rowNode || rowNode.type !== 'group' || rowNode.children.length === 0) {
|
|
197
212
|
return;
|
|
@@ -205,10 +220,9 @@ const useGridDataSourceBasePro = (apiRef, props, options = {}) => {
|
|
|
205
220
|
if (node.type === 'group' && node.children.length > 0) {
|
|
206
221
|
node.children.forEach(traverse);
|
|
207
222
|
}
|
|
208
|
-
removedRows.push({
|
|
209
|
-
id: nodeId,
|
|
223
|
+
removedRows.push((0, _extends2.default)({}, rowsLookup[nodeId], {
|
|
210
224
|
_action: 'delete'
|
|
211
|
-
});
|
|
225
|
+
}));
|
|
212
226
|
};
|
|
213
227
|
rowNode.children.forEach(traverse);
|
|
214
228
|
if (removedRows.length > 0) {
|
|
@@ -233,6 +247,7 @@ const useGridDataSourceBasePro = (apiRef, props, options = {}) => {
|
|
|
233
247
|
apiRef.current.setRows(response.rows);
|
|
234
248
|
} else {
|
|
235
249
|
const tree = (0, _xDataGrid.gridRowTreeSelector)(apiRef);
|
|
250
|
+
const rowsLookup = (0, _xDataGrid.gridRowsLookupSelector)(apiRef);
|
|
236
251
|
// Remove existing outdated rows before setting the new ones
|
|
237
252
|
// Create a set of the current root rows
|
|
238
253
|
const parentRowsToDelete = new Set((0, _internals.getTreeNodeDescendants)(tree, _xDataGrid.GRID_ROOT_GROUP_ID, false, true));
|
|
@@ -246,15 +261,13 @@ const useGridDataSourceBasePro = (apiRef, props, options = {}) => {
|
|
|
246
261
|
const descendants = (0, _internals.getTreeNodeDescendants)(tree, parentRowId, false, false);
|
|
247
262
|
for (let i = descendants.length - 1; i >= 0; i -= 1) {
|
|
248
263
|
// delete deepest descendants first
|
|
249
|
-
rowsToDelete.push({
|
|
250
|
-
id: descendants[i],
|
|
264
|
+
rowsToDelete.push((0, _extends2.default)({}, rowsLookup[descendants[i]], {
|
|
251
265
|
_action: 'delete'
|
|
252
|
-
});
|
|
266
|
+
}));
|
|
253
267
|
}
|
|
254
|
-
rowsToDelete.push({
|
|
255
|
-
id: parentRowId,
|
|
268
|
+
rowsToDelete.push((0, _extends2.default)({}, rowsLookup[parentRowId], {
|
|
256
269
|
_action: 'delete'
|
|
257
|
-
});
|
|
270
|
+
}));
|
|
258
271
|
});
|
|
259
272
|
}
|
|
260
273
|
apiRef.current.updateRows(response.rows.concat(rowsToDelete));
|
|
@@ -263,7 +276,8 @@ const useGridDataSourceBasePro = (apiRef, props, options = {}) => {
|
|
|
263
276
|
params: params.fetchParams,
|
|
264
277
|
response
|
|
265
278
|
}, true);
|
|
266
|
-
|
|
279
|
+
startPolling();
|
|
280
|
+
}, [apiRef, startPolling]);
|
|
267
281
|
const dataSourceApi = {
|
|
268
282
|
dataSource: (0, _extends2.default)({}, api.public.dataSource, {
|
|
269
283
|
setChildrenLoading,
|
|
@@ -20,7 +20,9 @@ export declare class NestedDataManager {
|
|
|
20
20
|
private maxConcurrentRequests;
|
|
21
21
|
constructor(privateApiRef: RefObject<GridPrivateApiPro>, maxConcurrentRequests?: number);
|
|
22
22
|
private processQueue;
|
|
23
|
-
queue: (ids: GridRowId[]
|
|
23
|
+
queue: (ids: GridRowId[], options?: {
|
|
24
|
+
showChildrenLoading?: boolean;
|
|
25
|
+
}) => Promise<void>;
|
|
24
26
|
setRequestSettled: (id: GridRowId) => void;
|
|
25
27
|
clear: () => void;
|
|
26
28
|
clearPendingRequest: (id: GridRowId) => void;
|
|
@@ -44,17 +44,24 @@ class NestedDataManager {
|
|
|
44
44
|
this.api.fetchRowChildren(id);
|
|
45
45
|
}
|
|
46
46
|
};
|
|
47
|
-
queue = async ids => {
|
|
47
|
+
queue = async (ids, options = {}) => {
|
|
48
|
+
const {
|
|
49
|
+
showChildrenLoading = true
|
|
50
|
+
} = options;
|
|
48
51
|
const loadingIds = {};
|
|
49
52
|
ids.forEach(id => {
|
|
50
53
|
this.queuedRequests.add(id);
|
|
51
|
-
|
|
54
|
+
if (showChildrenLoading) {
|
|
55
|
+
loadingIds[id] = true;
|
|
56
|
+
}
|
|
52
57
|
});
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
if (showChildrenLoading) {
|
|
59
|
+
this.api.setState(state => (0, _extends2.default)({}, state, {
|
|
60
|
+
dataSource: (0, _extends2.default)({}, state.dataSource, {
|
|
61
|
+
loading: (0, _extends2.default)({}, state.dataSource.loading, loadingIds)
|
|
62
|
+
})
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
58
65
|
this.processQueue();
|
|
59
66
|
};
|
|
60
67
|
setRequestSettled = id => {
|
|
@@ -6,4 +6,4 @@ import type { DataGridProProcessedProps } from "../../../models/dataGridProProps
|
|
|
6
6
|
* @requires useGridPagination (state)
|
|
7
7
|
* @requires useGridScroll (method
|
|
8
8
|
*/
|
|
9
|
-
export declare const useGridDataSourceLazyLoader: (privateApiRef: RefObject<GridPrivateApiPro>, props: Pick<DataGridProProcessedProps, "dataSource" | "lazyLoading" | "lazyLoadingRequestThrottleMs">) => void;
|
|
9
|
+
export declare const useGridDataSourceLazyLoader: (privateApiRef: RefObject<GridPrivateApiPro>, props: Pick<DataGridProProcessedProps, "dataSource" | "lazyLoading" | "lazyLoadingRequestThrottleMs" | "dataSourceRevalidateMs">) => void;
|
|
@@ -10,6 +10,7 @@ exports.useGridDataSourceLazyLoader = void 0;
|
|
|
10
10
|
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
|
|
11
11
|
var React = _interopRequireWildcard(require("react"));
|
|
12
12
|
var _throttle = require("@mui/x-internals/throttle");
|
|
13
|
+
var _isDeepEqual = require("@mui/x-internals/isDeepEqual");
|
|
13
14
|
var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback"));
|
|
14
15
|
var _debounce = _interopRequireDefault(require("@mui/utils/debounce"));
|
|
15
16
|
var _xDataGrid = require("@mui/x-data-grid");
|
|
@@ -42,10 +43,42 @@ const useGridDataSourceLazyLoader = (privateApiRef, props) => {
|
|
|
42
43
|
const loadingTrigger = React.useRef(null);
|
|
43
44
|
const rowsStale = React.useRef(false);
|
|
44
45
|
const draggedRowId = React.useRef(null);
|
|
46
|
+
const pollingIntervalRef = React.useRef(null);
|
|
45
47
|
const fetchRows = React.useCallback(params => {
|
|
46
48
|
privateApiRef.current.dataSource.fetchRows(_xDataGrid.GRID_ROOT_GROUP_ID, params);
|
|
47
49
|
}, [privateApiRef]);
|
|
48
50
|
const debouncedFetchRows = React.useMemo(() => (0, _debounce.default)(fetchRows, 0), [fetchRows]);
|
|
51
|
+
const revalidate = (0, _useEventCallback.default)(params => {
|
|
52
|
+
if (rowsStale.current) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Check cache first — if data is still cached, skip entirely
|
|
57
|
+
// (no backend call, no diffing needed)
|
|
58
|
+
const cache = privateApiRef.current.dataSource.cache;
|
|
59
|
+
const cachedResponse = cache.get(params);
|
|
60
|
+
if (cachedResponse !== undefined) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Cache is stale/expired — fetch in background (no loading indicator)
|
|
65
|
+
debouncedFetchRows(params);
|
|
66
|
+
});
|
|
67
|
+
const stopPolling = React.useCallback(() => {
|
|
68
|
+
if (pollingIntervalRef.current !== null) {
|
|
69
|
+
clearInterval(pollingIntervalRef.current);
|
|
70
|
+
pollingIntervalRef.current = null;
|
|
71
|
+
}
|
|
72
|
+
}, []);
|
|
73
|
+
const startPolling = (0, _useEventCallback.default)(params => {
|
|
74
|
+
stopPolling();
|
|
75
|
+
if (props.dataSourceRevalidateMs <= 0) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
pollingIntervalRef.current = setInterval(() => {
|
|
79
|
+
revalidate(params);
|
|
80
|
+
}, props.dataSourceRevalidateMs);
|
|
81
|
+
});
|
|
49
82
|
const resetGrid = React.useCallback(() => {
|
|
50
83
|
privateApiRef.current.setLoading(true);
|
|
51
84
|
privateApiRef.current.dataSource.cache.clear();
|
|
@@ -96,38 +129,10 @@ const useGridDataSourceLazyLoader = (privateApiRef, props) => {
|
|
|
96
129
|
if (rootChildrenCount === 0) {
|
|
97
130
|
return;
|
|
98
131
|
}
|
|
99
|
-
const paginationModel = (0, _xDataGrid.gridPaginationModelSelector)(privateApiRef);
|
|
100
|
-
const pageToSkip = (0, _utils.adjustRowParams)({
|
|
101
|
-
start: renderedRowsIntervalCache.current.firstRowToRender,
|
|
102
|
-
end: renderedRowsIntervalCache.current.lastRowToRender
|
|
103
|
-
}, {
|
|
104
|
-
pageSize: paginationModel.pageSize,
|
|
105
|
-
rowCount: pageRowCount
|
|
106
|
-
});
|
|
107
132
|
let hasChanged = false;
|
|
108
|
-
const isInitialPage = renderedRowsIntervalCache.current.firstRowToRender === 0 && renderedRowsIntervalCache.current.lastRowToRender === 0;
|
|
109
|
-
for (let i = 0; i < rootChildrenCount; i += 1) {
|
|
110
|
-
if (isInitialPage) {
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
// replace the rows not in the viewport with skeleton rows
|
|
114
|
-
if (pageToSkip.start <= i && i <= pageToSkip.end || tree[rootGroupChildren[i]]?.type === 'skeletonRow' ||
|
|
115
|
-
// ignore rows that are already skeleton rows
|
|
116
|
-
tree[rootGroupChildren[i]]?.id === draggedRowId.current // ignore row that is being dragged (https://github.com/mui/mui-x/issues/17854)
|
|
117
|
-
) {
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
const rowId = tree[rootGroupChildren[i]].id; // keep the id, so that row related state is maintained
|
|
121
|
-
const skeletonRowNode = {
|
|
122
|
-
type: 'skeletonRow',
|
|
123
|
-
id: rowId,
|
|
124
|
-
parent: _xDataGrid.GRID_ROOT_GROUP_ID,
|
|
125
|
-
depth: 0
|
|
126
|
-
};
|
|
127
|
-
tree[rowId] = skeletonRowNode;
|
|
128
|
-
hasChanged = true;
|
|
129
|
-
}
|
|
130
133
|
|
|
134
|
+
// SWR: Only add skeleton padding for never-fetched positions beyond current data.
|
|
135
|
+
// Previously fetched rows are kept in place (not skeletonized) to avoid flicker on scroll-back.
|
|
131
136
|
// Should only happen with VIEWPORT loading trigger
|
|
132
137
|
if (loadingTrigger.current === LoadingTrigger.VIEWPORT) {
|
|
133
138
|
// fill the grid with skeleton rows
|
|
@@ -193,39 +198,121 @@ const useGridDataSourceLazyLoader = (privateApiRef, props) => {
|
|
|
193
198
|
const filteredSortedRowIds = (0, _xDataGrid.gridFilteredSortedRowIdsSelector)(privateApiRef);
|
|
194
199
|
const startingIndex = typeof fetchParams.start === 'string' ? Math.max(filteredSortedRowIds.indexOf(fetchParams.start), 0) : fetchParams.start;
|
|
195
200
|
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
201
|
+
// Determine if this is a background revalidation (target rows are real, not skeletons)
|
|
202
|
+
const firstTargetRow = rootGroupChildren[startingIndex];
|
|
203
|
+
const isRevalidation = firstTargetRow && tree[firstTargetRow]?.type !== 'skeletonRow';
|
|
204
|
+
if (isRevalidation) {
|
|
205
|
+
// --- SWR PATH ---
|
|
206
|
+
// Compare response row IDs with existing row IDs at target positions
|
|
207
|
+
const newRowIds = response.rows.map(row => (0, _xDataGrid.gridRowIdSelector)(privateApiRef, row));
|
|
208
|
+
const existingRowIds = rootGroupChildren.slice(startingIndex, startingIndex + response.rows.length);
|
|
209
|
+
const sameRowIds = existingRowIds.length === newRowIds.length && existingRowIds.every((id, i) => id === newRowIds[i]);
|
|
210
|
+
if (sameRowIds) {
|
|
211
|
+
// SAME ROW IDs — check for data changes only
|
|
212
|
+
const changedRows = response.rows.filter((newRow, i) => {
|
|
213
|
+
const existingRow = dataRowIdToModelLookup[existingRowIds[i]];
|
|
214
|
+
return !(0, _isDeepEqual.isDeepEqual)(newRow, existingRow);
|
|
215
|
+
});
|
|
216
|
+
if (changedRows.length === 0) {
|
|
217
|
+
// No changes — skip update entirely. Cache already refreshed by fetchRows.
|
|
218
|
+
privateApiRef.current.setLoading(false);
|
|
219
|
+
return;
|
|
211
220
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
221
|
+
|
|
222
|
+
// Efficient data-only update — no tree restructuring needed
|
|
223
|
+
privateApiRef.current.updateRows(changedRows);
|
|
224
|
+
// Cache is already updated by fetchRows in useGridDataSourceBase
|
|
225
|
+
} else {
|
|
226
|
+
// DIFFERENT ROW IDs — server returned new rows for this range
|
|
227
|
+
// 1. Remove old rows at target positions
|
|
228
|
+
for (let i = startingIndex; i < startingIndex + response.rows.length && i < rootGroupChildren.length; i += 1) {
|
|
229
|
+
const oldRowId = rootGroupChildren[i];
|
|
230
|
+
if (oldRowId && tree[oldRowId]?.type !== 'skeletonRow') {
|
|
231
|
+
delete tree[oldRowId];
|
|
232
|
+
delete dataRowIdToModelLookup[oldRowId];
|
|
233
|
+
const skeletonId = getSkeletonRowId(i);
|
|
234
|
+
rootGroupChildren[i] = skeletonId;
|
|
235
|
+
tree[skeletonId] = {
|
|
236
|
+
type: 'skeletonRow',
|
|
237
|
+
id: skeletonId,
|
|
238
|
+
parent: _xDataGrid.GRID_ROOT_GROUP_ID,
|
|
239
|
+
depth: 0
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// 2. Duplicate detection for incoming rows
|
|
245
|
+
let duplicateRowCount = 0;
|
|
246
|
+
response.rows.forEach(row => {
|
|
247
|
+
const rowId = (0, _xDataGrid.gridRowIdSelector)(privateApiRef, row);
|
|
248
|
+
if (tree[rowId] || dataRowIdToModelLookup[rowId]) {
|
|
249
|
+
const index = rootGroupChildren.indexOf(rowId);
|
|
250
|
+
if (index !== -1) {
|
|
251
|
+
const skeletonId = getSkeletonRowId(index);
|
|
252
|
+
rootGroupChildren[index] = skeletonId;
|
|
253
|
+
tree[skeletonId] = {
|
|
254
|
+
type: 'skeletonRow',
|
|
255
|
+
id: skeletonId,
|
|
256
|
+
parent: _xDataGrid.GRID_ROOT_GROUP_ID,
|
|
257
|
+
depth: 0
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
delete tree[rowId];
|
|
261
|
+
delete dataRowIdToModelLookup[rowId];
|
|
262
|
+
duplicateRowCount += 1;
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
if (duplicateRowCount > 0) {
|
|
266
|
+
tree[_xDataGrid.GRID_ROOT_GROUP_ID] = (0, _extends2.default)({}, rootGroup, {
|
|
267
|
+
children: rootGroupChildren
|
|
268
|
+
});
|
|
269
|
+
privateApiRef.current.setState(state => (0, _extends2.default)({}, state, {
|
|
270
|
+
rows: (0, _extends2.default)({}, state.rows, {
|
|
271
|
+
tree,
|
|
272
|
+
dataRowIdToModelLookup
|
|
273
|
+
})
|
|
274
|
+
}));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 3. Replace rows
|
|
278
|
+
privateApiRef.current.unstable_replaceRows(startingIndex, response.rows);
|
|
215
279
|
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
280
|
+
} else {
|
|
281
|
+
// --- ORIGINAL PATH (skeleton → real row replacement) ---
|
|
282
|
+
// Check for duplicate rows
|
|
283
|
+
let duplicateRowCount = 0;
|
|
284
|
+
response.rows.forEach(row => {
|
|
285
|
+
const rowId = (0, _xDataGrid.gridRowIdSelector)(privateApiRef, row);
|
|
286
|
+
if (tree[rowId] || dataRowIdToModelLookup[rowId]) {
|
|
287
|
+
const index = rootGroupChildren.indexOf(rowId);
|
|
288
|
+
if (index !== -1) {
|
|
289
|
+
const skeletonId = getSkeletonRowId(index);
|
|
290
|
+
rootGroupChildren[index] = skeletonId;
|
|
291
|
+
tree[skeletonId] = {
|
|
292
|
+
type: 'skeletonRow',
|
|
293
|
+
id: skeletonId,
|
|
294
|
+
parent: _xDataGrid.GRID_ROOT_GROUP_ID,
|
|
295
|
+
depth: 0
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
delete tree[rowId];
|
|
299
|
+
delete dataRowIdToModelLookup[rowId];
|
|
300
|
+
duplicateRowCount += 1;
|
|
301
|
+
}
|
|
220
302
|
});
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
|
|
303
|
+
if (duplicateRowCount > 0) {
|
|
304
|
+
tree[_xDataGrid.GRID_ROOT_GROUP_ID] = (0, _extends2.default)({}, rootGroup, {
|
|
305
|
+
children: rootGroupChildren
|
|
306
|
+
});
|
|
307
|
+
privateApiRef.current.setState(state => (0, _extends2.default)({}, state, {
|
|
308
|
+
rows: (0, _extends2.default)({}, state.rows, {
|
|
309
|
+
tree,
|
|
310
|
+
dataRowIdToModelLookup
|
|
311
|
+
})
|
|
312
|
+
}));
|
|
313
|
+
}
|
|
314
|
+
privateApiRef.current.unstable_replaceRows(startingIndex, response.rows);
|
|
227
315
|
}
|
|
228
|
-
privateApiRef.current.unstable_replaceRows(startingIndex, response.rows);
|
|
229
316
|
}
|
|
230
317
|
rowsStale.current = false;
|
|
231
318
|
if (loadingTrigger.current === null) {
|
|
@@ -237,8 +324,11 @@ const useGridDataSourceLazyLoader = (privateApiRef, props) => {
|
|
|
237
324
|
params: params.fetchParams,
|
|
238
325
|
response
|
|
239
326
|
}, false);
|
|
327
|
+
if (loadingTrigger.current === LoadingTrigger.VIEWPORT) {
|
|
328
|
+
startPolling(params.fetchParams);
|
|
329
|
+
}
|
|
240
330
|
privateApiRef.current.requestPipeProcessorsApplication('hydrateRows');
|
|
241
|
-
}, [privateApiRef, updateLoadingTrigger, addSkeletonRows]);
|
|
331
|
+
}, [privateApiRef, updateLoadingTrigger, addSkeletonRows, startPolling]);
|
|
242
332
|
const handleRowCountChange = React.useCallback(() => {
|
|
243
333
|
if (rowsStale.current || loadingTrigger.current === null) {
|
|
244
334
|
return;
|
|
@@ -296,26 +386,46 @@ const useGridDataSourceLazyLoader = (privateApiRef, props) => {
|
|
|
296
386
|
visibleRows: currentVisibleRows.rows,
|
|
297
387
|
range: renderContext
|
|
298
388
|
});
|
|
389
|
+
const paginationModel = (0, _xDataGrid.gridPaginationModelSelector)(privateApiRef);
|
|
299
390
|
if (!skeletonRowsSection) {
|
|
391
|
+
// SWR: No skeleton rows in viewport — all visible rows have real data.
|
|
392
|
+
// Schedule background revalidation if cache has expired for this range.
|
|
393
|
+
if (loadingTrigger.current === LoadingTrigger.VIEWPORT) {
|
|
394
|
+
const adjustedParams = (0, _utils.adjustRowParams)(getRowsParams, {
|
|
395
|
+
pageSize: paginationModel.pageSize,
|
|
396
|
+
rowCount: privateApiRef.current.state.pagination.rowCount
|
|
397
|
+
});
|
|
398
|
+
revalidate(adjustedParams);
|
|
399
|
+
startPolling(adjustedParams);
|
|
400
|
+
}
|
|
300
401
|
return;
|
|
301
402
|
}
|
|
302
403
|
getRowsParams.start = skeletonRowsSection.firstRowIndex;
|
|
303
404
|
getRowsParams.end = skeletonRowsSection.lastRowIndex;
|
|
304
|
-
const paginationModel = (0, _xDataGrid.gridPaginationModelSelector)(privateApiRef);
|
|
305
405
|
fetchRows((0, _utils.adjustRowParams)(getRowsParams, {
|
|
306
406
|
pageSize: paginationModel.pageSize,
|
|
307
407
|
rowCount: privateApiRef.current.state.pagination.rowCount
|
|
308
408
|
}));
|
|
309
|
-
}, [privateApiRef, fetchRows]);
|
|
409
|
+
}, [privateApiRef, fetchRows, revalidate, startPolling]);
|
|
310
410
|
const throttledHandleRenderedRowsIntervalChange = React.useMemo(() => (0, _throttle.throttle)(handleRenderedRowsIntervalChange, props.lazyLoadingRequestThrottleMs), [props.lazyLoadingRequestThrottleMs, handleRenderedRowsIntervalChange]);
|
|
311
411
|
React.useEffect(() => {
|
|
312
412
|
return () => {
|
|
313
413
|
throttledHandleRenderedRowsIntervalChange.clear();
|
|
414
|
+
stopPolling();
|
|
314
415
|
};
|
|
315
|
-
}, [throttledHandleRenderedRowsIntervalChange]);
|
|
416
|
+
}, [throttledHandleRenderedRowsIntervalChange, stopPolling]);
|
|
417
|
+
|
|
418
|
+
// Stop polling when dataSourceRevalidateMs is set to 0
|
|
419
|
+
React.useEffect(() => {
|
|
420
|
+
if (props.dataSourceRevalidateMs <= 0) {
|
|
421
|
+
stopPolling();
|
|
422
|
+
}
|
|
423
|
+
}, [props.dataSourceRevalidateMs, stopPolling]);
|
|
424
|
+
React.useEffect(() => stopPolling, [stopPolling]);
|
|
316
425
|
const handleGridSortModelChange = React.useCallback(newSortModel => {
|
|
317
426
|
rowsStale.current = true;
|
|
318
427
|
throttledHandleRenderedRowsIntervalChange.clear();
|
|
428
|
+
stopPolling();
|
|
319
429
|
previousLastRowIndex.current = 0;
|
|
320
430
|
const paginationModel = (0, _xDataGrid.gridPaginationModelSelector)(privateApiRef);
|
|
321
431
|
const filterModel = (0, _xDataGrid.gridFilterModelSelector)(privateApiRef);
|
|
@@ -327,10 +437,11 @@ const useGridDataSourceLazyLoader = (privateApiRef, props) => {
|
|
|
327
437
|
};
|
|
328
438
|
privateApiRef.current.setLoading(true);
|
|
329
439
|
debouncedFetchRows(getRowsParams);
|
|
330
|
-
}, [privateApiRef, debouncedFetchRows, throttledHandleRenderedRowsIntervalChange]);
|
|
440
|
+
}, [privateApiRef, debouncedFetchRows, throttledHandleRenderedRowsIntervalChange, stopPolling]);
|
|
331
441
|
const handleGridFilterModelChange = React.useCallback(newFilterModel => {
|
|
332
442
|
rowsStale.current = true;
|
|
333
443
|
throttledHandleRenderedRowsIntervalChange.clear();
|
|
444
|
+
stopPolling();
|
|
334
445
|
previousLastRowIndex.current = 0;
|
|
335
446
|
const paginationModel = (0, _xDataGrid.gridPaginationModelSelector)(privateApiRef);
|
|
336
447
|
const sortModel = (0, _xDataGrid.gridSortModelSelector)(privateApiRef);
|
|
@@ -342,7 +453,7 @@ const useGridDataSourceLazyLoader = (privateApiRef, props) => {
|
|
|
342
453
|
};
|
|
343
454
|
privateApiRef.current.setLoading(true);
|
|
344
455
|
debouncedFetchRows(getRowsParams);
|
|
345
|
-
}, [privateApiRef, debouncedFetchRows, throttledHandleRenderedRowsIntervalChange]);
|
|
456
|
+
}, [privateApiRef, debouncedFetchRows, throttledHandleRenderedRowsIntervalChange, stopPolling]);
|
|
346
457
|
const handleDragStart = React.useCallback(row => {
|
|
347
458
|
draggedRowId.current = row.id;
|
|
348
459
|
}, []);
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mui/x-data-grid-pro",
|
|
3
|
-
"version": "8.27.
|
|
3
|
+
"version": "8.27.4",
|
|
4
4
|
"author": "MUI Team",
|
|
5
5
|
"description": "The Pro plan edition of the MUI X Data Grid components.",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"@mui/utils": "^7.3.5",
|
|
38
38
|
"clsx": "^2.1.1",
|
|
39
39
|
"prop-types": "^15.8.1",
|
|
40
|
+
"@mui/x-internals": "8.26.0",
|
|
40
41
|
"@mui/x-license": "8.26.0",
|
|
41
|
-
"@mui/x-data-grid": "8.27.
|
|
42
|
-
"@mui/x-internals": "8.26.0"
|
|
42
|
+
"@mui/x-data-grid": "8.27.4"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"@emotion/react": "^11.9.0",
|