From 429b515eabc25ad0cec73c8c51b36dc5cf2ce07d Mon Sep 17 00:00:00 2001 From: Darren Ethier Date: Mon, 4 Mar 2019 15:48:25 -0500 Subject: [PATCH 1/3] add new actions for invalidating resolution caches --- packages/data/CHANGELOG.md | 2 + packages/data/src/store/actions.js | 36 ++++++++++++++++++ packages/data/src/store/reducer.js | 35 ++++++++++++++++-- packages/data/src/store/test/reducer.js | 49 +++++++++++++++++++++++++ 4 files changed, 119 insertions(+), 3 deletions(-) diff --git a/packages/data/CHANGELOG.md b/packages/data/CHANGELOG.md index 15a723006d351..34cfc2e0096e1 100644 --- a/packages/data/CHANGELOG.md +++ b/packages/data/CHANGELOG.md @@ -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 diff --git a/packages/data/src/store/actions.js b/packages/data/src/store/actions.js index e4d887ee3e58a..b7bd9aa805738 100644 --- a/packages/data/src/store/actions.js +++ b/packages/data/src/store/actions.js @@ -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, + }; +} diff --git a/packages/data/src/store/reducer.js b/packages/data/src/store/reducer.js index 69cd865766234..680d31e111307 100644 --- a/packages/data/src/store/reducer.js +++ b/packages/data/src/store/reducer.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { flowRight } from 'lodash'; +import { flowRight, omit, has } from 'lodash'; import EquivalentKeyMap from 'equivalent-key-map'; /** @@ -37,8 +37,37 @@ const isResolved = flowRight( [ return nextState; } } - return state; } ); -export default isResolved; +/** + * Reducer function returning next state for selector resolution, object form: + * + * reducerKey -> selectorName -> EquivalentKeyMap + * + * @param {Object} state Current state. + * @param {Object} action Dispatched action. + * + * @return {Object} Next state. + */ +const topLevelIsResolved = ( 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; + } + return isResolved( state, action ); +}; + +export default topLevelIsResolved; diff --git a/packages/data/src/store/test/reducer.js b/packages/data/src/store/test/reducer.js index 186703c7738a2..eab156e89a71e 100644 --- a/packages/data/src/store/test/reducer.js +++ b/packages/data/src/store/test/reducer.js @@ -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 ); + } ); } ); From 1a588ee23bf58cbec036b82eda65bece4cc61ec1 Mon Sep 17 00:00:00 2001 From: Darren Ethier Date: Wed, 6 Mar 2019 08:42:08 -0500 Subject: [PATCH 2/3] use path array for omit --- packages/data/src/store/reducer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/data/src/store/reducer.js b/packages/data/src/store/reducer.js index 680d31e111307..ea1825104696f 100644 --- a/packages/data/src/store/reducer.js +++ b/packages/data/src/store/reducer.js @@ -54,7 +54,7 @@ const topLevelIsResolved = ( state = {}, action ) => { switch ( action.type ) { case 'INVALIDATE_RESOLUTION_FOR_STORE': return has( state, action.reducerKey ) ? - omit( state, action.reducerKey ) : + omit( state, [ action.reducerKey ] ) : state; case 'INVALIDATE_RESOLUTION_FOR_STORE_SELECTOR': return has( state, [ action.reducerKey, action.selectorName ] ) ? @@ -62,7 +62,7 @@ const topLevelIsResolved = ( state = {}, action ) => { ...state, [ action.reducerKey ]: omit( state[ action.reducerKey ], - action.selectorName + [ action.selectorName ] ), } : state; From b089514e32abcf346f85f9f60752358fbe6c7761 Mon Sep 17 00:00:00 2001 From: Darren Ethier Date: Wed, 6 Mar 2019 10:31:05 -0500 Subject: [PATCH 3/3] clarify logic --- packages/data/src/store/reducer.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/data/src/store/reducer.js b/packages/data/src/store/reducer.js index ea1825104696f..cd043f753f833 100644 --- a/packages/data/src/store/reducer.js +++ b/packages/data/src/store/reducer.js @@ -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 * @@ -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 ) => { @@ -50,7 +51,7 @@ const isResolved = flowRight( [ * * @return {Object} Next state. */ -const topLevelIsResolved = ( state = {}, action ) => { +const isResolved = ( state = {}, action ) => { switch ( action.type ) { case 'INVALIDATE_RESOLUTION_FOR_STORE': return has( state, action.reducerKey ) ? @@ -66,8 +67,12 @@ const topLevelIsResolved = ( state = {}, action ) => { ), } : state; + case 'START_RESOLUTION': + case 'FINISH_RESOLUTION': + case 'INVALIDATE_RESOLUTION': + return subKeysIsResolved( state, action ); } - return isResolved( state, action ); + return state; }; -export default topLevelIsResolved; +export default isResolved;