diff --git a/README.md b/README.md index 01e49e4e..26eac363 100644 --- a/README.md +++ b/README.md @@ -368,7 +368,7 @@ A custom function receiving the same arguments as your selectors (and `inputSele `cacheKey` is **by default a `string` or `number`** but can be anything depending on the chosen cache strategy (see [`cacheObject` option](#optionscacheobject)). -The `keySelector` idea comes from [Lodash's .memoize][lodash-memoize]. +The `keySelector` idea comes from [Lodash's .memoize resolver][lodash-memoize]. ### options @@ -386,6 +386,23 @@ Default: `reselect`'s [`createSelector`][reselect-create-selector] An optional function describing a [custom version of createSelector][reselect-create-selector-creator]. +#### keySelectorCreator + +Type: `function`
+Default: `undefined` + +An optional function with the following signature returning the [`keySelector`](#keyselector) used by the cached selector. + +```typescript +export type keySelectorCreator = (selectorInputs: { + inputSelectors: InputSelector[]; + resultFunc: ResultFunc; + keySelector: KeySelector; +}) => KeySelector; +``` + +This allows to dynamically **generate `keySelectors` on runtime** based on provided `inputSelectors`/`resultFunc` and support [**key selectors composition**](https://github.com/toomuchdesign/re-reselect/pull/73). + ### re-reselect selector instance `createCachedSelector` and `createStructuredCachedSelector` return a **selector instance** which extends the API of a **standard reselect selector**. diff --git a/src/__tests__/createCachedSelector.spec.js b/src/__tests__/createCachedSelector.spec.js index 1b6f145b..01468848 100644 --- a/src/__tests__/createCachedSelector.spec.js +++ b/src/__tests__/createCachedSelector.spec.js @@ -114,24 +114,47 @@ describe('createCachedSelector', () => { it('Should throw an error when a function is provided as 2° argument', () => { expect(() => { - const cachedSelector = createCachedSelector(resultFuncMock)(() => {}, - reselect.createSelector); + createCachedSelector(resultFuncMock)(() => {}, reselect.createSelector); }).toThrow(/Second argument "options" must be an object/); }); - it('Should accept an options object', () => { - const cachedSelector = createCachedSelector(resultFuncMock)( - (arg1, arg2) => arg2, - { - cacheObject: new FlatObjectCache(), - selectorCreator: reselect.createSelector, - } - ); - - expect(cachedSelector.recomputations()).toBe(0); - cachedSelector('foo', 'bar'); - cachedSelector('foo', 'bar'); - expect(cachedSelector.recomputations()).toBe(1); + describe('options', () => { + it('Should accept cacheObject and selectorCreator options', () => { + const cachedSelector = createCachedSelector(resultFuncMock)( + (arg1, arg2) => arg2, + { + cacheObject: new FlatObjectCache(), + selectorCreator: reselect.createSelector, + } + ); + + expect(cachedSelector.recomputations()).toBe(0); + cachedSelector('foo', 'bar'); + cachedSelector('foo', 'bar'); + expect(cachedSelector.recomputations()).toBe(1); + }); + + it('Should accept selectorCreator option', () => { + const inputSelector = () => {}; + const resultFunc = () => {}; + const keySelector = () => {}; + const generatedKeySelector = () => {}; + const keySelectorCreatorMock = jest.fn(() => generatedKeySelector); + + const cachedSelector = createCachedSelector(inputSelector, resultFunc)( + keySelector, + { + keySelectorCreator: keySelectorCreatorMock, + } + ); + + expect(keySelectorCreatorMock).toHaveBeenCalledWith({ + inputSelectors: [inputSelector], + resultFunc: resultFunc, + keySelector: keySelector, + }); + expect(cachedSelector.keySelector).toBe(generatedKeySelector); + }); }); describe('getMatchingSelector()', () => { diff --git a/src/createCachedSelector.js b/src/createCachedSelector.js index a566edc7..d598fdbb 100644 --- a/src/createCachedSelector.js +++ b/src/createCachedSelector.js @@ -28,6 +28,14 @@ function createCachedSelector(...funcs) { const selectorCreator = options.selectorCreator || createSelector; const isValidCacheKey = cache.isValidCacheKey || defaultCacheKeyValidator; + if (options.keySelectorCreator) { + keySelector = options.keySelectorCreator({ + inputSelectors: dependencies, + resultFunc, + keySelector, + }); + } + // Application receives this function const selector = function(...args) { const cacheKey = keySelector(...args); diff --git a/src/index.d.ts b/src/index.d.ts index 38f79396..92689f12 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -34,20 +34,25 @@ export type OutputParametricSelector = ParametricSelector< export type CreateSelectorInstance = typeof createSelector; -type Options = +type Options = | { selectorCreator?: CreateSelectorInstance; - cacheObject: ICacheObject; + cacheObject?: ICacheObject; + keySelectorCreator?: KeySelectorCreator; } + | CreateSelectorInstance; + +type ParametricOptions = | { - selectorCreator: CreateSelectorInstance; + selectorCreator?: CreateSelectorInstance; cacheObject?: ICacheObject; + keySelectorCreator?: ParametricKeySelectorCreator; } | CreateSelectorInstance; export type OutputCachedSelector = ( keySelector: KeySelector, - optionsOrSelectorCreator?: Options + optionsOrSelectorCreator?: Options ) => OutputSelector & { getMatchingSelector: (state: S, ...args: any[]) => OutputSelector; removeMatchingSelector: (state: S, ...args: any[]) => void; @@ -58,7 +63,7 @@ export type OutputCachedSelector = ( export type OutputParametricCachedSelector = ( keySelector: ParametricKeySelector, - optionsOrSelectorCreator?: Options + optionsOrSelectorCreator?: ParametricOptions ) => OutputParametricSelector & { getMatchingSelector: ( state: S, @@ -4447,3 +4452,18 @@ export class LruMapCache implements ICacheObject { remove(key: any): void; clear(): void; } + +/* + * Key selector creators + */ +export type KeySelectorCreator = (selectorInputs: { + inputSelectors: D; + resultFunc: C; + keySelector: KeySelector; +}) => KeySelector; + +export type ParametricKeySelectorCreator = (selectorInputs: { + inputSelectors: D; + resultFunc: C; + keySelector: ParametricKeySelector; +}) => ParametricKeySelector; diff --git a/typescript_test/test.ts b/typescript_test/createCachedSelector.ts similarity index 92% rename from typescript_test/test.ts rename to typescript_test/createCachedSelector.ts index aaafa2f2..85e51c9c 100644 --- a/typescript_test/test.ts +++ b/typescript_test/createCachedSelector.ts @@ -330,7 +330,7 @@ function testArrayArgument() { } } -function testResolver() { +function testKeySelector() { type State = {foo: string; obj: {bar: string}}; const selector = createCachedSelector( @@ -348,7 +348,7 @@ function testResolver() { )((state: never, obj) => obj); } -function testCustomSelectorCreator() { +function testSelectorCreatorOption() { type State = {foo: string}; const selector1 = createCachedSelector( @@ -369,3 +369,29 @@ function testCustomSelectorCreator() { foo => foo )((state: State) => state.foo, (): void => {}); } + +function testKeySelectorCreatorOption() { + type State = {foo: string}; + const state = {foo: 'bar'}; + const inputSelector = (state: State) => state.foo; + const resultFunc = (input: string) => input; + const keySelector = (state: State) => state.foo; + + const selector = createCachedSelector( + inputSelector, + inputSelector, + resultFunc + )(keySelector, { + keySelectorCreator: ({inputSelectors, resultFunc, keySelector}) => { + const input1 = inputSelectors[0](state); + const input2 = inputSelectors[1](state); + // typings:expect-error + inputSelectors[2]; + + const result: string = resultFunc(input1, input2); + return keySelector; + }, + }); + + const result: string = selector(state); +}