-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[DataGrid] Avoid <GridRoot />
double-render pass on mount in SPA mode
#15648
[DataGrid] Avoid <GridRoot />
double-render pass on mount in SPA mode
#15648
Conversation
Deploy preview: https://deploy-preview-15648--material-ui-x.netlify.app/ Updated pages: |
dd1a1e1
to
8da261b
Compare
<GridRoot />
double-render pass on mount in SPA mode
Side-benefit is also probably more robust tests if the Datagrid is mounted on the first pass. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code LGTM, we can merge once the tests are passing.
I have no idea how to fix the last failing test. Either it's a symptom of column headers updated more times now or just a bad test. But I lack understanding why it was previously expected that the warning message will be output exactly twice. Edit: discovered some areas to improve around mounting / state initialization |
12fb3da
to
c8580d9
Compare
Also fixes an annoying flicker coming out of loading state. It's probably the root cause of a layout shift I once reported with Before: After: |
Tests should be passing now. 4 was the right amount of warnings for this approach, 1 extra pass (+1 warning for header/row each = 2+2 = 4) that doesn't even flush to the dom, so I think we're good there. Curiously, after this optimisation: mui-x/packages/x-data-grid/src/hooks/features/columns/useGridColumns.tsx Lines 400 to 412 in 5ca918f
This test started failing: mui-x/packages/x-data-grid-premium/src/tests/aggregation.DataGridPremium.test.tsx Lines 145 to 150 in 1a079e1
The only reason I can think of is that aggregations didn't explicitly trigger column updates, but somehow were piggybacking on If that's the case, then I suppose there was a bug whereby columns didn't clear when there was no totals row visible, as |
packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx
Outdated
Show resolved
Hide resolved
packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts
Outdated
Show resolved
Hide resolved
@@ -67,4 +73,4 @@ export type GridFilteringMethodValue = Omit<GridFilterState, 'filterModel'>; | |||
* A row is visible if it is passing the filters AND if its parents are expanded. | |||
* If a row is not registered in this lookup, it is visible. | |||
*/ | |||
export type GridVisibleRowsLookupState = Record<GridRowId, boolean>; | |||
export type GridVisibleRowsLookupState = Record<GridRowId, false>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same change for visibleRowsLookup
as previously with filteredRowsLookup
.
@@ -20,7 +26,7 @@ export interface GridFilterState { | |||
* If a row is not registered in this lookup, it is filtered. | |||
* This is the equivalent of the `visibleRowsLookup` if all the groups were expanded. | |||
*/ | |||
filteredRowsLookup: Record<GridRowId, boolean>; | |||
filteredRowsLookup: Record<GridRowId, false>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we don't store true
values now, does it make sense to simplify the type further? It could be slightly better performance wise, and more aligned DX wise, as the filteredRowsLookup
no longer stores the "filtered" rows, rather it stores "excluded" ones.
For consistency, a similar concept could be applied on the visible rows (invisibleRows: Set<GridRowId>
), column visibility model (hiddenColumns: Set<GridColDef['field']>
), etc.
filteredRowsLookup: Record<GridRowId, false>; | |
excludedRows: Set<GridRowId>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing Record
to Set
/Map
won't necessarily be faster – see #9120 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #10068 (comment) and #10068 (comment). I think the change is good and has potential for improvement, but I didn't have the bandwidth to investigate it more.
packages/x-data-grid/src/hooks/features/filter/gridFilterState.ts
Outdated
Show resolved
Hide resolved
packages/x-data-grid/src/internals/selectors/dimensionSelectors.ts
Outdated
Show resolved
Hide resolved
export const gridDimensionsColumnsTotalWidthSelector = (state: GridStateCommunity) => | ||
state.dimensions.columnsTotalWidth; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You said there is a name conflict with another selector called gridColumnsTotalWidthSelector
. Are the two selectors returning the same value, calculated differently? If so, would it be appropriate to remove one of them?
Otherwise, could we rename the other one and keep the short name for this one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are the two selectors returning the same value, calculated differently? If so, would it be appropriate to remove one of them?
No, the original gridColumnsTotalWidthSelector
calculates the value, and that value change triggers the dimensions state update and gets stored in that state.
AFAIU @lauri865 used gridDimensionsColumnsTotalWidthSelector
instead because it guarantees that the dimensions state is up to date.
I've moved the original selector to useGridDimensions
, it's only used internally now.
I've also renamed gridDimensionsColumnsTotalWidthSelector
to gridColumnsTotalWidthSelector
and used createSelector
to keep the signature of the original selector.
@lauri865 Does this make sense to you?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly. I believe your change makes sense, as nothing else probably depends on the calculated size other than useGridDimensions
, it makes sense to localise it.
packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM beyond the small comments above.
Cherry-pick PRs will be created targeting branches: v7.x |
<GridRoot />
double-render pass on mount in SPA mode<GridRoot />
double-render pass on mount in SPA mode
…de (mui#15648) Co-authored-by: Andrew Cherniavskyi <[email protected]>
(dimensions) => dimensions.columnsTotalWidth, | ||
); | ||
|
||
export const gridRowHeightSelector = (state: GridStateCommunity) => state.dimensions.rowHeight; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lauri865
any particular reason why all these selectors are not using gridDimensionsSelector
as an input?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would it be solving to wrap them in another function call? Just went with my gut feel tbh - simple selectors, simpler to write plainly. Localized to a single file, enforced by typescript.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am asking because in this PR I have to replace all params with apiRef
, so each one of these will have apiRef.current.state.dimensions.X
repeated. Having just dimensions
will be much easier to read.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They should probably be transformed to something like const xxxSelector = createRootSelector(state => state.dimensions.xxx)
to keep them readable.
Currently, GridRoot double-renders in all cases on mount to prevent SSR. In SPA-mode this is irrelevant, and can be avoided with the help of
useSyncExternalStore
, since there's no server snapshot in SPA-mode, the gird will be mounted directly. As a result, the rootRef becomes immediately available on first mount in SPAs, as one would expect.Adds a new dependency
use-sync-external-store
to make it backwards compatible with React 17. Charts and treeview have the same dependency.Changelog
Breaking changes
The
filteredRowsLookup
object of the filter state does not containtrue
values anymore. If the row is filtered out, the value isfalse
. Otherwise, the row id is not present in the object.This change only impacts you if you relied on
filteredRowsLookup
to get ids of filtered rows. In this case,usegridDataRowIdsSelector
selector to get row ids and checkfilteredRowsLookup
forfalse
values:The
visibleRowsLookup
state does not containtrue
values anymore. If the row is not visible, the value isfalse
. Otherwise, the row id is not present in the object: