Skip to content

Conversation

lauri865
Copy link
Contributor

Fixes #19370
Alternative to #19891

Avoids cancelling the aggregation process early, as well as includes sortColumns (if aggregated) in the initial chunk / viewport to speed up things visually.

@mui-bot
Copy link

mui-bot commented Oct 10, 2025

Deploy preview: https://deploy-preview-19892--material-ui-x.netlify.app/

Bundle size report

Bundle Parsed size Gzip size
@mui/x-data-grid 🔺+678B(+0.17%) 🔺+138B(+0.12%)
@mui/x-data-grid-pro 🔺+705B(+0.15%) 🔺+72B(+0.05%)
@mui/x-data-grid-premium 🔺+1.06KB(+0.17%) 🔺+214B(+0.11%)
@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 🔺+16B(+0.03%) 🔺+6B(+0.03%)
@mui/x-tree-view-pro 🔺+16B(+0.02%) 🔺+7B(+0.03%)

Details of bundle changes

Generated by 🚫 dangerJS against 352b5c3

@lauri865
Copy link
Contributor Author

lauri865 commented Oct 10, 2025

There's one more issue that should be fixed. In case of row grouping (probably tree data as well) with sorting, there's a flash of unsorted content on first mount.

@lauri865
Copy link
Contributor Author

lauri865 commented Oct 10, 2025

@romgrk, I noticed that useStoreEffect doesn't have any equality check built on, so it can cause cascading state updates. Noticed that e.g. the below state setter runs multiple times in one render pass:

useStoreEffect(virtualizer.store, Dimensions.selectors.dimensions, (_, dimensions) => {

Not sure if I broke something by adding the equality check now, but...

@zannager zannager added the scope: data grid Changes related to the data grid. label Oct 10, 2025
Copy link
Member

@cherniavskii cherniavskii left a comment

Choose a reason for hiding this comment

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

Hey @lauri865
Thanks for another PR!

const visibleAggregatedFields = visibleColumns
.slice(renderContext.firstColumnIndex, renderContext.lastColumnIndex + 1)
.filter((field) => aggregatedFields.includes(field));
const visibleAggregatedFieldsWithSort = visibleAggregatedFields.concat(
Copy link
Member

Choose a reason for hiding this comment

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

OK, so IIUC instead of calling applyAggregation('sort') on sort model change, sorted columns are now included in the first chunk, correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's two different things.

applyAggregation('sort') needs to run only when there are sort dependent aggregations (rank, median, first/last, etc.).

But Sort columns were included in the initial chunk due to what I wrote in the next comment. We want them to be included in the first chunk (=these columns affect "viewport" due to potentially changing the row order, regardless of whether they are in the renderContext or not) so that we don't end up with a visual flicker.

Copy link
Member

Choose a reason for hiding this comment

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

Can you explain why the implementation changed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, so, when I went with the previous implementation, I was under the assumption that filtering, etc. doesn't depend on aggregation, hence, we want to guarantee maximum level of deduplication with the queueMicrotask approach. But the case is actually that sorting under certain circumstances (=when sorting an aggregated row with grouping) can depend on aggregation, hence, we cannot defer without incurring visual flicker on mount.

Check this reproduction:
https://stackblitz.com/edit/xdrrihtc?file=src%2FDemo.tsx

Comment on lines 40 to 42
if (!argsEqual(previousState, nextState)) {
instance.effect(previousState, nextState);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can try this optimization, the store is still an evolving API. I would keep it simple though, just Object.is. Do you have any objection to that?

Copy link
Contributor Author

@lauri865 lauri865 Oct 13, 2025

Choose a reason for hiding this comment

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

Object.is is fine. I added the other check initially because I had a thought about replacing multiple events with a single Effect that selects multiple parts of the state. But that was misguided on my part anyways, because the effects are synchronously run on setting state, hence that wouldn't help deduplicate events in any case.

@lauri865
Copy link
Contributor Author

lauri865 commented Oct 13, 2025

@romgrk, another thing I noted on the virtualizer decoupling is that there's now more render passes required to reconcile the initial state for rows to be rendered than before (5 passes in total with static data / columns).

In v8.9.1 rows were rendered on the 3rd render pass already vs. 5th. (<~15ms in vs 60ms+ now)

@lauri865
Copy link
Contributor Author

lauri865 commented Oct 13, 2025

Also, there's no way this can run properly:

useGridEventPriority(apiRef, 'sortedRowsSet', forceUpdateRenderContext);

useVirtualization uses inputs that are provided during rendering as params, and an event is run outside of rendering in connection to store updates. So, the forceUpdateRenderContext will never have access to the fresh state (=rows in this case).

Previously inputs were accessed directly from the store (=had access to update values outside of rendering).

@lauri865
Copy link
Contributor Author

lauri865 commented Oct 13, 2025

Also, the virtualizer used to be isolated from the rest of the Datagrid feature hooks, which was great for performance, since a virtualizer can update quite frequently.

Now, by including it in the root of the grid in useDataGridComponent under useGridVirtualization, means that everything under that tree needs to re-evaluate when scrolling, as the root state updates quite frequently.

This is bad imho. And shouldn't have been released under a minor version.

@lauri865
Copy link
Contributor Author

lauri865 commented Oct 13, 2025

Added an experimental refactor that takes mount performance back to the old baselines if not slightly better (<15ms vs. 60m = 4x better; in 3 render passes vs. 5 currently). The structure is closer to how it used to be as well where Virtualizer doesn't affect the whole grid's feature hook tree with its state updates.

No need for hacky commiting setters into a store that consumer that very store (=source for memory leaks), or state setting whenever a ref changes.

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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[data grid] Column aggregation breaks if sortModel is passed to props

5 participants