Skip to content

Commit

Permalink
Add new actions for invalidating resolution caches (#14225)
Browse files Browse the repository at this point in the history
* add new actions for invalidating resolution caches

* use path array for omit

* clarify logic
  • Loading branch information
nerrad authored and youknowriad committed Mar 20, 2019
1 parent 53dd02b commit 1fcb003
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 4 deletions.
2 changes: 2 additions & 0 deletions packages/data/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
### Enhancements

- The `registerStore` function now accepts an optional `initialState` option value.
- Introduce new `invalidateResolutionForStore` dispatch action for signalling to invalidate the resolution cache for an entire given store.
- Introduce new `invalidateResolutionForStoreSelector` dispatch action for signalling to invalidate the resolution cache for a store selector (and all variations of arguments on that selector).

### Bug Fix

Expand Down
36 changes: 36 additions & 0 deletions packages/data/src/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,39 @@ export function invalidateResolution( reducerKey, selectorName, args ) {
args,
};
}

/**
* Returns an action object used in signalling that the resolution cache for a
* given reducerKey should be invalidated.
*
* @param {string} reducerKey Registered store reducer key.
*
* @return {Object} Action object.
*/
export function invalidateResolutionForStore( reducerKey ) {
return {
type: 'INVALIDATE_RESOLUTION_FOR_STORE',
reducerKey,
};
}

/**
* Returns an action object used in signalling that the resolution cache for a
* given reducerKey and selectorName should be invalidated.
*
* @param {string} reducerKey Registered store reducer key.
* @param {string} selectorName Name of selector for which all resolvers should
* be invalidated.
*
* @return {Object} Action object.
*/
export function invalidateResolutionForStoreSelector(
reducerKey,
selectorName
) {
return {
type: 'INVALIDATE_RESOLUTION_FOR_STORE_SELECTOR',
reducerKey,
selectorName,
};
}
42 changes: 38 additions & 4 deletions packages/data/src/store/reducer.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
import { flowRight } from 'lodash';
import { flowRight, omit, has } from 'lodash';
import EquivalentKeyMap from 'equivalent-key-map';

/**
Expand All @@ -10,7 +10,8 @@ import EquivalentKeyMap from 'equivalent-key-map';
import { onSubKey } from './utils';

/**
* Reducer function returning next state for selector resolution, object form:
* Reducer function returning next state for selector resolution of
* subkeys, object form:
*
* reducerKey -> selectorName -> EquivalentKeyMap<Array,boolean>
*
Expand All @@ -19,7 +20,7 @@ import { onSubKey } from './utils';
*
* @returns {Object} Next state.
*/
const isResolved = flowRight( [
const subKeysIsResolved = flowRight( [
onSubKey( 'reducerKey' ),
onSubKey( 'selectorName' ),
] )( ( state = new EquivalentKeyMap(), action ) => {
Expand All @@ -37,8 +38,41 @@ const isResolved = flowRight( [
return nextState;
}
}

return state;
} );

/**
* Reducer function returning next state for selector resolution, object form:
*
* reducerKey -> selectorName -> EquivalentKeyMap<Array, boolean>
*
* @param {Object} state Current state.
* @param {Object} action Dispatched action.
*
* @return {Object} Next state.
*/
const isResolved = ( state = {}, action ) => {
switch ( action.type ) {
case 'INVALIDATE_RESOLUTION_FOR_STORE':
return has( state, action.reducerKey ) ?
omit( state, [ action.reducerKey ] ) :
state;
case 'INVALIDATE_RESOLUTION_FOR_STORE_SELECTOR':
return has( state, [ action.reducerKey, action.selectorName ] ) ?
{
...state,
[ action.reducerKey ]: omit(
state[ action.reducerKey ],
[ action.selectorName ]
),
} :
state;
case 'START_RESOLUTION':
case 'FINISH_RESOLUTION':
case 'INVALIDATE_RESOLUTION':
return subKeysIsResolved( state, action );
}
return state;
};

export default isResolved;
49 changes: 49 additions & 0 deletions packages/data/src/store/test/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,53 @@ describe( 'reducer', () => {
expect( state.test.getFoo.get( [ 'post' ] ) ).toBe( false );
expect( state.test.getFoo.get( [ 'block' ] ) ).toBe( true );
} );

it( 'should remove invalidation for store level and leave others ' +
'intact', () => {
const original = reducer( undefined, {
type: 'FINISH_RESOLUTION',
reducerKey: 'testA',
selectorName: 'getFoo',
args: [ 'post' ],
} );
let state = reducer( deepFreeze( original ), {
type: 'FINISH_RESOLUTION',
reducerKey: 'testB',
selectorName: 'getBar',
args: [ 'postBar' ],
} );
state = reducer( deepFreeze( state ), {
type: 'INVALIDATE_RESOLUTION_FOR_STORE',
reducerKey: 'testA',
} );

expect( state.testA ).toBeUndefined();
// { testB: { getBar: EquivalentKeyMap( [] => false ) } }
expect( state.testB.getBar.get( [ 'postBar' ] ) ).toBe( false );
} );

it( 'should remove invalidation for store and selector name level and ' +
'leave other selectors at store level intact', () => {
const original = reducer( undefined, {
type: 'FINISH_RESOLUTION',
reducerKey: 'test',
selectorName: 'getFoo',
args: [ 'post' ],
} );
let state = reducer( deepFreeze( original ), {
type: 'FINISH_RESOLUTION',
reducerKey: 'test',
selectorName: 'getBar',
args: [ 'postBar' ],
} );
state = reducer( deepFreeze( state ), {
type: 'INVALIDATE_RESOLUTION_FOR_STORE_SELECTOR',
reducerKey: 'test',
selectorName: 'getBar',
} );

expect( state.test.getBar ).toBeUndefined();
// { test: { getFoo: EquivalentKeyMap( [] => false ) } }
expect( state.test.getFoo.get( [ 'post' ] ) ).toBe( false );
} );
} );

0 comments on commit 1fcb003

Please sign in to comment.