diff --git a/packages/data/README.md b/packages/data/README.md index 757b4e8c383ae4..d56687a65c190f 100644 --- a/packages/data/README.md +++ b/packages/data/README.md @@ -174,11 +174,16 @@ const reduxStore = createStore(); const boundSelectors = mapValues( existingSelectors, - ( selector ) => ( ...args ) => selector( reduxStore.getState(), ...args ) + ( selector ) => + ( ...args ) => + selector( reduxStore.getState(), ...args ) ); -const boundActions = mapValues( existingActions, ( action ) => ( ...args ) => - reduxStore.dispatch( action( ...args ) ) +const boundActions = mapValues( + existingActions, + ( action ) => + ( ...args ) => + reduxStore.dispatch( action( ...args ) ) ); const genericStore = { @@ -369,11 +374,11 @@ const store = createReduxStore( 'demo', { _Parameters_ - _key_ `string`: Unique namespace identifier. -- _options_ `ReduxStoreConfig`: Registered store options, with properties describing reducer, actions, selectors, and resolvers. +- _options_ `ReduxStoreConfig`: Registered store options, with properties describing reducer, actions, selectors, and resolvers. _Returns_ -- `StoreDescriptor`: Store Object. +- `StoreDescriptor>`: Store Object. ### createRegistry @@ -431,7 +436,9 @@ that allows to select data from the store's `state`, a registry selector has signature: ```js -( select ) => ( state, ...selectorArgs ) => result; +( select ) => + ( state, ...selectorArgs ) => + result; ``` that supports also selecting from other registered stores. @@ -834,12 +841,12 @@ function Paste( { children } ) { _Parameters_ -- _mapSelect_ `Function|StoreDescriptor|string`: Function called on every state change. The returned value is exposed to the component implementing this hook. The function receives the `registry.select` method on the first argument and the `registry` on the second argument. When a store key is passed, all selectors for the store will be returned. This is only meant for usage of these selectors in event callbacks, not for data needed to create the element tree. -- _deps_ `Array`: If provided, this memoizes the mapSelect so the same `mapSelect` is invoked on every state change unless the dependencies change. +- _mapSelect_ `T`: Function called on every state change. The returned value is exposed to the component implementing this hook. The function receives the `registry.select` method on the first argument and the `registry` on the second argument. When a store key is passed, all selectors for the store will be returned. This is only meant for usage of these selectors in event callbacks, not for data needed to create the element tree. +- _deps_ `unknown[]`: If provided, this memoizes the mapSelect so the same `mapSelect` is invoked on every state change unless the dependencies change. _Returns_ -- `Function`: A custom react hook. +- `UseSelectReturn`: A custom react hook. ### useSuspenseSelect diff --git a/packages/data/src/components/use-select/index.js b/packages/data/src/components/use-select/index.js index 575af0369ed090..d4190dea47ad88 100644 --- a/packages/data/src/components/use-select/index.js +++ b/packages/data/src/components/use-select/index.js @@ -20,7 +20,19 @@ import useAsyncMode from '../async-mode-provider/use-async-mode'; const noop = () => {}; const renderQueue = createQueue(); -/** @typedef {import('../../types').StoreDescriptor} StoreDescriptor */ +/** + * @typedef {import('../../types').StoreDescriptor} StoreDescriptor + * @template C + */ +/** + * @typedef {import('../../types').ReduxStoreConfig} ReduxStoreConfig + * @template State,Actions,Selectors + */ +/** + * @typedef {import('../../types').UseSelectReturn} UseSelectReturn + * @template T + */ +/** @typedef {import('../../types').MapSelect} MapSelect */ /** * Custom react hook for retrieving props from registered selectors. @@ -28,20 +40,16 @@ const renderQueue = createQueue(); * In general, this custom React hook follows the * [rules of hooks](https://reactjs.org/docs/hooks-rules.html). * - * @param {Function|StoreDescriptor|string} mapSelect Function called on every state change. The - * returned value is exposed to the component - * implementing this hook. The function receives - * the `registry.select` method on the first - * argument and the `registry` on the second - * argument. - * When a store key is passed, all selectors for - * the store will be returned. This is only meant - * for usage of these selectors in event - * callbacks, not for data needed to create the - * element tree. - * @param {Array} deps If provided, this memoizes the mapSelect so the - * same `mapSelect` is invoked on every state - * change unless the dependencies change. + * @template {MapSelect | StoreDescriptor} T + * @param {T} mapSelect Function called on every state change. The returned value is + * exposed to the component implementing this hook. The function + * receives the `registry.select` method on the first argument + * and the `registry` on the second argument. + * When a store key is passed, all selectors for the store will be + * returned. This is only meant for usage of these selectors in event + * callbacks, not for data needed to create the element tree. + * @param {unknown[]} deps If provided, this memoizes the mapSelect so the same `mapSelect` is + * invoked on every state change unless the dependencies change. * * @example * ```js @@ -86,8 +94,7 @@ const renderQueue = createQueue(); * return
{ children }
; * } * ``` - * - * @return {Function} A custom react hook. + * @return {UseSelectReturn} A custom react hook. */ export default function useSelect( mapSelect, deps ) { const hasMappingFunction = 'function' === typeof mapSelect; diff --git a/packages/data/src/redux-store/index.js b/packages/data/src/redux-store/index.js index 08b36435d7fdc7..81a5e3e56cb4f5 100644 --- a/packages/data/src/redux-store/index.js +++ b/packages/data/src/redux-store/index.js @@ -23,8 +23,14 @@ import * as metadataSelectors from './metadata/selectors'; import * as metadataActions from './metadata/actions'; /** @typedef {import('../types').DataRegistry} DataRegistry */ -/** @typedef {import('../types').StoreDescriptor} StoreDescriptor */ -/** @typedef {import('../types').ReduxStoreConfig} ReduxStoreConfig */ +/** + * @typedef {import('../types').StoreDescriptor} StoreDescriptor + * @template C + */ +/** + * @typedef {import('../types').ReduxStoreConfig} ReduxStoreConfig + * @template State,Actions,Selectors + */ const trimUndefinedValues = ( array ) => { const result = [ ...array ]; @@ -83,12 +89,13 @@ function createResolversCache() { * } ); * ``` * - * @param {string} key Unique namespace identifier. - * @param {ReduxStoreConfig} options Registered store options, with properties - * describing reducer, actions, selectors, - * and resolvers. + * @template State,Actions,Selectors + * @param {string} key Unique namespace identifier. + * @param {ReduxStoreConfig} options Registered store options, with properties + * describing reducer, actions, selectors, + * and resolvers. * - * @return {StoreDescriptor} Store Object. + * @return {StoreDescriptor>} Store Object. */ export default function createReduxStore( key, options ) { return { diff --git a/packages/data/src/types.ts b/packages/data/src/types.ts index 6094aa19674b55..ed65772988ed8f 100644 --- a/packages/data/src/types.ts +++ b/packages/data/src/types.ts @@ -27,7 +27,7 @@ export interface StoreDescriptor< Config extends AnyConfig > { export interface ReduxStoreConfig< State, ActionCreators extends MapOf< ActionCreator >, - Selectors extends MapOf< Selector > + Selectors > { initialState?: State; reducer: ( state: any, action: any ) => any; @@ -37,6 +37,40 @@ export interface ReduxStoreConfig< controls?: MapOf< Function >; } +export type UseSelectReturn< F extends MapSelect | StoreDescriptor< any > > = + F extends MapSelect + ? ReturnType< F > + : F extends StoreDescriptor< any > + ? CurriedSelectorsOf< F > + : never; + +export type MapSelect = ( select: SelectFunction ) => any; + +export type SelectFunction = < S >( store: S ) => CurriedSelectorsOf< S >; + +export type CurriedSelectorsOf< S > = S extends StoreDescriptor< + ReduxStoreConfig< any, any, infer Selectors > +> + ? { [ key in keyof Selectors ]: CurriedState< Selectors[ key ] > } + : never; + +/** + * Removes the first argument from a function + * + * This is designed to remove the `state` parameter from + * registered selectors since that argument is supplied + * by the editor when calling `select(…)`. + * + * For functions with no arguments, which some selectors + * are free to define, returns the original function. + */ +export type CurriedState< F > = F extends ( + state: any, + ...args: infer P +) => infer R + ? ( ...args: P ) => R + : F; + export interface DataRegistry { register: ( store: StoreDescriptor< any > ) => void; } @@ -51,11 +85,10 @@ export interface DataEmitter { // Type Helpers. -type ActionCreatorsOf< - Config extends AnyConfig -> = Config extends ReduxStoreConfig< any, infer ActionCreators, any > - ? { [ name in keyof ActionCreators ]: Function | Generator } - : never; +type ActionCreatorsOf< Config extends AnyConfig > = + Config extends ReduxStoreConfig< any, infer ActionCreators, any > + ? { [ name in keyof ActionCreators ]: Function | Generator } + : never; type SelectorsOf< Config extends AnyConfig > = Config extends ReduxStoreConfig< any,