Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new actions for invalidating resolution caches #14225

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 );
} );
} );