Skip to content

Commit

Permalink
Support private selectors and actions for stores registered via regis…
Browse files Browse the repository at this point in the history
…try.registerStore() and for sub registries. (#47421)

`registerPrivateActions` and `registerPrivateSelectors` only supported stores created using `createReduxStore`. On stores registered using the deprecated `registerStore` helper the private actions are not returned upon `unlock()`-ing the `useDispatch()` call (as reported by @talldan in #47375 (comment)).

This PR adds the missing support for the `registerStore()` stores as well as for the children stores in sub registries.
  • Loading branch information
adamziel authored Jan 26, 2023
1 parent 23ee4bd commit 3bc98f0
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 33 deletions.
25 changes: 17 additions & 8 deletions packages/data/src/redux-store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,16 @@ function createResolversCache() {
export default function createReduxStore( key, options ) {
const privateActions = {};
const privateSelectors = {};
const privateRegistrationFunctions = {
privateActions,
registerPrivateActions: ( actions ) => {
Object.assign( privateActions, actions );
},
privateSelectors,
registerPrivateSelectors: ( selectors ) => {
Object.assign( privateSelectors, selectors );
},
};
const storeDescriptor = {
name: key,
instantiate: ( registry ) => {
Expand Down Expand Up @@ -141,6 +151,9 @@ export default function createReduxStore( key, options ) {
registry,
thunkArgs
);
// Expose the private registration functions on the store
// so they can be copied to a sub registry in registry.js.
lock( store, privateRegistrationFunctions );
const resolversCache = createResolversCache();

let resolvers;
Expand Down Expand Up @@ -260,14 +273,10 @@ export default function createReduxStore( key, options ) {
},
};

lock( storeDescriptor, {
registerPrivateActions: ( actions ) => {
Object.assign( privateActions, actions );
},
registerPrivateSelectors: ( selectors ) => {
Object.assign( privateSelectors, selectors );
},
} );
// Expose the private registration functions on the store
// descriptor. That's a natural choice since that's where the
// public actions and selectors are stored .
lock( storeDescriptor, privateRegistrationFunctions );

return storeDescriptor;
}
Expand Down
38 changes: 37 additions & 1 deletion packages/data/src/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import deprecated from '@wordpress/deprecated';
import createReduxStore from './redux-store';
import coreDataStore from './store';
import { createEmitter } from './utils/emitter';
import { lock, unlock } from './experiments';

/** @typedef {import('./types').StoreDescriptor} StoreDescriptor */

Expand Down Expand Up @@ -245,6 +246,22 @@ export function createRegistry( storeConfigs = {}, parent = null ) {
};
stores[ name ] = store;
store.subscribe( globalListener );

// Copy private actions and selectors from the parent store.
if ( parent ) {
try {
unlock( store.store ).registerPrivateActions(
unlock( parent ).privateActionsOf( name )
);
unlock( store.store ).registerPrivateSelectors(
unlock( parent ).privateSelectorsOf( name )
);
} catch ( e ) {
// unlock() throws if store.store was not locked.
// The error indicates there's nothing to do here so let's
// ignore it.
}
}
}

/**
Expand Down Expand Up @@ -334,5 +351,24 @@ export function createRegistry( storeConfigs = {}, parent = null ) {
parent.subscribe( globalListener );
}

return withPlugins( registry );
const registryWithPlugins = withPlugins( registry );
lock( registryWithPlugins, {
privateActionsOf: ( name ) => {
try {
return unlock( stores[ name ].store ).privateActions;
} catch ( e ) {
// unlock() throws an error the store was not locked – this means
// there no private actions are available
return {};
}
},
privateSelectorsOf: ( name ) => {
try {
return unlock( stores[ name ].store ).privateSelectors;
} catch ( e ) {
return {};
}
},
} );
return registryWithPlugins;
}
104 changes: 80 additions & 24 deletions packages/data/src/test/privateAPIs.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,32 +38,34 @@ describe( 'Private data APIs', () => {
function setPublicPrice( price ) {
return { type: 'SET_PUBLIC_PRICE', price };
}
function createStore() {
const groceryStore = createReduxStore( 'grocer', {
selectors: {
getPublicPrice,
getState: ( state ) => state,
},
actions: { setPublicPrice },
reducer: ( state, action ) => {
if ( action?.type === 'SET_PRIVATE_PRICE' ) {
return {
...state,
secretDiscount: action?.price,
};
} else if ( action?.type === 'SET_PUBLIC_PRICE' ) {
return {
...state,
price: action?.price,
};
}
const storeName = 'grocer';
const storeDescriptor = {
selectors: {
getPublicPrice,
getState: ( state ) => state,
},
actions: { setPublicPrice },
reducer: ( state, action ) => {
if ( action?.type === 'SET_PRIVATE_PRICE' ) {
return {
price: 1000,
secretDiscount: 800,
...( state || {} ),
...state,
secretDiscount: action?.price,
};
},
} );
} else if ( action?.type === 'SET_PUBLIC_PRICE' ) {
return {
...state,
price: action?.price,
};
}
return {
price: 1000,
secretDiscount: 800,
...( state || {} ),
};
},
};
function createStore() {
const groceryStore = createReduxStore( storeName, storeDescriptor );
registry.register( groceryStore );
return groceryStore;
}
Expand Down Expand Up @@ -126,6 +128,28 @@ describe( 'Private data APIs', () => {
const unlockedSelectors = unlock( registry.select( groceryStore ) );
expect( unlockedSelectors.getPublicPrice() ).toEqual( 1000 );
} );

it( 'should support sub registries', () => {
const groceryStore = registry.registerStore(
storeName,
storeDescriptor
);
unlock( groceryStore ).registerPrivateSelectors( {
getSecretDiscount,
} );
const subRegistry = createRegistry( {}, registry );
subRegistry.registerStore( storeName, storeDescriptor );

const parentPrivateSelectors = unlock(
registry.select( storeName )
);
expect( parentPrivateSelectors.getSecretDiscount() ).toEqual( 800 );

const subPrivateSelectors = unlock(
subRegistry.select( storeName )
);
expect( subPrivateSelectors.getSecretDiscount() ).toEqual( 800 );
} );
} );

describe( 'private actions', () => {
Expand Down Expand Up @@ -224,5 +248,37 @@ describe( 'Private data APIs', () => {
unlock( registry.select( groceryStore ) ).getSecretDiscount()
).toEqual( 100 );
} );

it( 'should support sub registries', () => {
const groceryStore = createStore();
unlock( groceryStore ).registerPrivateSelectors( {
getSecretDiscount,
} );
unlock( groceryStore ).registerPrivateActions( {
setSecretDiscount,
} );
const subRegistry = createRegistry( {}, registry );
subRegistry.registerStore( storeName, storeDescriptor );

const parentPrivateActions = unlock(
registry.dispatch( storeName )
);
const parentPrivateSelectors = unlock(
registry.select( storeName )
);

const subPrivateActions = unlock(
subRegistry.dispatch( storeName )
);
const subPrivateSelectors = unlock(
subRegistry.select( storeName )
);

parentPrivateActions.setSecretDiscount( 400 );
subPrivateActions.setSecretDiscount( 478 );

expect( parentPrivateSelectors.getSecretDiscount() ).toEqual( 400 );
expect( subPrivateSelectors.getSecretDiscount() ).toEqual( 478 );
} );
} );
} );

0 comments on commit 3bc98f0

Please sign in to comment.