mui-x icon indicating copy to clipboard operation
mui-x copied to clipboard

[data grid] Scroll position jumps when scrolling with detail panels that have auto height and a dynamic content

Open vladislav-gorovenko opened this issue 1 month ago • 11 comments

Steps to reproduce

Steps:

  1. Create a DataGridPremium with expandable detail panels (getDetailPanelContent, getDetailPanelHeight={() => 'auto'})
  2. Inside the detail panel, render another DataGridPremium with expandable rows
  3. Expand multiple rows in the parent table (detail panels have dynamic heights based on nested expanded rows)
  4. Scroll up/down through the parent table
  5. Observe scroll position jumping as new rows enter viewport

https://codesandbox.io/p/sandbox/mnwvzc

Current behavior

Scroll position jumps when scrolling, especially when new rows are virtualized into view. The issue occurs when detail panels have dynamic heights that change based on expanded rows in nested tables.

https://github.com/user-attachments/assets/a3c2e335-ca8c-4b3d-be5e-c19a7685d5dc

Expected behavior

Scroll position should remain stable when scrolling, even when rows are virtualized and detail panel heights are dynamically calculated.

Context

We're building an order management interface where users need to:

  • View a list of orders in a table
  • Expand an order to see its order items (nested table)
  • Expand an order item to see its details (third-level nested table)

Each level uses dynamic heights because the number of items/details varies per order/item. Users need to scroll through many orders while keeping some expanded to compare items across orders. The scroll jumping makes this workflow difficult and causes user frustration.

https://github.com/user-attachments/assets/a3c2e335-ca8c-4b3d-be5e-c19a7685d5dc

Your environment

npx @mui/envinfo
  Used Chrome.
  System:
    OS: macOS 26.0
  Binaries:
    Node: 20.19.4 - /Users/martinedeniam/.nvm/versions/node/v20.19.4/bin/node
    npm: 10.4.0 - /usr/local/bin/npm
    pnpm: Not Found
  Browsers:
    Chrome: 142.0.7444.176
    Edge: Not Found
    Firefox: Not Found
    Safari: 26.0
  npmPackages:
    @emotion/react: 11.11.1 => 11.11.1 
    @emotion/styled: 11.11.0 => 11.11.0 
    @mui/core-downloads-tracker:  7.2.0 
    @mui/icons-material: 7.2.0 => 7.2.0 
    @mui/lab: 7.0.0-beta.14 => 7.0.0-beta.14 
    @mui/material: 7.2.0 => 7.2.0 
    @mui/private-theming:  7.2.0 
    @mui/styled-engine:  7.2.0 
    @mui/system:  7.2.0 
    @mui/types:  7.4.4 
    @mui/utils:  7.2.0 
    @mui/x-data-grid: 8.6.0 => 8.6.0 
    @mui/x-data-grid-generator: 8.6.0 => 8.6.0 
    @mui/x-data-grid-premium: 8.6.0 => 8.6.0 
    @mui/x-data-grid-pro: 8.6.0 => 8.6.0 
    @mui/x-date-pickers: 8.6.0 => 8.6.0 
    @mui/x-date-pickers-pro: 8.6.0 => 8.6.0 
    @mui/x-internals:  8.6.0 
    @mui/x-license: 8.6.0 => 8.6.0 
    @mui/x-telemetry:  8.5.3 
    @types/react: 18.2.33 => 18.2.33 
    react: 18.2.0 => 18.2.0 
    react-dom: 18.2.0 => 18.2.0 
    typescript: 5.2.2 => 5.2.2

Search keywords: scroll jump, detail panel, nested data grid, virtualization, dynamic height, scroll reset

Order ID: can be provided privately upon request.

vladislav-gorovenko avatar Nov 20 '25 10:11 vladislav-gorovenko

I'm facing a similar problem. If virtualization is disabled, taking into account the fact that after each click on a row, the entire table is re-rendered, nested tables become impossible to use.

SergeiKachenia avatar Nov 20 '25 14:11 SergeiKachenia

Thanks for reporting this @vladislav-gorovenko I will take a look.

arminmeh avatar Nov 24 '25 15:11 arminmeh

@vladislav-gorovenko, a couple of questions.

  1. Is it acceptable for you to have a grid without virtualization (but with proper scrolling)?
  2. Do you know the number of rows in the details panel upfront?
  3. Did you consider using Tree data or the Row grouping feature to achieve the same?

