From ee180652718510e674449453ba0c47f86476783d Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Fri, 16 Aug 2024 19:15:55 +0500 Subject: [PATCH 1/6] [DataGrid] Selectors with arguments --- .../GridDataSourceTreeDataGroupingCell.tsx | 11 +- .../dataSource/gridDataSourceSelector.ts | 13 +- .../src/hooks/utils/useGridSelector.ts | 81 ++++++++- packages/x-data-grid/src/internals/index.ts | 8 +- .../x-data-grid/src/utils/createSelector.ts | 161 ++++++++++++++++++ 5 files changed, 267 insertions(+), 7 deletions(-) diff --git a/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx b/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx index eedd1ea4f7d38..79e002211adf2 100644 --- a/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx +++ b/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx @@ -8,12 +8,17 @@ import { GridDataSourceGroupNode, useGridSelector, } from '@mui/x-data-grid'; +import { useGridSelectorV8 } from '@mui/x-data-grid/internals'; import CircularProgress from '@mui/material/CircularProgress'; import { useGridRootProps } from '../hooks/utils/useGridRootProps'; import { useGridPrivateApiContext } from '../hooks/utils/useGridPrivateApiContext'; import { DataGridProProcessedProps } from '../models/dataGridProProps'; import { GridPrivateApiPro } from '../models/gridApiPro'; import { GridStatePro } from '../models/gridStatePro'; +import { + gridDataSourceErrorSelector, + gridDataSourceLoadingIdSelector, +} from '../hooks/features/dataSource/gridDataSourceSelector'; type OwnerState = DataGridProProcessedProps; @@ -50,10 +55,8 @@ function GridTreeDataGroupingCellIcon(props: GridTreeDataGroupingCellIconProps) const classes = useUtilityClasses(rootProps); const { rowNode, id, field, descendantCount } = props; - const loadingSelector = (state: GridStatePro) => state.dataSource.loading[id] ?? false; - const errorSelector = (state: GridStatePro) => state.dataSource.errors[id]; - const isDataLoading = useGridSelector(apiRef, loadingSelector); - const error = useGridSelector(apiRef, errorSelector); + const isDataLoading = useGridSelectorV8(apiRef, gridDataSourceLoadingIdSelector, id); + const error = useGridSelectorV8(apiRef, gridDataSourceErrorSelector, id); const handleClick = (event: React.MouseEvent) => { if (!rowNode.childrenExpanded) { diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts index a7bee9eec1d98..199d473faaf9c 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts @@ -3,8 +3,9 @@ import { gridFilterModelSelector, gridSortModelSelector, gridPaginationModelSelector, + GridRowId, } from '@mui/x-data-grid'; -import { createSelector } from '@mui/x-data-grid/internals'; +import { createSelector, createArgumentsSelector } from '@mui/x-data-grid/internals'; import { GridStatePro } from '../../../models/gridStatePro'; const computeStartEnd = (paginationModel: GridPaginationModel) => { @@ -37,7 +38,17 @@ export const gridDataSourceLoadingSelector = createSelector( (dataSource) => dataSource.loading, ); +export const gridDataSourceLoadingIdSelector = createArgumentsSelector()( + gridDataSourceStateSelector, + (dataSource, id) => dataSource.loading[id] ?? false, +); + export const gridDataSourceErrorsSelector = createSelector( gridDataSourceStateSelector, (dataSource) => dataSource.errors, ); + +export const gridDataSourceErrorSelector = createArgumentsSelector()( + gridDataSourceStateSelector, + (dataSource, id) => dataSource.errors[id], +); diff --git a/packages/x-data-grid/src/hooks/utils/useGridSelector.ts b/packages/x-data-grid/src/hooks/utils/useGridSelector.ts index 3e8720b9689d5..63b8f11e3c794 100644 --- a/packages/x-data-grid/src/hooks/utils/useGridSelector.ts +++ b/packages/x-data-grid/src/hooks/utils/useGridSelector.ts @@ -1,10 +1,11 @@ import * as React from 'react'; import { fastObjectShallowCompare } from '@mui/x-internals/fastObjectShallowCompare'; import type { GridApiCommon } from '../../models/api/gridApiCommon'; -import { OutputSelector } from '../../utils/createSelector'; +import { OutputSelector, OutputArgumentsSelector } from '../../utils/createSelector'; import { useLazyRef } from './useLazyRef'; import { useOnMount } from './useOnMount'; import { warnOnce } from '../../internals/utils/warning'; +import type { GridCoreApi } from '../../models/api/gridCoreApi'; function isOutputSelector( selector: any, @@ -12,6 +13,17 @@ function isOutputSelector( return selector.acceptsApiRef; } +type Selector = + | ((state: Api['state']) => T) + | OutputSelector + | OutputArgumentsSelector; + +function isArgumentsSelector( + selector: any, +): selector is OutputArgumentsSelector { + return selector.acceptsArguments; +} + function applySelector( apiRef: React.MutableRefObject, selector: ((state: Api['state']) => T) | OutputSelector, @@ -22,6 +34,21 @@ function applySelector( return selector(apiRef.current.state); } +function applySelectorV8( + apiRef: React.MutableRefObject, + selector: Selector, + args: Args, + instanceId: GridCoreApi['instanceId'], +) { + if (isOutputSelector(selector)) { + if (isArgumentsSelector(selector)) { + return selector(apiRef, args); + } + return selector(apiRef); + } + return selector(apiRef.current.state, instanceId); +} + const defaultCompare = Object.is; export const objectShallowCompare = fastObjectShallowCompare; @@ -72,3 +99,55 @@ export const useGridSelector = ( return state; }; + +export const useGridSelectorV8 = ( + apiRef: React.MutableRefObject, + selector: Selector, + 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( + // 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; +}; diff --git a/packages/x-data-grid/src/internals/index.ts b/packages/x-data-grid/src/internals/index.ts index 1bb8284e5a270..dad8a7057137f 100644 --- a/packages/x-data-grid/src/internals/index.ts +++ b/packages/x-data-grid/src/internals/index.ts @@ -139,7 +139,13 @@ export type * from '../models/props/DataGridProps'; export type * from '../models/gridDataSource'; export { getColumnsToExport, defaultGetRowsToExport } from '../hooks/features/export/utils'; export * from '../utils/createControllablePromise'; -export { createSelector, createSelectorMemoized } from '../utils/createSelector'; +export { + createSelector, + createArgumentsSelector, + createSelectorMemoized, + createArgumentsSelectorMemoized, +} from '../utils/createSelector'; +export { useGridSelectorV8 } from '../hooks/utils/useGridSelector'; export { gridRowGroupsToFetchSelector } from '../hooks/features/rows/gridRowsSelector'; export { findParentElementFromClassName, diff --git a/packages/x-data-grid/src/utils/createSelector.ts b/packages/x-data-grid/src/utils/createSelector.ts index 647ed42838532..b5ab88a84f872 100644 --- a/packages/x-data-grid/src/utils/createSelector.ts +++ b/packages/x-data-grid/src/utils/createSelector.ts @@ -11,6 +11,16 @@ export interface OutputSelector { acceptsApiRef: boolean; } +export interface OutputArgumentsSelector { + ( + apiRef: React.MutableRefObject<{ state: State; instanceId: GridCoreApi['instanceId'] }>, + args: Args, + ): Result; + (state: State, instanceId: GridCoreApi['instanceId']): Result; + acceptsApiRef: boolean; + acceptsArguments: boolean; +} + type StateFromSelector = T extends (first: infer F, ...args: any[]) => any ? F extends { state: infer F2 } ? F2 @@ -32,10 +42,31 @@ type SelectorArgs>, Result> = // Input selectors as separate inline arguments | [...Selectors, (...args: SelectorResultArray) => Result]; +type SelectorResultArrayWithArgs>, Args> = [ + ...SelectorResultArray, + Args, +]; + +type ArgumentsSelectorArgs>, Args, Result> = + // Input selectors as a separate array + | [ + selectors: [...Selectors], + combiner: (...args: SelectorResultArrayWithArgs) => Result, + ] + // Input selectors as separate inline arguments + | [...Selectors, (...args: SelectorResultArrayWithArgs) => Result]; + type CreateSelectorFunction = >, Result>( ...items: SelectorArgs ) => OutputSelector, Result>; +type CreateArgumentsSelectorFunction = () => < + Selectors extends ReadonlyArray>, + Result, +>( + ...items: ArgumentsSelectorArgs +) => OutputArgumentsSelector, Args, Result>; + const cache = new WeakMap>(); function checkIsAPIRef(value: any) { @@ -125,6 +156,89 @@ export const createSelector = (( return selector; }) as unknown as CreateSelectorFunction; +export const createArgumentsSelector = (() => + ( + a: Function, + b: Function, + c?: Function, + d?: Function, + e?: Function, + f?: Function, + ...other: any[] + ) => { + if (other.length > 0) { + throw new Error('Unsupported number of selectors'); + } + + let selector: any; + + if (a && b && c && d && e && f) { + selector = (stateOrApiRef: any, args: any, instanceIdParam: any) => { + const isAPIRef = checkIsAPIRef(stateOrApiRef); + const instanceId = + instanceIdParam ?? (isAPIRef ? stateOrApiRef.current.instanceId : DEFAULT_INSTANCE_ID); + const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef; + const va = a(state, args, instanceId); + const vb = b(state, args, instanceId); + const vc = c(state, args, instanceId); + const vd = d(state, args, instanceId); + const ve = e(state, args, instanceId); + return f(va, vb, vc, vd, ve, args); + }; + } else if (a && b && c && d && e) { + selector = (stateOrApiRef: any, args: any, instanceIdParam: any) => { + const isAPIRef = checkIsAPIRef(stateOrApiRef); + const instanceId = + instanceIdParam ?? (isAPIRef ? stateOrApiRef.current.instanceId : DEFAULT_INSTANCE_ID); + const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef; + const va = a(state, args, instanceId); + const vb = b(state, args, instanceId); + const vc = c(state, args, instanceId); + const vd = d(state, args, instanceId); + return e(va, vb, vc, vd, args); + }; + } else if (a && b && c && d) { + selector = (stateOrApiRef: any, args: any, instanceIdParam: any) => { + const isAPIRef = checkIsAPIRef(stateOrApiRef); + const instanceId = + instanceIdParam ?? (isAPIRef ? stateOrApiRef.current.instanceId : DEFAULT_INSTANCE_ID); + const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef; + const va = a(state, args, instanceId); + const vb = b(state, args, instanceId); + const vc = c(state, args, instanceId); + return d(va, vb, vc, args); + }; + } else if (a && b && c) { + selector = (stateOrApiRef: any, args: any, instanceIdParam: any) => { + const isAPIRef = checkIsAPIRef(stateOrApiRef); + const instanceId = + instanceIdParam ?? (isAPIRef ? stateOrApiRef.current.instanceId : DEFAULT_INSTANCE_ID); + const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef; + const va = a(state, args, instanceId); + const vb = b(state, args, instanceId); + return c(va, vb, args); + }; + } else if (a && b) { + selector = (stateOrApiRef: any, args: any, instanceIdParam: any) => { + const isAPIRef = checkIsAPIRef(stateOrApiRef); + const instanceId = + instanceIdParam ?? (isAPIRef ? stateOrApiRef.current.instanceId : DEFAULT_INSTANCE_ID); + const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef; + const va = a(state, args, instanceId); + return b(va, args); + }; + } else { + throw new Error('Missing arguments'); + } + + // We use this property to detect if the selector was created with createSelector + // or it's only a simple function the receives the state and returns part of it. + selector.acceptsApiRef = true; + selector.acceptsArguments = true; + + return selector; + }) as unknown as CreateArgumentsSelectorFunction; + export const createSelectorMemoized: CreateSelectorFunction = (...args: any) => { const selector = (stateOrApiRef: any, instanceId?: any) => { const isAPIRef = checkIsAPIRef(stateOrApiRef); @@ -168,3 +282,50 @@ export const createSelectorMemoized: CreateSelectorFunction = (...args: any) => return selector; }; + +export const createArgumentsSelectorMemoized: CreateArgumentsSelectorFunction = + () => + (...args: any) => { + const selector = (stateOrApiRef: any, selectorArgs: any, instanceId?: any) => { + const isAPIRef = checkIsAPIRef(stateOrApiRef); + const cacheKey = isAPIRef + ? stateOrApiRef.current.instanceId + : (instanceId ?? DEFAULT_INSTANCE_ID); + const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef; + + if (process.env.NODE_ENV !== 'production') { + if (cacheKey.id === 'default') { + warnOnce([ + 'MUI X: A selector was called without passing the instance ID, which may impact the performance of the grid.', + 'To fix, call it with `apiRef`, for example `mySelector(apiRef)`, or pass the instance ID explicitly, for example `mySelector(state, apiRef.current.instanceId)`.', + ]); + } + } + + const cacheArgsInit = cache.get(cacheKey); + const cacheArgs = cacheArgsInit ?? new Map(); + const cacheFn = cacheArgs?.get(args); + + if (cacheArgs && cacheFn) { + // We pass the cache key because the called selector might have as + // dependency another selector created with this `createSelector`. + return cacheFn(state, selectorArgs, cacheKey); + } + + const fn = reselectCreateSelector(...args); + + if (!cacheArgsInit) { + cache.set(cacheKey, cacheArgs); + } + cacheArgs.set(args, fn); + + return fn(state, selectorArgs, cacheKey); + }; + + // We use this property to detect if the selector was created with createSelector + // or it's only a simple function the receives the state and returns part of it. + selector.acceptsApiRef = true; + selector.acceptsArguments = true; + + return selector; + }; From 97348c2b29c6078637ab2df32c347a1ff54b7803 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Thu, 22 Aug 2024 12:21:43 +0500 Subject: [PATCH 2/6] Remove acceptsArguments from arguments selector --- .../src/hooks/utils/useGridSelector.ts | 16 +++++----------- packages/x-data-grid/src/utils/createSelector.ts | 3 --- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/packages/x-data-grid/src/hooks/utils/useGridSelector.ts b/packages/x-data-grid/src/hooks/utils/useGridSelector.ts index 63b8f11e3c794..f2a2a33ed8f67 100644 --- a/packages/x-data-grid/src/hooks/utils/useGridSelector.ts +++ b/packages/x-data-grid/src/hooks/utils/useGridSelector.ts @@ -15,15 +15,9 @@ function isOutputSelector( type Selector = | ((state: Api['state']) => T) - | OutputSelector | OutputArgumentsSelector; -function isArgumentsSelector( - selector: any, -): selector is OutputArgumentsSelector { - return selector.acceptsArguments; -} - +// TODO v8: Remove this function function applySelector( apiRef: React.MutableRefObject, selector: ((state: Api['state']) => T) | OutputSelector, @@ -34,6 +28,7 @@ function applySelector( return selector(apiRef.current.state); } +// TODO v8: Rename this function to `applySelector` function applySelectorV8( apiRef: React.MutableRefObject, selector: Selector, @@ -41,10 +36,7 @@ function applySelectorV8( instanceId: GridCoreApi['instanceId'], ) { if (isOutputSelector(selector)) { - if (isArgumentsSelector(selector)) { - return selector(apiRef, args); - } - return selector(apiRef); + return selector(apiRef, args); } return selector(apiRef.current.state, instanceId); } @@ -54,6 +46,7 @@ export const objectShallowCompare = fastObjectShallowCompare; const createRefs = () => ({ state: null, equals: null, selector: null }) as any; +// TODO v8: Remove this function export const useGridSelector = ( apiRef: React.MutableRefObject, selector: ((state: Api['state']) => T) | OutputSelector, @@ -100,6 +93,7 @@ export const useGridSelector = ( return state; }; +// TODO v8: Rename this function to `useGridSelector` export const useGridSelectorV8 = ( apiRef: React.MutableRefObject, selector: Selector, diff --git a/packages/x-data-grid/src/utils/createSelector.ts b/packages/x-data-grid/src/utils/createSelector.ts index b5ab88a84f872..cdcca4490491b 100644 --- a/packages/x-data-grid/src/utils/createSelector.ts +++ b/packages/x-data-grid/src/utils/createSelector.ts @@ -18,7 +18,6 @@ export interface OutputArgumentsSelector { ): Result; (state: State, instanceId: GridCoreApi['instanceId']): Result; acceptsApiRef: boolean; - acceptsArguments: boolean; } type StateFromSelector = T extends (first: infer F, ...args: any[]) => any @@ -234,7 +233,6 @@ export const createArgumentsSelector = (() => // We use this property to detect if the selector was created with createSelector // or it's only a simple function the receives the state and returns part of it. selector.acceptsApiRef = true; - selector.acceptsArguments = true; return selector; }) as unknown as CreateArgumentsSelectorFunction; @@ -325,7 +323,6 @@ export const createArgumentsSelectorMemoized: CreateArgumentsSelectorFunction = // We use this property to detect if the selector was created with createSelector // or it's only a simple function the receives the state and returns part of it. selector.acceptsApiRef = true; - selector.acceptsArguments = true; return selector; }; From a64f4476c4885a7f1414bda6d3f1deb273c71197 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Thu, 22 Aug 2024 14:49:04 +0500 Subject: [PATCH 3/6] Remove object as default argument --- packages/x-data-grid/src/hooks/utils/useGridSelector.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-data-grid/src/hooks/utils/useGridSelector.ts b/packages/x-data-grid/src/hooks/utils/useGridSelector.ts index f2a2a33ed8f67..36d8bae39cc7f 100644 --- a/packages/x-data-grid/src/hooks/utils/useGridSelector.ts +++ b/packages/x-data-grid/src/hooks/utils/useGridSelector.ts @@ -97,7 +97,7 @@ export const useGridSelector = ( export const useGridSelectorV8 = ( apiRef: React.MutableRefObject, selector: Selector, - args: Args = {} as Args, + args: Args = undefined as Args, equals: (a: T, b: T) => boolean = defaultCompare, ) => { if (process.env.NODE_ENV !== 'production') { From 99753bc1ff4fd2446ae02361802d794cf1c5a8b6 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Thu, 22 Aug 2024 14:59:10 +0500 Subject: [PATCH 4/6] Refactor as per Armin's suggestion --- .../dataSource/gridDataSourceSelector.ts | 10 +- packages/x-data-grid/src/internals/index.ts | 4 +- .../x-data-grid/src/utils/createSelector.ts | 228 +++++++++--------- 3 files changed, 120 insertions(+), 122 deletions(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts index 199d473faaf9c..6b6aa5a9404d9 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts @@ -5,7 +5,7 @@ import { gridPaginationModelSelector, GridRowId, } from '@mui/x-data-grid'; -import { createSelector, createArgumentsSelector } from '@mui/x-data-grid/internals'; +import { createSelector, createSelectorV8 } from '@mui/x-data-grid/internals'; import { GridStatePro } from '../../../models/gridStatePro'; const computeStartEnd = (paginationModel: GridPaginationModel) => { @@ -38,9 +38,9 @@ export const gridDataSourceLoadingSelector = createSelector( (dataSource) => dataSource.loading, ); -export const gridDataSourceLoadingIdSelector = createArgumentsSelector()( +export const gridDataSourceLoadingIdSelector = createSelectorV8( gridDataSourceStateSelector, - (dataSource, id) => dataSource.loading[id] ?? false, + (dataSource, id: GridRowId) => dataSource.loading[id] ?? false, ); export const gridDataSourceErrorsSelector = createSelector( @@ -48,7 +48,7 @@ export const gridDataSourceErrorsSelector = createSelector( (dataSource) => dataSource.errors, ); -export const gridDataSourceErrorSelector = createArgumentsSelector()( +export const gridDataSourceErrorSelector = createSelectorV8( gridDataSourceStateSelector, - (dataSource, id) => dataSource.errors[id], + (dataSource, id: GridRowId) => dataSource.errors[id], ); diff --git a/packages/x-data-grid/src/internals/index.ts b/packages/x-data-grid/src/internals/index.ts index dad8a7057137f..864b5b5890138 100644 --- a/packages/x-data-grid/src/internals/index.ts +++ b/packages/x-data-grid/src/internals/index.ts @@ -141,9 +141,9 @@ export { getColumnsToExport, defaultGetRowsToExport } from '../hooks/features/ex export * from '../utils/createControllablePromise'; export { createSelector, - createArgumentsSelector, + createSelectorV8, createSelectorMemoized, - createArgumentsSelectorMemoized, + createSelectorMemoizedV8, } from '../utils/createSelector'; export { useGridSelectorV8 } from '../hooks/utils/useGridSelector'; export { gridRowGroupsToFetchSelector } from '../hooks/features/rows/gridRowsSelector'; diff --git a/packages/x-data-grid/src/utils/createSelector.ts b/packages/x-data-grid/src/utils/createSelector.ts index cdcca4490491b..05e3c0881c59d 100644 --- a/packages/x-data-grid/src/utils/createSelector.ts +++ b/packages/x-data-grid/src/utils/createSelector.ts @@ -59,8 +59,9 @@ type CreateSelectorFunction = >, R ...items: SelectorArgs ) => OutputSelector, Result>; -type CreateArgumentsSelectorFunction = () => < +type CreateArgumentsSelectorFunction = < Selectors extends ReadonlyArray>, + Args, Result, >( ...items: ArgumentsSelectorArgs @@ -155,87 +156,86 @@ export const createSelector = (( return selector; }) as unknown as CreateSelectorFunction; -export const createArgumentsSelector = (() => - ( - a: Function, - b: Function, - c?: Function, - d?: Function, - e?: Function, - f?: Function, - ...other: any[] - ) => { - if (other.length > 0) { - throw new Error('Unsupported number of selectors'); - } +export const createSelectorV8 = (( + a: Function, + b: Function, + c?: Function, + d?: Function, + e?: Function, + f?: Function, + ...other: any[] +) => { + if (other.length > 0) { + throw new Error('Unsupported number of selectors'); + } - let selector: any; - - if (a && b && c && d && e && f) { - selector = (stateOrApiRef: any, args: any, instanceIdParam: any) => { - const isAPIRef = checkIsAPIRef(stateOrApiRef); - const instanceId = - instanceIdParam ?? (isAPIRef ? stateOrApiRef.current.instanceId : DEFAULT_INSTANCE_ID); - const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef; - const va = a(state, args, instanceId); - const vb = b(state, args, instanceId); - const vc = c(state, args, instanceId); - const vd = d(state, args, instanceId); - const ve = e(state, args, instanceId); - return f(va, vb, vc, vd, ve, args); - }; - } else if (a && b && c && d && e) { - selector = (stateOrApiRef: any, args: any, instanceIdParam: any) => { - const isAPIRef = checkIsAPIRef(stateOrApiRef); - const instanceId = - instanceIdParam ?? (isAPIRef ? stateOrApiRef.current.instanceId : DEFAULT_INSTANCE_ID); - const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef; - const va = a(state, args, instanceId); - const vb = b(state, args, instanceId); - const vc = c(state, args, instanceId); - const vd = d(state, args, instanceId); - return e(va, vb, vc, vd, args); - }; - } else if (a && b && c && d) { - selector = (stateOrApiRef: any, args: any, instanceIdParam: any) => { - const isAPIRef = checkIsAPIRef(stateOrApiRef); - const instanceId = - instanceIdParam ?? (isAPIRef ? stateOrApiRef.current.instanceId : DEFAULT_INSTANCE_ID); - const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef; - const va = a(state, args, instanceId); - const vb = b(state, args, instanceId); - const vc = c(state, args, instanceId); - return d(va, vb, vc, args); - }; - } else if (a && b && c) { - selector = (stateOrApiRef: any, args: any, instanceIdParam: any) => { - const isAPIRef = checkIsAPIRef(stateOrApiRef); - const instanceId = - instanceIdParam ?? (isAPIRef ? stateOrApiRef.current.instanceId : DEFAULT_INSTANCE_ID); - const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef; - const va = a(state, args, instanceId); - const vb = b(state, args, instanceId); - return c(va, vb, args); - }; - } else if (a && b) { - selector = (stateOrApiRef: any, args: any, instanceIdParam: any) => { - const isAPIRef = checkIsAPIRef(stateOrApiRef); - const instanceId = - instanceIdParam ?? (isAPIRef ? stateOrApiRef.current.instanceId : DEFAULT_INSTANCE_ID); - const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef; - const va = a(state, args, instanceId); - return b(va, args); - }; - } else { - throw new Error('Missing arguments'); - } + let selector: any; + + if (a && b && c && d && e && f) { + selector = (stateOrApiRef: any, args: any, instanceIdParam: any) => { + const isAPIRef = checkIsAPIRef(stateOrApiRef); + const instanceId = + instanceIdParam ?? (isAPIRef ? stateOrApiRef.current.instanceId : DEFAULT_INSTANCE_ID); + const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef; + const va = a(state, args, instanceId); + const vb = b(state, args, instanceId); + const vc = c(state, args, instanceId); + const vd = d(state, args, instanceId); + const ve = e(state, args, instanceId); + return f(va, vb, vc, vd, ve, args); + }; + } else if (a && b && c && d && e) { + selector = (stateOrApiRef: any, args: any, instanceIdParam: any) => { + const isAPIRef = checkIsAPIRef(stateOrApiRef); + const instanceId = + instanceIdParam ?? (isAPIRef ? stateOrApiRef.current.instanceId : DEFAULT_INSTANCE_ID); + const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef; + const va = a(state, args, instanceId); + const vb = b(state, args, instanceId); + const vc = c(state, args, instanceId); + const vd = d(state, args, instanceId); + return e(va, vb, vc, vd, args); + }; + } else if (a && b && c && d) { + selector = (stateOrApiRef: any, args: any, instanceIdParam: any) => { + const isAPIRef = checkIsAPIRef(stateOrApiRef); + const instanceId = + instanceIdParam ?? (isAPIRef ? stateOrApiRef.current.instanceId : DEFAULT_INSTANCE_ID); + const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef; + const va = a(state, args, instanceId); + const vb = b(state, args, instanceId); + const vc = c(state, args, instanceId); + return d(va, vb, vc, args); + }; + } else if (a && b && c) { + selector = (stateOrApiRef: any, args: any, instanceIdParam: any) => { + const isAPIRef = checkIsAPIRef(stateOrApiRef); + const instanceId = + instanceIdParam ?? (isAPIRef ? stateOrApiRef.current.instanceId : DEFAULT_INSTANCE_ID); + const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef; + const va = a(state, args, instanceId); + const vb = b(state, args, instanceId); + return c(va, vb, args); + }; + } else if (a && b) { + selector = (stateOrApiRef: any, args: any, instanceIdParam: any) => { + const isAPIRef = checkIsAPIRef(stateOrApiRef); + const instanceId = + instanceIdParam ?? (isAPIRef ? stateOrApiRef.current.instanceId : DEFAULT_INSTANCE_ID); + const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef; + const va = a(state, args, instanceId); + return b(va, args); + }; + } else { + throw new Error('Missing arguments'); + } - // We use this property to detect if the selector was created with createSelector - // or it's only a simple function the receives the state and returns part of it. - selector.acceptsApiRef = true; + // We use this property to detect if the selector was created with createSelector + // or it's only a simple function the receives the state and returns part of it. + selector.acceptsApiRef = true; - return selector; - }) as unknown as CreateArgumentsSelectorFunction; + return selector; +}) as unknown as CreateArgumentsSelectorFunction; export const createSelectorMemoized: CreateSelectorFunction = (...args: any) => { const selector = (stateOrApiRef: any, instanceId?: any) => { @@ -281,48 +281,46 @@ export const createSelectorMemoized: CreateSelectorFunction = (...args: any) => return selector; }; -export const createArgumentsSelectorMemoized: CreateArgumentsSelectorFunction = - () => - (...args: any) => { - const selector = (stateOrApiRef: any, selectorArgs: any, instanceId?: any) => { - const isAPIRef = checkIsAPIRef(stateOrApiRef); - const cacheKey = isAPIRef - ? stateOrApiRef.current.instanceId - : (instanceId ?? DEFAULT_INSTANCE_ID); - const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef; +export const createSelectorMemoizedV8: CreateArgumentsSelectorFunction = (...args: any) => { + const selector = (stateOrApiRef: any, selectorArgs: any, instanceId?: any) => { + const isAPIRef = checkIsAPIRef(stateOrApiRef); + const cacheKey = isAPIRef + ? stateOrApiRef.current.instanceId + : (instanceId ?? DEFAULT_INSTANCE_ID); + const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef; - if (process.env.NODE_ENV !== 'production') { - if (cacheKey.id === 'default') { - warnOnce([ - 'MUI X: A selector was called without passing the instance ID, which may impact the performance of the grid.', - 'To fix, call it with `apiRef`, for example `mySelector(apiRef)`, or pass the instance ID explicitly, for example `mySelector(state, apiRef.current.instanceId)`.', - ]); - } + if (process.env.NODE_ENV !== 'production') { + if (cacheKey.id === 'default') { + warnOnce([ + 'MUI X: A selector was called without passing the instance ID, which may impact the performance of the grid.', + 'To fix, call it with `apiRef`, for example `mySelector(apiRef)`, or pass the instance ID explicitly, for example `mySelector(state, apiRef.current.instanceId)`.', + ]); } + } - const cacheArgsInit = cache.get(cacheKey); - const cacheArgs = cacheArgsInit ?? new Map(); - const cacheFn = cacheArgs?.get(args); + const cacheArgsInit = cache.get(cacheKey); + const cacheArgs = cacheArgsInit ?? new Map(); + const cacheFn = cacheArgs?.get(args); - if (cacheArgs && cacheFn) { - // We pass the cache key because the called selector might have as - // dependency another selector created with this `createSelector`. - return cacheFn(state, selectorArgs, cacheKey); - } + if (cacheArgs && cacheFn) { + // We pass the cache key because the called selector might have as + // dependency another selector created with this `createSelector`. + return cacheFn(state, selectorArgs, cacheKey); + } - const fn = reselectCreateSelector(...args); + const fn = reselectCreateSelector(...args); - if (!cacheArgsInit) { - cache.set(cacheKey, cacheArgs); - } - cacheArgs.set(args, fn); + if (!cacheArgsInit) { + cache.set(cacheKey, cacheArgs); + } + cacheArgs.set(args, fn); - return fn(state, selectorArgs, cacheKey); - }; + return fn(state, selectorArgs, cacheKey); + }; - // We use this property to detect if the selector was created with createSelector - // or it's only a simple function the receives the state and returns part of it. - selector.acceptsApiRef = true; + // We use this property to detect if the selector was created with createSelector + // or it's only a simple function the receives the state and returns part of it. + selector.acceptsApiRef = true; - return selector; - }; + return selector; +}; From ef21ca8d695e98738345bb74f5605a3b1c939d9f Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Thu, 22 Aug 2024 15:12:54 +0500 Subject: [PATCH 5/6] Rename remaining types and add comments --- .../x-data-grid/src/utils/createSelector.ts | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/x-data-grid/src/utils/createSelector.ts b/packages/x-data-grid/src/utils/createSelector.ts index 05e3c0881c59d..44fbe4cdb0674 100644 --- a/packages/x-data-grid/src/utils/createSelector.ts +++ b/packages/x-data-grid/src/utils/createSelector.ts @@ -5,13 +5,15 @@ import { warnOnce } from '../internals/utils/warning'; type CacheKey = { id: number }; +// TODO v8: Remove this type export interface OutputSelector { (apiRef: React.MutableRefObject<{ state: State; instanceId: GridCoreApi['instanceId'] }>): Result; (state: State, instanceId: GridCoreApi['instanceId']): Result; acceptsApiRef: boolean; } -export interface OutputArgumentsSelector { +// TODO v8: Rename this type to `OutputSelector` +export interface OutputSelectorV8 { ( apiRef: React.MutableRefObject<{ state: State; instanceId: GridCoreApi['instanceId'] }>, args: Args, @@ -35,6 +37,7 @@ type StateFromSelectorList = Selectors extends : StateFromSelectorList : {}; +// TODO v8: Remove this type type SelectorArgs>, Result> = // Input selectors as a separate array | [selectors: [...Selectors], combiner: (...args: SelectorResultArray) => Result] @@ -46,7 +49,8 @@ type SelectorResultArrayWithArgs>, Args, ]; -type ArgumentsSelectorArgs>, Args, Result> = +// TODO v8: Rename this type to `SelectorArgs` +type SelectorArgsV8>, Args, Result> = // Input selectors as a separate array | [ selectors: [...Selectors], @@ -55,17 +59,19 @@ type ArgumentsSelectorArgs>, Args, // Input selectors as separate inline arguments | [...Selectors, (...args: SelectorResultArrayWithArgs) => Result]; +// TODO v8: Remove this type type CreateSelectorFunction = >, Result>( ...items: SelectorArgs ) => OutputSelector, Result>; -type CreateArgumentsSelectorFunction = < +// TODO v8: Rename this type to `CreateSelectorFunction` +type CreateSelectorFunctionV8 = < Selectors extends ReadonlyArray>, Args, Result, >( - ...items: ArgumentsSelectorArgs -) => OutputArgumentsSelector, Args, Result>; + ...items: SelectorArgsV8 +) => OutputSelectorV8, Args, Result>; const cache = new WeakMap>(); @@ -75,6 +81,7 @@ function checkIsAPIRef(value: any) { const DEFAULT_INSTANCE_ID = { id: 'default' }; +// TODO v8: Remove this function export const createSelector = (( a: Function, b: Function, @@ -156,6 +163,7 @@ export const createSelector = (( return selector; }) as unknown as CreateSelectorFunction; +// TODO v8: Rename this function to `createSelector` export const createSelectorV8 = (( a: Function, b: Function, @@ -235,8 +243,9 @@ export const createSelectorV8 = (( selector.acceptsApiRef = true; return selector; -}) as unknown as CreateArgumentsSelectorFunction; +}) as unknown as CreateSelectorFunctionV8; +// TODO v8: Remove this function export const createSelectorMemoized: CreateSelectorFunction = (...args: any) => { const selector = (stateOrApiRef: any, instanceId?: any) => { const isAPIRef = checkIsAPIRef(stateOrApiRef); @@ -281,7 +290,8 @@ export const createSelectorMemoized: CreateSelectorFunction = (...args: any) => return selector; }; -export const createSelectorMemoizedV8: CreateArgumentsSelectorFunction = (...args: any) => { +// TODO v8: Rename this function to `createSelectorMemoized` +export const createSelectorMemoizedV8: CreateSelectorFunctionV8 = (...args: any) => { const selector = (stateOrApiRef: any, selectorArgs: any, instanceId?: any) => { const isAPIRef = checkIsAPIRef(stateOrApiRef); const cacheKey = isAPIRef From b9f52924783c646aea8387fca2641bef0d6f1584 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Thu, 22 Aug 2024 15:54:05 +0500 Subject: [PATCH 6/6] Prettier and fix import name --- packages/x-data-grid/src/hooks/utils/useGridSelector.ts | 4 ++-- packages/x-data-grid/src/utils/createSelector.ts | 6 +----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/x-data-grid/src/hooks/utils/useGridSelector.ts b/packages/x-data-grid/src/hooks/utils/useGridSelector.ts index 36d8bae39cc7f..470c7a554db6b 100644 --- a/packages/x-data-grid/src/hooks/utils/useGridSelector.ts +++ b/packages/x-data-grid/src/hooks/utils/useGridSelector.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { fastObjectShallowCompare } from '@mui/x-internals/fastObjectShallowCompare'; import type { GridApiCommon } from '../../models/api/gridApiCommon'; -import { OutputSelector, OutputArgumentsSelector } from '../../utils/createSelector'; +import { OutputSelector, OutputSelectorV8 } from '../../utils/createSelector'; import { useLazyRef } from './useLazyRef'; import { useOnMount } from './useOnMount'; import { warnOnce } from '../../internals/utils/warning'; @@ -15,7 +15,7 @@ function isOutputSelector( type Selector = | ((state: Api['state']) => T) - | OutputArgumentsSelector; + | OutputSelectorV8; // TODO v8: Remove this function function applySelector( diff --git a/packages/x-data-grid/src/utils/createSelector.ts b/packages/x-data-grid/src/utils/createSelector.ts index 44fbe4cdb0674..a9a9bc69010fa 100644 --- a/packages/x-data-grid/src/utils/createSelector.ts +++ b/packages/x-data-grid/src/utils/createSelector.ts @@ -65,11 +65,7 @@ type CreateSelectorFunction = >, R ) => OutputSelector, Result>; // TODO v8: Rename this type to `CreateSelectorFunction` -type CreateSelectorFunctionV8 = < - Selectors extends ReadonlyArray>, - Args, - Result, ->( +type CreateSelectorFunctionV8 = >, Args, Result>( ...items: SelectorArgsV8 ) => OutputSelectorV8, Args, Result>;