Skip to content

Commit

Permalink
Add keySelectorCreator option
Browse files Browse the repository at this point in the history
  • Loading branch information
toomuchdesign committed Jun 22, 2019
1 parent e44ce2d commit c17fec3
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 23 deletions.
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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`<br />
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**.
Expand Down
53 changes: 38 additions & 15 deletions src/__tests__/createCachedSelector.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()', () => {
Expand Down
8 changes: 8 additions & 0 deletions src/createCachedSelector.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
30 changes: 25 additions & 5 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,25 @@ export type OutputParametricSelector<S, P, R, C, D> = ParametricSelector<

export type CreateSelectorInstance = typeof createSelector;

type Options =
type Options<S, C, D> =
| {
selectorCreator?: CreateSelectorInstance;
cacheObject: ICacheObject;
cacheObject?: ICacheObject;
keySelectorCreator?: KeySelectorCreator<S, C, D>;
}
| CreateSelectorInstance;

type ParametricOptions<S, P, C, D> =
| {
selectorCreator: CreateSelectorInstance;
selectorCreator?: CreateSelectorInstance;
cacheObject?: ICacheObject;
keySelectorCreator?: ParametricKeySelectorCreator<S, P, C, D>;
}
| CreateSelectorInstance;

export type OutputCachedSelector<S, R, C, D> = (
keySelector: KeySelector<S>,
optionsOrSelectorCreator?: Options
optionsOrSelectorCreator?: Options<S, C, D>
) => OutputSelector<S, R, C, D> & {
getMatchingSelector: (state: S, ...args: any[]) => OutputSelector<S, R, C, D>;
removeMatchingSelector: (state: S, ...args: any[]) => void;
Expand All @@ -58,7 +63,7 @@ export type OutputCachedSelector<S, R, C, D> = (

export type OutputParametricCachedSelector<S, P, R, C, D> = (
keySelector: ParametricKeySelector<S, P>,
optionsOrSelectorCreator?: Options
optionsOrSelectorCreator?: ParametricOptions<S, P, C, D>
) => OutputParametricSelector<S, P, R, C, D> & {
getMatchingSelector: (
state: S,
Expand Down Expand Up @@ -4447,3 +4452,18 @@ export class LruMapCache implements ICacheObject {
remove(key: any): void;
clear(): void;
}

/*
* Key selector creators
*/
export type KeySelectorCreator<S, C, D> = (selectorInputs: {
inputSelectors: D;
resultFunc: C;
keySelector: KeySelector<S>;
}) => KeySelector<S>;

export type ParametricKeySelectorCreator<S, P, C, D> = (selectorInputs: {
inputSelectors: D;
resultFunc: C;
keySelector: ParametricKeySelector<S, P>;
}) => ParametricKeySelector<S, P>;
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ function testArrayArgument() {
}
}

function testResolver() {
function testKeySelector() {
type State = {foo: string; obj: {bar: string}};

const selector = createCachedSelector(
Expand All @@ -348,7 +348,7 @@ function testResolver() {
)((state: never, obj) => obj);
}

function testCustomSelectorCreator() {
function testSelectorCreatorOption() {
type State = {foo: string};

const selector1 = createCachedSelector(
Expand All @@ -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);
}

0 comments on commit c17fec3

Please sign in to comment.