Skip to content

Commit

Permalink
Introduce selectors with arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
MBilalShafi committed Aug 1, 2024
1 parent fdab7f0 commit 8e5238e
Show file tree
Hide file tree
Showing 4 changed files with 335 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import { useGridRootProps } from '../../hooks/utils/useGridRootProps';
import { getDataGridUtilityClass } from '../../constants/gridClasses';
import type { DataGridProcessedProps } from '../../models/props/DataGridProps';
import type { GridRowSelectionCheckboxParams } from '../../models/params/gridRowSelectionCheckboxParams';
import { useGridSelector } from '../../hooks/utils/useGridSelector';
import { getGridSomeChildrenSelectedSelector } from '../../hooks/features/rowSelection/utils';
import { useGridSelectorV8 } from '../../hooks/utils/useGridSelectorV8';
import { gridSomeChildrenSelectedSelector } from '../../hooks/features/rowSelection/utils';

type OwnerState = { classes: DataGridProcessedProps['classes'] };

Expand Down Expand Up @@ -52,8 +52,9 @@ const GridCellCheckboxForwardRef = React.forwardRef<HTMLInputElement, GridRender
const classes = useUtilityClasses(ownerState);
const checkboxElement = React.useRef<HTMLElement>(null);

const someChildrenSelectedSelector = getGridSomeChildrenSelectedSelector(id);
const someChildrenSelected = useGridSelector(apiRef, someChildrenSelectedSelector);
const someChildrenSelected = useGridSelectorV8(apiRef, gridSomeChildrenSelectedSelector, {
groupId: id,
});

