Skip to content

[data grid] Fix noRowsOverlay flicker between dataSource re-fetches#22465

Open
LukasTy wants to merge 2 commits into
mui:masterfrom
LukasTy:claude/blissful-ishizaka-570f57
Open

[data grid] Fix noRowsOverlay flicker between dataSource re-fetches#22465
LukasTy wants to merge 2 commits into
mui:masterfrom
LukasTy:claude/blissful-ishizaka-570f57

Conversation

@LukasTy
Copy link
Copy Markdown
Member

@LukasTy LukasTy commented May 15, 2026

Fixes #22458
Closes #22460.

Summary

  • Fixes the brief "No rows" overlay flash that appears between pagination, sort, filter, and row-grouping changes when using the dataSource API. The flash is especially visible on cached navigation, where the response is available synchronously but a stale empty render still appears for a frame. Reported in Previous cache entry not persisted during dataSource.fetchRows calls resulting in no rows flicker #22458.
  • Adds a new opt-in dataSourceKeepPreviousData prop that keeps the previously displayed rows visible during the fetch (mirrors TanStack Query's placeholderData: keepPreviousData and SWR's keepPreviousData).

Root cause

The navigation event handlers (handleFetchRowsOnParamsChange in useGridDataSourceBase and handleRowGroupingModelChange in useGridDataSourcePremium) called apiRef.current.setRows([]) synchronously and queued debouncedFetchRows() afterwards. Inside useGridRows, setRows rebuilds state.rows from props.loading via getRowsStateFromCache. Because dataSource grids don't pass a loading prop, that rebuild reset state.rows.loading to undefined, exposing a render with rows = [] and loading = false. The overlay selector then picked noRowsOverlay for that single frame.

Fix

In both navigation handlers, call setLoading(true) immediately after setRows([]) so the loading flag survives the setRows rebuild. The first render after the click now has rows = [] and loading = true, which picks loadingOverlay (skeleton variant) instead of noRowsOverlay. The cache-hit branch of fetchRows now also calls setLoading(false) so the loading flag the handler just set is cleared when the cached response is applied synchronously.

The new dataSourceKeepPreviousData prop guards the setRows([]) call so the previous rows stay visible during the fetch and the loading overlay (linear-progress variant, since rows are non-empty) is shown on top.

Test plan

  • Added unit tests in dataSource.DataGrid.test.tsx covering cache-hit, cache-miss, empty-response, and the new dataSourceKeepPreviousData opt-in.
  • pnpm test:unit --project "x-data-grid*" --run — 1612 passed.
  • pnpm typescript clean across the monorepo.
  • pnpm eslint, pnpm prettier, pnpm markdownlint clean.
  • Manual verification on the Data Source demo: clicking back and forth between pages no longer flashes the "No rows" overlay, and toggling dataSourceKeepPreviousData on the new demo keeps the previous rows visible with a linear-progress overlay on top.

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) <noreply@anthropic.com>
@code-infra-dashboard
Copy link
Copy Markdown

code-infra-dashboard Bot commented May 15, 2026

Deploy preview

Bundle size

Bundle Parsed size Gzip size
@mui/x-data-grid 🔺+250B(+0.06%) 🔺+56B(+0.05%)
@mui/x-data-grid-pro 🔺+309B(+0.06%) 🔺+80B(+0.05%)
@mui/x-data-grid-premium 🔺+351B(+0.05%) 🔺+78B(+0.04%)
@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-license 0B(0.00%) 0B(0.00%)

Details of bundle changes


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

@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 May 15, 2026
@LukasTy LukasTy self-assigned this May 15, 2026
@LukasTy LukasTy changed the title [data-grid] Fix noRowsOverlay flicker between dataSource re-fetches [data grid] Fix noRowsOverlay flicker between dataSource re-fetches May 15, 2026
@LukasTy LukasTy marked this pull request as ready for review May 15, 2026 14:45
…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) <noreply@anthropic.com>
@GMchris
Copy link
Copy Markdown
Contributor

GMchris commented May 17, 2026

Hey Lukas, Im the original reporter of this issue. I figured I'll share my thinking here instead of my own PR since this one is more comprehensive anyway.

Im personally happy with this approach and would love to have any resolution honestly. Just two things to consider:

  1. By making this prop and defaulting it to false I'd argue you're maintaining behavior (thus not a breaking change -good) but making a worse overall default behavior. Do you really want to make the default behavior actually include this flash of no rows?
  2. I guess the ship may have sailed since this isnt the only "dataSource" related prop, but don't these props make more sense as part o the dataSource object? The one that accepts getRows, like so:
<DataGrid
dataSource={{
   fetchRows: ...,
  keepPreviousData: true
}}

The second is more subjective, but for the first I'll offer an alternative. You can keep the previous behavior (don't persist previous data) and get rid of the flash by removing the "debounce" on the fetchRows. Im guessing its there for a reason, maybe the original implementation didn't want to call setRows([]) and then setRows(someRows) immediately after, but removing this debounce, I can confirm, fixes the flash without needing to persist rows.

My ideal solution would be to do both. Remove debounce (if acceptable) to improve default behavior for everyone out of the box and add this new prop for people like me who prefer keeping previous rows mounted for aesthetic purposes.

@LukasTy
Copy link
Copy Markdown
Member Author

LukasTy commented May 18, 2026

@GMchris thank you for the response.
I agree with both points.
If other Data Grid maintainers agree, we could add it to the object config.

In regards to the No Rows flashing, isn't it fixed: https://deploy-preview-22465--material-ui-x.netlify.app/x/react-data-grid/server-side-data/#with-the-data-source? Or can you still reproduce it in this demo?

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.

Previous cache entry not persisted during dataSource.fetchRows calls resulting in no rows flicker

2 participants