[DataGrid] Add dataSourceKeepPreviousData prop#22554
Conversation
The navigation event handlers cleared rows synchronously before queueing the fetch. Because `setRows` rebuilds `state.rows` from `props.loading`, the synchronous clear also reset `loading` to false, exposing a render with `rows = []` and `loading = false` that triggered `noRowsOverlay`. The flash was most visible when navigating between already-cached pages. Now the handlers call `setLoading(true)` after `setRows([])`, so the loading state survives the `setRows` rebuild and the overlay selector picks `loadingOverlay` instead. Add the `dataSourceKeepPreviousData` prop to opt into keeping the previously displayed rows visible during the fetch (mirrors React Query's `placeholderData: keepPreviousData`). Fixes mui#22458 Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…ousData` The error path in `handleDataUpdate` and `handleGroupedDataUpdate` cleared rows unconditionally, so a rejected refetch wiped the last known-good data even when `dataSourceKeepPreviousData` was on — the opposite of what users opt into the prop for. Gate both `setRows([])` calls behind the prop so a failed refetch leaves the previous rows visible while `onDataSourceError` delivers the error. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…mmetry The community handler had a redundant `if (standardRowsUpdateStrategyActive)` guard inside the callback even though the event listener is already wired with `runIf(standardRowsUpdateStrategyActive, ...)`. Drop the inner guard and add a comment so the gating intent is explicit. This also restores the pre-PR behavior of unconditionally clearing rows on non-standard strategies (the outer `runIf` skips the handler entirely instead). The Premium row-grouping handler is wired without a strategy check at the event level, so it could call `setLoading(true)` while `fetchRows` would not call the matching `setLoading(false)` (cleared only on standard strategies). Add an inline strategy check there so the loading flag is only flipped when `fetchRows` will eventually clear it again. Tests: - Add `state.rows.loading === false` assertion to the error-recovery test. - Add Premium row-grouping regression tests that exercise the loading toggle and the keep-previous-data behavior on grouping model change. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Per review on mui#22465 (mui#22465 (comment)), the opt-in prop has the same shape as the bug mui#21619 fixed: skipping the synchronous `setRows([])` would leave tree-data sort/filter changes rendering rows in stale order until the user collapsed/re-expanded. Keep this PR focused on the reported `noRowsOverlay` flicker and ship the controllable prop in a follow-up that handles the tree-data interaction. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
When `true`, the Data Grid keeps the previously displayed rows visible while new rows are being fetched after pagination, sorting, or filtering changes, rather than blanking the grid and showing the skeleton loading overlay. The loading overlay (linear-progress variant, since rows are non-empty) is rendered on top of the previous rows. Mirrors React Query's `placeholderData: keepPreviousData` and SWR's `keepPreviousData`. Scope: the opt-in only applies to the flat `Default` strategy. For tree-data and row grouping (`GroupedData` strategy), the Data Grid always resets the rows on refetch — otherwise the existing tree would be merged on top of the new response via `updateRows` and rows would render in stale sort order (the regression mui#21619 fixed by adding the synchronous `setRows([])` to `handleFetchRowsOnParamsChange`). Tests: - Flat data: previous rows stay visible during fetch; loading overlay does not switch to `noRowsOverlay`; previous rows persist on error. - Tree data (regression coverage for mui#21619): root rows are still re-ordered on sort change when `dataSourceKeepPreviousData` is enabled. Builds on mui#22465. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Deploy previewBundle sizeℹ️ Using snapshot from parent commit d575a01 (fallback from merge base 0cb49d9).
Check out the code infra dashboard for more information about this PR. |
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
…-keep-previous-data # Conflicts: # packages/x-data-grid-premium/src/hooks/features/dataSource/useGridDataSourcePremium.tsx # packages/x-data-grid-premium/src/tests/dataSourceRowGrouping.DataGridPremium.test.tsx # packages/x-data-grid/src/hooks/features/dataSource/useGridDataSourceBase.ts # packages/x-data-grid/src/tests/dataSource.DataGrid.test.tsx
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
|
I just wanted to check if there's something blocking this work as the PR is in Draft, or is it just a matter of priority |
…-keep-previous-data # Conflicts: # packages/x-data-grid/src/DataGrid/DataGrid.tsx
Follow-up polish from a readiness review of the feature: - Add an inverse default-clears test: with the prop omitted, assert the rows drop to 0 while the refetch is in flight. Locks the core contract so an accidental flip of the default (or inversion of the `!keepPreviousData` guard) is caught. - Assert `state.rows.loading === true` while the previous rows are retained, positively confirming the loading overlay renders on top of the rows (not just that `noRowsOverlay` is absent). - Document the error-path caveat: on a failed refetch the previous rows stay visible while pagination/sorting/filtering already reflect the new request, so the displayed rows may not match the current query state; point consumers at `onDataSourceError`. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The `icon` prop is `React.JSXElementConstructor<...> | React.ReactNode`. Under React 19, `ReactNode` expands to a union that includes `Iterable`, `bigint`, and `Promise`, which `typescript-to-proptypes` serializes using TypeScript's internal `Symbol.iterator` / `Symbol.toStringTag` ids (e.g. `__@iterator@659`). Those ids are assigned lazily by the TS checker, so they shift whenever the package's type graph changes -- any unrelated prop addition flips `659` to `660` and forces a churn diff in this file, failing the `pnpm proptypes` CI check. This was the only file in the repo carrying these volatile symbol ids. Pin a stable, faithful representation with the established `@typescript-to-proptypes-ignore` directive (as used in DataGrid.tsx, GridPanel.tsx, the pickers inputs, etc.), collapsing the prop to `oneOfType([elementType, node])`. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The demo intentionally sets a 500-1500ms mock-server delay so the keep-previous-data behavior is visible when paginating. An explicit delay bypasses the regression build's delay-zeroing (`__DISABLE_CHANCE_RANDOM__` -> `DEFAULT_SERVER_OPTIONS` delays of 0), and the screenshot harness's `aria-busy` gate only tracks font loading, not async demo data -- so the initial skeleton overlay was being captured instead of the loaded grid. Add a `waitForSelector` rule that waits for a real, non-skeleton row before screenshotting, mirroring the existing GridToolbarCustom handling. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Thank you for bringing this topic up. I refreshed and finalized the PR. |
There was a problem hiding this comment.
Pull request overview
Adds a new dataSourceKeepPreviousData boolean prop to the Data Grid Data Source integration, allowing previously rendered rows to remain visible while a new fetch is in flight (pagination/sort/filter changes). The implementation is explicitly scoped to the flat Default rows update strategy so tree data / row grouping continue to clear rows synchronously to preserve correct ordering (per the prior tree-data regression fix).
Changes:
- Introduces
dataSourceKeepPreviousDataacross DataGrid / Pro / Premium (types, defaults, propTypes, and API docs). - Updates the community data source strategy processor + params-change handler to honor the prop only for the
Defaultstrategy and to keep previous rows on fetch errors in that strategy. - Adds test coverage (community + Pro tree-data regression) and a new docs demo + docs section describing behavior and caveats.
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| test/regressions/index.test.ts | Adds a screenshot regression rule to wait for real rows in the new demo route. |
| packages/x-data-grid/src/tests/dataSource.DataGrid.test.tsx | Adds unit tests covering keep-previous-rows during fetch, default behavior, overlay behavior, and error behavior. |
| packages/x-data-grid/src/models/props/DataGridProps.ts | Adds the new prop to the DataGrid processed/defaulted props typing and JSDoc. |
| packages/x-data-grid/src/hooks/features/dataSource/useGridDataSourceBase.ts | Implements the behavior (strategy-gated keep-previous-rows + preserve rows on default-strategy errors). |
| packages/x-data-grid/src/DataGrid/DataGrid.tsx | Adds propTypes entry for the new prop. |
| packages/x-data-grid/src/constants/dataGridPropsDefaultValues.ts | Sets the default value for dataSourceKeepPreviousData to false. |
| packages/x-data-grid/src/components/cell/GridActionsCellItem.tsx | PropTypes generation adjustment for icon (unrelated to the data-source feature). |
| packages/x-data-grid-pro/src/tests/dataSourceTreeData.DataGridPro.test.tsx | Adds tree-data regression test ensuring root order updates correctly even when the prop is enabled. |
| packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx | Adds propTypes entry for the new prop. |
| packages/x-data-grid-premium/src/hooks/features/dataSource/useGridDataSourcePremium.tsx | Clarifies that row-grouping model changes still clear rows (prop intentionally not honored). |
| packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx | Adds propTypes entry for the new prop. |
| docs/translations/api-docs/data-grid/data-grid/data-grid.json | Documents the new prop in API translations (community). |
| docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json | Documents the new prop in API translations (pro). |
| docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json | Documents the new prop in API translations (premium). |
| docs/pages/x/api/data-grid/data-grid.json | Adds prop metadata to generated API page JSON (community). |
| docs/pages/x/api/data-grid/data-grid-pro.json | Adds prop metadata to generated API page JSON (pro). |
| docs/pages/x/api/data-grid/data-grid-premium.json | Adds prop metadata to generated API page JSON (premium). |
| docs/data/data-grid/server-side-data/ServerSideDataGridKeepPreviousData.tsx.preview | Adds preview snippet for the new demo. |
| docs/data/data-grid/server-side-data/ServerSideDataGridKeepPreviousData.tsx | Adds a new demo showcasing keep-previous-data behavior with mock delay. |
| docs/data/data-grid/server-side-data/ServerSideDataGridKeepPreviousData.js | Adds the generated JS version of the new demo for docs consumption. |
| docs/data/data-grid/server-side-data/index.md | Adds a new docs section describing the prop, failure-mode caveat, and tree/row-grouping limitation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
packages/x-data-grid/src/hooks/features/dataSource/useGridDataSourceBase.ts - dataSource identity-change effect clears rows unconditionally, silently defeating keepPreviousData
A useEffect at lines 421–443 fires whenever props.dataSource changes and calls apiRef.current.setRows([]) at line 434 unconditionally — no guard for props.dataSourceKeepPreviousData. The PR does not touch this effect. When dataSource is defined as an inline object literal (users tend to do this frequenty, although it is discouraged to do so, dataSource={{ getRows: myFn }}), every parent re-render produces a new reference, this effect fires, and all rows are cleared regardless of keepPreviousData=true. The feature is silently a no-op for any user who doesn't memoize dataSource. The new docs section does not mention this memoization requirement. The fix is a one-liner: wrap line 434 with if (!props.dataSourceKeepPreviousData) { ... }, mirroring the guard already added to handleFetchRowsOnParamsChange.
packages/x-data-grid/src/hooks/features/dataSource/useGridDataSourceBase.ts and packages/x-data-grid-premium/src/hooks/features/dataSource/useGridDataSourcePremium.tsx - no dev-mode warning when keepPreviousData=true is silently ignored for tree data / row grouping
When dataSourceKeepPreviousData=true is used with tree data or row grouping, the prop is silently a no-op — rows are still cleared. The PR adds a docs callout but no runtime warnOnce. The project already uses warnOnce for this class of issue: packages/x-data-grid/src/hooks/features/listView/useGridListView.tsx:80 warns when a prop/mode combination is unsupported. A developer who hits this will see nothing in the console and will have to trace through the docs to find the limitation. A warnOnce under process.env.NODE_ENV !== 'production' when props.dataSourceKeepPreviousData === true and activeStrategy !== DataSourceRowsUpdateStrategy.Default would match the established pattern.
Two findings from mui#22554 (review): 1. The `dataSource`-identity effect cleared rows unconditionally on every `dataSource` reference change, silently defeating `keepPreviousData` (e.g. for a non-memoized inline `dataSource`). Guard the `setRows([])` there so the previous rows are kept during the refetch. The guard is strategy-scoped to the flat `Default` strategy (not the reviewer's plain prop-only check): this effect fires for all strategies, and tree data / row grouping must still reset to keep row order consistent with the response (mui#21619). 2. `keepPreviousData` is silently a no-op for tree data / row grouping. Add a dev-mode `warnOnce` (matching the established `useGridListView` pattern) so the limitation is discoverable at runtime, not only in the docs. Tests: - Keep previous rows on a `dataSource` reference change (flat data). - Warn when `keepPreviousData` is combined with row grouping (Premium) and tree data (Pro regression test now asserts the warning). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
Addressed both points (commit a952e11):
Two things I left out on purpose:
|
…usData` Per review (mui#22554 (review)), a failed refetch now resets the rows even with `dataSourceKeepPreviousData`, instead of keeping the previous rows. The previous rows belong to the previous query while the pagination/sorting/filtering controls already reflect the failed request, so keeping them presents stale data as if it satisfied the new query. This also matches TanStack Query, which clears the `keepPreviousData` placeholder once the query settles with an error. `keepPreviousData` still keeps the previous rows during the in-flight fetch (the actual feature); only the error-settle path changes. Update the test and docs accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Per review (mui#22554 (comment)), `oneOfType([elementType, node])` allowed component types (functions), which break at runtime: the non-menu variant clones the icon via `React.cloneElement(icon!)` (its type is `React.ReactElement`), and the menu variant renders it as a node -- a function child throws "Functions are not valid as a React child". Every in-repo usage passes an element. Tighten to `PropTypes.element`, keeping the `@typescript-to-proptypes-ignore` directive that stabilizes the generated proptype. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Follow-up to #22465.
Summary
Adds
dataSourceKeepPreviousData-- a boolean prop that keeps the previously displayed rows visible while new rows are being fetched after pagination, sorting, or filtering changes. The loading overlay (linear-progress variant, since rows are non-empty) is rendered on top of the previous rows. MirrorsplaceholderData: keepPreviousDatain TanStack Query andkeepPreviousDatain SWR.This was originally part of #22465 but was split out per @MBilalShafi's review because the prop has a non-trivial interaction with tree data that this PR now handles explicitly.
Tree-data interaction
#21619 had to add the synchronous
setRows([])call tohandleFetchRowsOnParamsChangebecause tree-data withkeepChildrenExpanded=true(the default) goes throughupdateRows, which merges new rows into the existing tree but keeps their old order. Without the clear, a sort/filter change rendered the new data in the stale order until the user collapsed and re-expanded.Skipping
setRows([])unconditionally -- as the original draft of this prop did -- would reopen exactly that hole. This PR scopes the opt-in to the flatDefaultstrategy via an inline strategy check inhandleFetchRowsOnParamsChange:GroupedData(tree-data / row grouping) still callssetRows([])on params change, so the #21619 fix is preserved. The same flat-only scoping is applied to the two other reset paths: thehandleDataUpdateerror processor (so previous rows survive a rejected refetch on flat data) and thedataSource-identity effect (so adataSourcereference change -- e.g. a non-memoized inline object -- keeps the previous rows for flat data while still resetting tree data / row grouping).When
dataSourceKeepPreviousDatais combined with tree data or row grouping, the docs flag the limitation with a warning callout and a dev-modewarnOncefires at runtime so it is discoverable without reading the docs.Behavior on a failed refetch
With the prop on, a rejected refetch resets the rows rather than keeping the previous ones -- they no longer match the pagination/sorting/filtering controls, which already reflect the failed request. This matches TanStack Query, which clears the
keepPreviousDataplaceholder once the query settles with an error. Consumers handle the failure throughonDataSourceError.Test plan
dataSource.DataGrid.test.tsx: previous rows stay visible during the pagination fetch (withstate.rows.loading === true) and during adataSourcereference change,noRowsOverlayis not rendered during fetch, rows are reset whengetRows()rejects, and -- with the prop omitted -- rows are still cleared to 0 during the in-flight refetch (guards the default).dataSourceTreeData.DataGridPro.test.tsx: regression test mirroring #21619 -- root rows are still re-ordered on sort change withdataSourceKeepPreviousData={true}and expanded children present, and the dev-mode warning is asserted. Confirms the prop is a no-op for tree data.dataSourceRowGrouping.DataGridPremium.test.tsx: asserts the dev-mode warning when the prop is combined with row grouping.pnpm test:unit --project "x-data-grid*" --runclean (25 community dataSource tests).pnpm test:browser --project "x-data-grid-pro" --run dataSourceTreeDataclean (18 tests, incl. the regression above).pnpm typescriptclean across the monorepo.pnpm eslint,pnpm prettier,pnpm valelintclean.