const rippleRef = React.useRef<TouchRippleActions>(null);
const handleRef = useForkRef(checkboxElement, ref);
Expand Down
134 changes: 66 additions & 68 deletions packages/x-data-grid/src/hooks/features/rowSelection/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,75 +2,72 @@ import type { DataGridProcessedProps } from '../../../models/props/DataGridProps
import { GridSignature } from '../../utils/useGridApiEventHandler';
import { GRID_ROOT_GROUP_ID } from '../rows/gridRowsUtils';
import type { GridGroupNode, GridRowId, GridRowTreeConfig } from '../../../models/gridRows';
import type {
GridPrivateApiCommunity,
GridApiCommunity,
} from '../../../models/api/gridApiCommunity';
import type { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity';
import { gridFilteredRowsLookupSelector } from '../filter/gridFilterSelector';
import { gridSortedRowIdsSelector } from '../sorting/gridSortingSelector';
import { selectedIdsLookupSelector } from './gridRowSelectionSelector';
import { gridRowTreeSelector } from '../rows/gridRowsSelector';
import { createSelector } from '../../../utils/createSelector';

function getGridRowGroupSelectableChildrenSelector(
apiRef: React.MutableRefObject<GridApiCommunity>,
groupId: GridRowId,
) {
return createSelector(
gridRowTreeSelector,
gridSortedRowIdsSelector,
gridFilteredRowsLookupSelector,
(rowTree, sortedRowIds, filteredRowsLookup) => {
const groupNode = rowTree[groupId];
if (!groupNode || groupNode.type !== 'group') {
return [];
}

const children: GridRowId[] = [];

const startIndex = sortedRowIds.findIndex((id) => id === groupId) + 1;
for (
let index = startIndex;
index < sortedRowIds.length && rowTree[sortedRowIds[index]]?.depth > groupNode.depth;
index += 1
) {
const id = sortedRowIds[index];
if (filteredRowsLookup[id] !== false && apiRef.current.isRowSelectable(id)) {
children.push(id);
}
}
import { createSelectorV8 } from '../../../utils/createSelectorV8';

export const gridRowGroupSelectableChildrenSelector = createSelectorV8(
gridRowTreeSelector,
gridSortedRowIdsSelector,
gridFilteredRowsLookupSelector,
(rowTree, sortedRowIds, filteredRowsLookup, args) => {
const children = new Set<GridRowId>();
const { groupId, apiRef } = args;
if (groupId === undefined || apiRef === undefined) {
return children;
},
);
}
}
const groupNode = rowTree[groupId];
if (!groupNode || groupNode.type !== 'group') {
return children;
}

export function getGridSomeChildrenSelectedSelector(groupId: GridRowId) {
return createSelector(
gridRowTreeSelector,
gridSortedRowIdsSelector,
gridFilteredRowsLookupSelector,
selectedIdsLookupSelector,
(rowTree, sortedRowIds, filteredRowsLookup, rowSelectionLookup) => {
const groupNode = rowTree[groupId];
if (!groupNode || groupNode.type !== 'group') {
return false;
const startIndex = sortedRowIds.findIndex((id) => id === groupId) + 1;
for (
let index = startIndex;
index < sortedRowIds.length && rowTree[sortedRowIds[index]]?.depth > groupNode.depth;
index += 1
) {
const id = sortedRowIds[index];
if (filteredRowsLookup[id] !== false && apiRef.current.isRowSelectable(id)) {
children.add(id);
}
}
return children;
},
);

export const gridSomeChildrenSelectedSelector = createSelectorV8(
gridRowTreeSelector,
gridSortedRowIdsSelector,
gridFilteredRowsLookupSelector,
selectedIdsLookupSelector,
(rowTree, sortedRowIds, filteredRowsLookup, rowSelectionLookup, args) => {
const groupId = args.groupId;
if (groupId === undefined) {
return false;
}
const groupNode = rowTree[groupId];
if (!groupNode || groupNode.type !== 'group') {
return false;
}

const startIndex = sortedRowIds.findIndex((id) => id === groupId) + 1;
for (
let index = startIndex;
index < sortedRowIds.length && rowTree[sortedRowIds[index]]?.depth > groupNode.depth;
index += 1
) {
const id = sortedRowIds[index];
if (filteredRowsLookup[id] !== false && rowSelectionLookup[id] !== undefined) {
return true;
}
const startIndex = sortedRowIds.findIndex((id) => id === groupId) + 1;
for (
let index = startIndex;
index < sortedRowIds.length && rowTree[sortedRowIds[index]]?.depth > groupNode.depth;
index += 1
) {
const id = sortedRowIds[index];
if (filteredRowsLookup[id] !== false && rowSelectionLookup[id] !== undefined) {
return true;
}
return false;
},
);
}
}
return false;
},
);

export function isMultipleRowSelectionEnabled(
props: Pick<
Expand Down Expand Up @@ -149,9 +146,11 @@ export const findRowsToSelect = (
const rowNode = apiRef.current.getRowNode(selectedRow);

if (rowNode?.type === 'group') {
const rowGroupChildrenSelector = getGridRowGroupSelectableChildrenSelector(apiRef, selectedRow);
const children = rowGroupChildrenSelector(apiRef);
return rowsToSelect.concat(children);
const children = gridRowGroupSelectableChildrenSelector(apiRef, {
groupId: selectedRow,
apiRef,
});
return rowsToSelect.concat(Array.from(children));
}
return rowsToSelect;
};
Expand All @@ -173,12 +172,11 @@ export const findRowsToDeselect = (

const rowNode = apiRef.current.getRowNode(deselectedRow);
if (rowNode?.type === 'group') {
const rowGroupChildrenSelector = getGridRowGroupSelectableChildrenSelector(
const children = gridRowGroupSelectableChildrenSelector(apiRef, {
groupId: deselectedRow,
apiRef,
deselectedRow,
);
const children = rowGroupChildrenSelector(apiRef);
return rowsToDeselect.concat(children);
});
return rowsToDeselect.concat(Array.from(children));
}
return rowsToDeselect;
};
83 changes: 83 additions & 0 deletions packages/x-data-grid/src/hooks/utils/useGridSelectorV8.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import * as React from 'react';
import type { GridApiCommon } from '../../models/api/gridApiCommon';
import { OutputSelectorV8 } from '../../utils/createSelectorV8';
import { useLazyRef } from './useLazyRef';
import { useOnMount } from './useOnMount';
import { warnOnce } from '../../internals/utils/warning';
import type { GridCoreApi } from '../../models/api/gridCoreApi';
import { fastObjectShallowCompare } from '../../utils/fastObjectShallowCompare';

function isOutputSelector<Api extends GridApiCommon, Args, T>(
selector: any,
): selector is OutputSelectorV8<Api['state'], Args, T> {
return selector.acceptsApiRef;
}

function applySelectorV8<Api extends GridApiCommon, Args, T>(
apiRef: React.MutableRefObject<Api>,
selector: ((state: Api['state']) => T) | OutputSelectorV8<Api['state'], Args, T>,
args: Args,
instanceId: GridCoreApi['instanceId'],
) {
if (isOutputSelector(selector)) {
return selector(apiRef, args);
}
return selector(apiRef.current.state, instanceId);
}

const defaultCompare = Object.is;
export const objectShallowCompare = fastObjectShallowCompare;

const createRefs = () => ({ state: null, equals: null, selector: null }) as any;

export const useGridSelectorV8 = <Api extends GridApiCommon, Args, T>(
apiRef: React.MutableRefObject<Api>,
selector: ((state: Api['state']) => T) | OutputSelectorV8<Api['state'], Args, T>,
args: Args = {} as Args,
equals: (a: T, b: T) => boolean = defaultCompare,
) => {
if (process.env.NODE_ENV !== 'production') {
if (!apiRef.current.state) {
warnOnce([
'MUI X: `useGridSelector` has been called before the initialization of the state.',
'This hook can only be used inside the context of the grid.',
]);
}
}

const refs = useLazyRef<
{
state: T;
equals: typeof equals;
selector: typeof selector;
},
never
>(createRefs);
const didInit = refs.current.selector !== null;

const [state, setState] = React.useState<T>(
// We don't use an initialization function to avoid allocations
(didInit ? null : applySelectorV8(apiRef, selector, args, apiRef.current.instanceId)) as T,
);

refs.current.state = state;
refs.current.equals = equals;
refs.current.selector = selector;

useOnMount(() => {
return apiRef.current.store.subscribe(() => {
const newState = applySelectorV8(
apiRef,
refs.current.selector,
args,
apiRef.current.instanceId,
) as T;
if (!refs.current.equals(refs.current.state, newState)) {
refs.current.state = newState;
setState(newState);
}
});
});

return state;
};
Loading

0 comments on commit 8e5238e

Please sign in to comment.