Skip to content

[DataGrid] Add dataSourceKeepPreviousData prop#22554

Merged
LukasTy merged 17 commits into
mui:masterfrom
LukasTy:claude/datasource-keep-previous-data
Jul 3, 2026
Merged

[DataGrid] Add dataSourceKeepPreviousData prop#22554
LukasTy merged 17 commits into
mui:masterfrom
LukasTy:claude/datasource-keep-previous-data

Conversation

@LukasTy

@LukasTy LukasTy commented May 21, 2026

Copy link
Copy Markdown
Member

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. Mirrors placeholderData: keepPreviousData in TanStack Query and keepPreviousData in 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 to handleFetchRowsOnParamsChange because tree-data with keepChildrenExpanded=true (the default) goes through updateRows, 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 flat Default strategy via an inline strategy check in handleFetchRowsOnParamsChange:

const activeStrategy = apiRef.current.getActiveStrategy(GridStrategyGroup.DataSource);
const keepPreviousData =
  props.dataSourceKeepPreviousData && activeStrategy === DataSourceRowsUpdateStrategy.Default;

GroupedData (tree-data / row grouping) still calls setRows([]) on params change, so the #21619 fix is preserved. The same flat-only scoping is applied to the two other reset paths: the handleDataUpdate error processor (so previous rows survive a rejected refetch on flat data) and the dataSource-identity effect (so a dataSource reference change -- e.g. a non-memoized inline object -- keeps the previous rows for flat data while still resetting tree data / row grouping).

When dataSourceKeepPreviousData is combined with tree data or row grouping, the docs flag the limitation with a warning callout and a dev-mode warnOnce fires 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 keepPreviousData placeholder once the query settles with an error. Consumers handle the failure through onDataSourceError.

Test plan

  • dataSource.DataGrid.test.tsx: previous rows stay visible during the pagination fetch (with state.rows.loading === true) and during a dataSource reference change, noRowsOverlay is not rendered during fetch, rows are reset when getRows() 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 with dataSourceKeepPreviousData={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*" --run clean (25 community dataSource tests).
  • pnpm test:browser --project "x-data-grid-pro" --run dataSourceTreeData clean (18 tests, incl. the regression above).
  • pnpm typescript clean across the monorepo.
  • pnpm eslint, pnpm prettier, pnpm valelint clean.
  • Manual verification on the new demo: clicking back and forth between pages keeps the previous rows visible with a linear-progress overlay on top.

LukasTy and others added 6 commits May 15, 2026 14:24
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]>
@code-infra-dashboard

code-infra-dashboard Bot commented May 21, 2026

Copy link
Copy Markdown

Deploy preview

Bundle size

ℹ️ Using snapshot from parent commit d575a01 (fallback from merge base 0cb49d9).

Bundle Parsed size Gzip size
@mui/x-data-grid 🔺+285B(+0.07%) 🔺+70B(+0.06%)
@mui/x-data-grid-pro 🔺+285B(+0.05%) 🔺+60B(+0.04%)
@mui/x-data-grid-premium 🔺+285B(+0.04%) 🔺+63B(+0.03%)
@mui/x-charts 0B(0.00%) 0B(0.00%)
@mui/x-charts-pro 0B(0.00%) 0B(0.00%)
@mui/x-charts-premium 0B(0.00%) 0B(0.00%)
@mui/x-date-pickers 0B(0.00%) 0B(0.00%)
@mui/x-date-pickers-pro 0B(0.00%) 0B(0.00%)
@mui/x-tree-view 0B(0.00%) 0B(0.00%)
@mui/x-tree-view-pro 0B(0.00%) 0B(0.00%)
@mui/x-scheduler 🔺+1.32KB(+0.37%) 🔺+301B(+0.31%)
@mui/x-scheduler-premium 🔺+3.18KB(+0.67%) 🔺+813B(+0.62%)
@mui/x-license 0B(0.00%) 0B(0.00%)

Details of bundle changes


Check out the code infra dashboard for more information about this PR.

@github-actions github-actions Bot added the PR: out-of-date The pull request has merge conflicts and can't be merged. label May 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@LukasTy LukasTy added scope: data grid Changes related to the data grid. type: enhancement It’s an improvement, but we can’t make up our mind whether it's a bug fix or a new feature. labels Jun 3, 2026
@LukasTy LukasTy self-assigned this Jun 3, 2026
…-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
@github-actions github-actions Bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged. label Jun 3, 2026
@github-actions github-actions Bot added the PR: out-of-date The pull request has merge conflicts and can't be merged. label Jun 15, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@GMchris

GMchris commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

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
@github-actions github-actions Bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged. label Jun 24, 2026
LukasTy and others added 4 commits June 24, 2026 12:23
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]>
@LukasTy
LukasTy marked this pull request as ready for review June 24, 2026 13:18
@LukasTy

LukasTy commented Jun 24, 2026

Copy link
Copy Markdown
Member Author

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

Thank you for bringing this topic up. I refreshed and finalized the PR.
It's ready for review. 👍

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 dataSourceKeepPreviousData across 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 Default strategy 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.

@michelengelen michelengelen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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]>
@LukasTy
LukasTy requested a review from michelengelen June 30, 2026 14:06
@LukasTy

LukasTy commented Jun 30, 2026

Copy link
Copy Markdown
Member Author

Addressed both points (commit a952e11):

  1. The dataSource-identity effect now keeps the previous rows on a dataSource reference change instead of clearing them unconditionally, so the feature isn't silently defeated for a non-memoized dataSource. The guard is scoped to the flat Default strategy rather than a plain !keepPreviousData check: this effect fires for every strategy, and tree data / row grouping must still reset on refetch to keep row order consistent with the response ([DataGridPro] Fix sorting not reflected in nested server-side data #21619). Added a test covering the dataSource ref change.

  2. Added a dev-mode warnOnce (matching the useGridListView pattern) when keepPreviousData is combined with tree data or row grouping, plus tests (Premium row grouping; the Pro tree-data regression test now asserts it).

Two things I left out on purpose:

  • The warning is scoped to tree data and row grouping (GroupedData / LazyLoadedGroupedData) to match the documented limitation. I did not extend it to flat lazy loading: that is outside the docs callout's scope and the [DataGridPro] Fix sorting not reflected in nested server-side data #21619 row-order reasoning does not apply there. Happy to add it as a follow-up if you want full coverage of every non-flat mode.

  • I did not add a "memoize your dataSource" note to the docs. That is a general dataSource concern rather than keepPreviousData-specific, and the guard above already removes the silent no-op for the realistic occasional ref-change case. (A truly every-render non-memoized dataSource still causes a refetch loop, but that is pre-existing and affects all dataSource users.)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 1 comment.

Comment thread packages/x-data-grid/src/components/cell/GridActionsCellItem.tsx Outdated
Comment thread docs/data/data-grid/server-side-data/index.md Outdated
LukasTy and others added 3 commits July 1, 2026 12:57
…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]>
@LukasTy
LukasTy merged commit 85188d5 into mui:master Jul 3, 2026
21 checks passed
@LukasTy
LukasTy deleted the claude/datasource-keep-previous-data branch July 3, 2026 06:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: data grid Changes related to the data grid. type: enhancement It’s an improvement, but we can’t make up our mind whether it's a bug fix or a new feature.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants