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);
+}