Skip to content
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] Potential fix for grid row state propagation #15627

Open
wants to merge 30 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
45f52f9
experimental fix for row state propagation
lauri865 Nov 27, 2024
7d6833c
rebase fix tests
lauri865 Nov 28, 2024
e73f872
fix tests
lauri865 Nov 28, 2024
9b63559
update api
lauri865 Nov 28, 2024
28ed635
fix e2e
lauri865 Nov 28, 2024
b3d87f7
refactor getCellParams into two methods
lauri865 Dec 2, 2024
a1db0cb
rebuild api
lauri865 Dec 2, 2024
9295e5d
refactor: selector with argument
romgrk Dec 10, 2024
cbd3793
refactor: remove EMPTY_CELL_PARAMS
romgrk Dec 10, 2024
72507ac
perf: avoid spread operator
romgrk Dec 10, 2024
d02033b
manual rebase
lauri865 Dec 19, 2024
33cc4c9
improve editing state selectors and separation between row and column…
lauri865 Dec 19, 2024
76d22ae
remove checkbox shallowcompare as it's incompatible with v8 selectors
lauri865 Dec 19, 2024
1f95e27
fix
lauri865 Dec 19, 2024
f89f70e
fix2
lauri865 Dec 19, 2024
28373b7
Merge branch 'master' into fix-grid-row-state-propagation
lauri865 Dec 20, 2024
8c3d5cb
use normal selector for DetailPanelToggleCell
lauri865 Dec 20, 2024
1b60107
fix private api being exported
lauri865 Dec 21, 2024
d84764d
Merge branch 'master' into fix-grid-row-state-propagation
lauri865 Dec 21, 2024
ce54c27
export type
lauri865 Dec 21, 2024
c61278c
fix column pinning state propagation
lauri865 Dec 21, 2024
e3eb6dd
rehydrate columns only when outerwidth changes (instead of inner)
lauri865 Dec 21, 2024
c22c4d9
fix resize / column hydration
lauri865 Dec 22, 2024
2810949
rehydrate columns on resize only if there are flex columns present
lauri865 Dec 22, 2024
1417385
fix
lauri865 Dec 22, 2024
4e14aa7
test fix
lauri865 Dec 22, 2024
b705c62
fix tests
lauri865 Dec 22, 2024
c5160ea
breaking fix: `viewportInnerWidth` should include `pinnedColumns`
lauri865 Dec 22, 2024
7ec0af7
cleanup
lauri865 Dec 22, 2024
fa8f8cf
Merge branch 'master' into fix-grid-row-state-propagation
lauri865 Dec 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/pages/x/api/data-grid/selectors.json
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,12 @@
"description": "",
"supportsApiRef": false
},
{
"name": "gridEditCellStateSelector",
"returnType": "GridEditCellProps<any>",
"description": "",
"supportsApiRef": true
},
{
"name": "gridEditRowsStateSelector",
"returnType": "GridEditingState",
Expand Down Expand Up @@ -403,6 +409,12 @@
"description": "",
"supportsApiRef": true
},
{
"name": "gridRowIsEditingSelector",
"returnType": "boolean",
"description": "",
"supportsApiRef": true
},
{
"name": "gridRowMaximumTreeDepthSelector",
"returnType": "number",
Expand Down
2 changes: 1 addition & 1 deletion docs/translations/api-docs/data-grid/grid-api.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"getAllGroupDetails": { "description": "Returns the column group lookup." },
"getAllRowIds": { "description": "Gets the list of row ids." },
"getCellElement": {
"description": "Gets the underlying DOM element for a cell at the given <code>id</code> and <code>field</code>."
"description": "Gets the <a href=\"/x/api/data-grid/grid-cell-params/\">GridCellParams</a> object that is passed as argument in events."
},
"getCellMode": { "description": "Gets the mode of a cell." },
"getCellParams": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import composeClasses from '@mui/utils/composeClasses';
import { getDataGridUtilityClass, useGridSelector, GridRenderCellParams } from '@mui/x-data-grid';
import {
getDataGridUtilityClass,
useGridSelector,
GridRenderCellParams,
GridRowId,
} from '@mui/x-data-grid';
import { createSelectorMemoized } from '@mui/x-data-grid/internals';
import { useGridRootProps } from '../hooks/utils/useGridRootProps';
import { useGridApiContext } from '../hooks/utils/useGridApiContext';
import { DataGridProProcessedProps } from '../models/dataGridProProps';
import { gridDetailPanelExpandedRowsContentCacheSelector } from '../hooks/features/detailPanel/gridDetailPanelSelector';
import {
gridDetailPanelExpandedRowIdsSelector,
gridDetailPanelExpandedRowsContentCacheSelector,
} from '../hooks/features/detailPanel/gridDetailPanelSelector';
import { GridApiPro } from '../models';

type OwnerState = { classes: DataGridProProcessedProps['classes']; isExpanded: boolean };

Expand All @@ -19,8 +29,17 @@ const useUtilityClasses = (ownerState: OwnerState) => {
return composeClasses(slots, getDataGridUtilityClass, classes);
};

const isExpandedSelector = createSelectorMemoized(
gridDetailPanelExpandedRowIdsSelector,
(expandedRowIds, rowId: GridRowId) => {
return expandedRowIds.has(rowId);
},
);
lauri865 marked this conversation as resolved.
Show resolved Hide resolved

function GridDetailPanelToggleCell(props: GridRenderCellParams) {
const { id, value: isExpanded } = props;
const { id, row, api } = props;
const rowId = api.getRowId(row);
const isExpanded = useGridSelector({ current: api as GridApiPro }, isExpandedSelector, rowId);

const rootProps = useGridRootProps();
const apiRef = useGridApiContext();
Expand Down
60 changes: 32 additions & 28 deletions packages/x-data-grid/src/components/GridRow.tsx
Copy link
Member

Choose a reason for hiding this comment

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

@romgrk It should be safe to cherry-pick this PR to v7 once we merge it. Any objections?

Copy link
Contributor

Choose a reason for hiding this comment

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

See comment above.

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { unstable_useForkRef as useForkRef } from '@mui/utils';
import { fastMemo } from '@mui/x-internals/fastMemo';
import { GridRowEventLookup } from '../models/events';
import { GridRowId, GridRowModel } from '../models/gridRows';
import { GridEditModes, GridRowModes, GridCellModes } from '../models/gridEditRowModel';
import { GridEditModes, GridCellModes } from '../models/gridEditRowModel';
import { gridClasses } from '../constants/gridClasses';
import { composeGridClasses } from '../utils/composeGridClasses';
import { useGridRootProps } from '../hooks/utils/useGridRootProps';
Expand All @@ -23,13 +23,28 @@ import { GRID_DETAIL_PANEL_TOGGLE_FIELD } from '../internals/constants';
import type { GridDimensions } from '../hooks/features/dimensions';
import { gridSortModelSelector } from '../hooks/features/sorting/gridSortingSelector';
import { gridRowMaximumTreeDepthSelector } from '../hooks/features/rows/gridRowsSelector';
import { gridEditRowsStateSelector } from '../hooks/features/editing/gridEditingSelectors';
import {
gridEditRowsStateSelector,
gridRowIsEditingSelector,
} from '../hooks/features/editing/gridEditingSelectors';
import { PinnedPosition, gridPinnedColumnPositionLookup } from './cell/GridCell';
import { GridScrollbarFillerCell as ScrollbarFiller } from './GridScrollbarFillerCell';
import { getPinnedCellOffset } from '../internals/utils/getPinnedCellOffset';
import { useGridConfiguration } from '../hooks/utils/useGridConfiguration';
import { useGridPrivateApiContext } from '../hooks/utils/useGridPrivateApiContext';
import { gridVirtualizationColumnEnabledSelector } from '../hooks';
import { createSelector } from '../utils/createSelector';

const gridIsRowReorderingEnabledSelector = createSelector(
gridEditRowsStateSelector,
(editRows, rowReordering: boolean) => {
if (!rowReordering) {
return false;
}
const isEditingRows = Object.keys(editRows).length > 0;
return !isEditingRows;
},
);

export interface GridRowProps extends React.HTMLAttributes<HTMLDivElement> {
row: GridRowModel;
Expand All @@ -52,11 +67,6 @@ export interface GridRowProps extends React.HTMLAttributes<HTMLDivElement> {
* If `null`, no cell in this row has focus.
*/
focusedColumnIndex: number | undefined;
/**
* Determines which cell should be tabbable by having tabIndex=0.
* If `null`, no cell in this row is in the tab sequence.
*/
tabbableCell: string | null;
isFirstVisible: boolean;
isLastVisible: boolean;
isNotVisible: boolean;
Expand Down Expand Up @@ -88,7 +98,6 @@ const GridRow = React.forwardRef<HTMLDivElement, GridRowProps>(function GridRow(
isLastVisible,
isNotVisible,
showBottomBorder,
tabbableCell,
onClick,
onDoubleClick,
onMouseEnter,
Expand All @@ -105,12 +114,17 @@ const GridRow = React.forwardRef<HTMLDivElement, GridRowProps>(function GridRow(
const sortModel = useGridSelector(apiRef, gridSortModelSelector);
const treeDepth = useGridSelector(apiRef, gridRowMaximumTreeDepthSelector);
const columnPositions = useGridSelector(apiRef, gridColumnPositionsSelector);
const editRowsState = useGridSelector(apiRef, gridEditRowsStateSelector);
const rowReordering = (rootProps as any).rowReordering as boolean;
const isRowReorderingEnabled = useGridSelector(
apiRef,
gridIsRowReorderingEnabledSelector,
rowReordering,
);
const handleRef = useForkRef(ref, refProp);
const rowNode = apiRef.current.getRowNode(rowId);
const scrollbarWidth = dimensions.hasScrollY ? dimensions.scrollbarSize : 0;
const gridHasFiller = dimensions.columnsTotalWidth < dimensions.viewportOuterSize.width;
const editing = apiRef.current.getRowMode(rowId) === GridRowModes.Edit;
const editing = useGridSelector(apiRef, gridRowIsEditingSelector, rowId);
const editable = rootProps.editMode === GridEditModes.Row;
const hasColumnVirtualization = useGridSelector(apiRef, gridVirtualizationColumnEnabledSelector);
const hasFocusCell = focusedColumnIndex !== undefined;
Expand Down Expand Up @@ -220,8 +234,6 @@ const GridRow = React.forwardRef<HTMLDivElement, GridRowProps>(function GridRow(

const { slots, slotProps, disableColumnReorder } = rootProps;

const rowReordering = (rootProps as any).rowReordering as boolean;

const heightEntry = useGridSelector(
apiRef,
() => ({ ...apiRef.current.getRowHeightEntry(rowId) }),
Expand Down Expand Up @@ -279,6 +291,11 @@ const GridRow = React.forwardRef<HTMLDivElement, GridRowProps>(function GridRow(
rowClassNames.push(rootProps.getRowClassName(rowParams));
}

/* Start of rendering */
if (!rowNode) {
return null;
}

Comment on lines +294 to +298
Copy link
Contributor

Choose a reason for hiding this comment

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

Is moving this block required?

I prefer to keep functional components organized as if it were a class component:

class GridRow {
  constructor() { ... }
  onClick() { ... }
  getCell() { ... }
  render() {}
}
function GridRow {
  /* initialization code */
  const onClick = () => { ... }
  const getCell = () => { ... }
  /* render code */
}

In particular for large components, it helps readability a lot to keep all render code grouped rather than spread in-between "class methods".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I like grouping logic, but here the balance is between a pretty large dead code path (defining an anyonymous function that should never be called, unless rowNode exists) vs. grouping logic. Not that clear what's more readable.

After this change, if you were to accidentally remove the rowNode check on component-level, the types would break (good imho), which is better than adding an extra conditional in the getCell that would open up the potential for using the compnent in currently unintended/undesigned ways.

Probably the right question here to ask is why the component needs to (try to) mount without a rowNode to begin with.

Copy link
Contributor

Choose a reason for hiding this comment

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

Probably the right question here to ask is why the component needs to (try to) mount without a rowNode to begin with.

Absolutely, the answer is often that the codebase went through many architectural & maintainer changes, and we haven't got around to refactoring everything.

const getCell = (
column: GridStateColDef,
indexInSection: number,
Expand Down Expand Up @@ -319,15 +336,12 @@ const GridRow = React.forwardRef<HTMLDivElement, GridRowProps>(function GridRow(
);
}

const editCellState = editRowsState[rowId]?.[column.field] ?? null;

// when the cell is a reorder cell we are not allowing to reorder the col
// fixes https://github.com/mui/mui-x/issues/11126
const isReorderCell = column.field === '__reorder__';
const isEditingRows = Object.keys(editRowsState).length > 0;

const canReorderColumn = !(disableColumnReorder || column.disableReorder);
const canReorderRow = rowReordering && !sortModel.length && treeDepth <= 1 && !isEditingRows;
const canReorderRow = isRowReorderingEnabled && !sortModel.length && treeDepth <= 1;

const disableDragEvents = !(canReorderColumn || (isReorderCell && canReorderRow));

Expand All @@ -343,24 +357,19 @@ const GridRow = React.forwardRef<HTMLDivElement, GridRowProps>(function GridRow(
colIndex={indexRelativeToAllColumns}
colSpan={colSpan}
disableDragEvents={disableDragEvents}
editCellState={editCellState}
isNotVisible={cellIsNotVisible}
pinnedOffset={pinnedOffset}
pinnedPosition={pinnedPosition}
sectionIndex={indexInSection}
sectionLength={sectionLength}
gridHasFiller={gridHasFiller}
row={row}
rowNode={rowNode}
{...slotProps?.cell}
/>
);
};

/* Start of rendering */

if (!rowNode) {
return null;
}

const leftCells = pinnedColumns.left.map((column, i) => {
const indexRelativeToAllColumns = i;
return getCell(
Expand Down Expand Up @@ -541,11 +550,6 @@ GridRow.propTypes = {
rowId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
selected: PropTypes.bool.isRequired,
showBottomBorder: PropTypes.bool.isRequired,
/**
* Determines which cell should be tabbable by having tabIndex=0.
* If `null`, no cell in this row is in the tab sequence.
*/
tabbableCell: PropTypes.string,
visibleColumns: PropTypes.arrayOf(PropTypes.object).isRequired,
} as any;

Expand Down
Loading
Loading