arminmeh avatar Nov 25 '25 08:11 arminmeh

@vladislav-gorovenko, a couple of questions.

  1. Is it acceptable for you to have a grid without virtualization (but with proper scrolling)?
  2. Do you know the number of rows in the details panel upfront?
  3. Did you consider using Tree data or the Row grouping feature to achieve the same?

1. Disabling virtualization isn’t an option. We sometimes load 800+ rows, and without virtualization the grid becomes noticeably laggy when selecting rows because the entire table re-renders. We tested disableVirtualization, but the performance degradation makes it unusable for our case.

2. Yes, we know the number of rows in each details panel upfront. The challenge is that we have three nested levels (A → B → C). For example, the height of Grid A depends on: • its own rows • the number of expanded/collapsed rows in Grid B • the expanded rows of Grid B that contain Grid C with a dynamic height

So, providing an explicit height in getDetailPanelHeight seems like a bad option, since we would need to wrap the component, control it externally, and manually estimate the height for each nested table.

However, in some scenarios, Grid C (and sometimes Grid B) loads data on scroll — in those cases we cap the max height (e.g., 50 rows × rowHeight), but we’re exploring better approaches. In those cases, we also know how many child entities each parent entity has.

3. Tree Data / Row Grouping don’t match our use case. • Row Grouping and Tree Data won’t work because each nested level has different column definitions. • Tree Data also doesn’t support our case where some nested levels load data incrementally on scroll.

vladislav-gorovenko avatar Nov 25 '25 11:11 vladislav-gorovenko

@vladislav-gorovenko, thanks for the details.

I have made an example, based on yours, that fixes re-rendering issues and works fine even with virtualization, although it works better without it. https://stackblitz.com/edit/rheqccpm?file=src%2FDemo.tsx

I have disabled some other features as well to improve the rendering time a bit.

With virtualization, the scrolling issue is gone as well, but the UX is still not great, because with the setup that you have, the detail panel needs to wait for the grid to render to adjust its height. And, with multiple levels, the size adjustments are made multiple times as you scroll previously expanded grids into the view.

In my demo, I have set 1000 top-level rows, and I don't see any major performance issues. I have left console logs for you to see that nothing gets re-rendered on expansion anymore.

Now, since this is a reproduction demo, you may need to move some pieces of code back into the component, but it is important to memoize those. Otherwise, you will face the same issue again.

Let me know if the demo provided helps.

arminmeh avatar Nov 26 '25 08:11 arminmeh

@vladislav-gorovenko, thanks for the details.

I have made an example, based on yours, that fixes re-rendering issues and works fine even with virtualization, although it works better without it. https://stackblitz.com/edit/rheqccpm?file=src%2FDemo.tsx

I have disabled some other features as well to improve the rendering time a bit.

With virtualization, the scrolling issue is gone as well, but the UX is still not great, because with the setup that you have, the detail panel needs to wait for the grid to render to adjust its height. And, with multiple levels, the size adjustments are made multiple times as you scroll previously expanded grids into the view.

In my demo, I have set 1000 top-level rows, and I don't see any major performance issues. I have left console logs for you to see that nothing gets re-rendered on expansion anymore.

Now, since this is a reproduction demo, you may need to move some pieces of code back into the component, but it is important to memoize those. Otherwise, you will face the same issue again.

Let me know if the demo provided helps.

Many thanks for provided example.

At the moment, when virtualization is enabled for the parent DataGrid (DetailPanelHeightBugContainer), the issue still persists — scrolling up triggers size recalculations, which causes noticeable scroll jumps (as you mentioned, the UX isn’t great).

Disabling virtualization unfortunately isn’t an option for us. We sometimes work with 800+ rows, and without virtualization there’s a clear delay when selecting multiple items in the parent grid. This lag becomes even more significant as we add more columns or rows.

Is disabling virtualization currently the only reliable workaround? And are there any plans to improve virtualization behavior for deeply nested detail panels in future releases?

https://github.com/user-attachments/assets/a264792c-d66d-48c0-9dd8-c8fb69ac99df

vladislav-gorovenko avatar Nov 26 '25 08:11 vladislav-gorovenko

At the moment, when virtualization is enabled for the parent DataGrid (DetailPanelHeightBugContainer), the issue still persists — scrolling up triggers size recalculations, which causes noticeable scroll jumps (as you mentioned, the UX isn’t great).

I noticed some blank screens for the re-rendering of the removed rows, but I didn't see the jumping of the rows. This should not be happening.

I will mark this as a bug once more and investigate further.

arminmeh avatar Nov 26 '25 09:11 arminmeh

The problem here is that we have the detail panes with dynamic height, and their content needs some time to render, which makes the detail panel resize while scrolling.

With virtualization enabled, the rows get removed once outside of the viewport. When scrolling back, the rows are added again, and their detail panel is rendered with a certain height that is smaller than their final height. As you continue scrolling up, the content renders, and it pushes all the rows down. This creates a jump in the scroll. We can't know upfront if the content will be adjusted or not, so we have to react to the initial height being set.

The same issue appears without nesting the data grid.

Based on https://mui.com/x/react-data-grid/master-detail/#lazy-loading-detail-panel-content I have created an example with a regular table in the detail panel https://stackblitz.com/edit/8ytnxdy9?file=src%2FDemo.tsx If you scroll all the way down and start scrolling up, the scroller gets pushed back to the bottom.

Similar to this, I have made an example with a dynamic row height that gets updated constantly (to simulate delayed updates) Basically combining https://mui.com/x/react-data-grid/row-height/#dynamic-row-height and https://mui.com/x/react-data-grid/row-updates/#high-frequency The resulting demo https://stackblitz.com/edit/8ytnxdy9-7mzvw956?file=src%2FDemo.tsx shows that the scrollbar also jumps while you are scrolling through the grid.

So far, I only see the option to store the current scroll position while hydrating the rows meta, get the updates, and calculate the new scroll position based on what was changed.

@romgrk, @cherniavskii, do you have any other suggestions?

arminmeh avatar Nov 28 '25 10:11 arminmeh

So far, I only see the option to store the current scroll position while hydrating the rows meta, get the updates, and calculate the new scroll position based on what was changed.

Would this work given that this has to be done while user is actively scrolling?

cherniavskii avatar Nov 28 '25 11:11 cherniavskii

If we programatically modify the scroll position while the user is scrolling, we might mess with native features like kinetic scrolling.

So, providing an explicit height in getDetailPanelHeight seems like a bad option, since we would need to wrap the component, control it externally, and manually estimate the height for each nested table.

This sounds like the most reliable solution. The virtualization layer can't prevent jumps if it doesn't know the dimensions ahead of time. But it also seems that you could cache the dimensions of each detail panel once it's been rendered and re-use them when they get re-rendered into view, it could avoid manual computations, although it might not be as reliable.

romgrk avatar Nov 29 '25 00:11 romgrk

If we programatically modify the scroll position while the user is scrolling, we might mess with native features like kinetic scrolling.

So, providing an explicit height in getDetailPanelHeight seems like a bad option, since we would need to wrap the component, control it externally, and manually estimate the height for each nested table.

This sounds like the most reliable solution. The virtualization layer can't prevent jumps if it doesn't know the dimensions ahead of time. But it also seems that you could cache the dimensions of each detail panel once it's been rendered and re-use them when they get re-rendered into view, it could avoid manual computations, although it might not be as reliable.

Thanks, that lines up with what we’re seeing too.

Given our constraints, caching previously measured heights and returning them via getDetailPanelHeight sounds like the best workable option for us right now. We still need to implement it and validate how reliable it is in practice.

Would you consider exploring a built-in, out-of-the-box solution in the DataGrid that could handle this automatically?

vladislav-gorovenko avatar Dec 10 '25 08:12 vladislav-gorovenko

Would you consider exploring a built-in, out-of-the-box solution in the DataGrid that could handle this automatically?

@vladislav-gorovenko, I will add this issue to the board for further refinement.

arminmeh avatar Dec 16 '25 10:12 arminmeh

@vladislav-gorovenko, I will add this issue to the board for further refinement.

Thanks for the update.

We’ll try to add a workaround on our side for now, but we’re definitely waiting for a built-in solution in the DataGrid to handle this case properly. Please keep us posted on any progress.

vladislav-gorovenko avatar Dec 16 '25 11:12 vladislav-gorovenko