Skip to content

Commit

Permalink
Add ability to subscribe to one store, remove __unstableSubscribeStore (
Browse files Browse the repository at this point in the history
#45513)

* Add ability to subscribe to one store, remove __unstableSubscribeStore

* Check typeof string in getStoreName()
  • Loading branch information
jsnajdr authored Dec 7, 2022
1 parent 22239e6 commit 9b136bb
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 62 deletions.
4 changes: 4 additions & 0 deletions packages/data/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

- Updated dependencies to require React 18 ([45235](https://github.com/WordPress/gutenberg/pull/45235))

### Enhancements

- The `registry.subscribe` function can now subscribe to updates only from one specific store, with a new optional parameter.

## 7.6.0 (2022-11-16)

## 7.5.0 (2022-11-02)
Expand Down
8 changes: 6 additions & 2 deletions packages/data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -658,8 +658,11 @@ _Returns_
### subscribe

Given a listener function, the function will be called any time the state value
of one of the registered stores has changed. This function returns a `unsubscribe`
function used to stop the subscription.
of one of the registered stores has changed. If you specify the optional
`storeNameOrDescriptor` parameter, the listener function will be called only
on updates on that one specific registered store.

This function returns an `unsubscribe` function used to stop the subscription.

_Usage_

Expand All @@ -678,6 +681,7 @@ unsubscribe();
_Parameters_

- _listener_ `Function`: Callback function.
- _storeNameOrDescriptor_ `string|StoreDescriptor?`: Optional store name.

### suspendSelect

Expand Down
4 changes: 2 additions & 2 deletions packages/data/src/components/use-select/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ export default function useSelect( mapSelect, deps ) {
onStoreChange();

const unsubscribers = listeningStores.current.map( ( storeName ) =>
registry.__unstableSubscribeStore( storeName, onChange )
registry.subscribe( onChange, storeName )
);

isMounted.current = true;
Expand Down Expand Up @@ -374,7 +374,7 @@ export function useSuspenseSelect( mapSelect, deps ) {
onStoreChange();

const unsubscribers = listeningStores.current.map( ( storeName ) =>
registry.__unstableSubscribeStore( storeName, onChange )
registry.subscribe( onChange, storeName )
);

isMounted.current = true;
Expand Down
10 changes: 7 additions & 3 deletions packages/data/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,14 @@ export const dispatch = defaultRegistry.dispatch;

/**
* Given a listener function, the function will be called any time the state value
* of one of the registered stores has changed. This function returns a `unsubscribe`
* function used to stop the subscription.
* of one of the registered stores has changed. If you specify the optional
* `storeNameOrDescriptor` parameter, the listener function will be called only
* on updates on that one specific registered store.
*
* @param {Function} listener Callback function.
* This function returns an `unsubscribe` function used to stop the subscription.
*
* @param {Function} listener Callback function.
* @param {string|StoreDescriptor?} storeNameOrDescriptor Optional store name.
*
* @example
* ```js
Expand Down
85 changes: 34 additions & 51 deletions packages/data/src/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ import { createEmitter } from './utils/emitter';
* @property {Function} registerStore registers store.
*/

function isObject( object ) {
return object !== null && typeof object === 'object';
function getStoreName( storeNameOrDescriptor ) {
return typeof storeNameOrDescriptor === 'string'
? storeNameOrDescriptor
: storeNameOrDescriptor.name;
}

/**
* Creates a new store registry, given an optional object of initial store
* configurations.
Expand All @@ -69,14 +70,36 @@ export function createRegistry( storeConfigs = {}, parent = null ) {
}

/**
* Subscribe to changes to any data.
* Subscribe to changes to any data, either in all stores in registry, or
* in one specific store.
*
* @param {Function} listener Listener function.
* @param {Function} listener Listener function.
* @param {string|StoreDescriptor?} storeNameOrDescriptor Optional store name.
*
* @return {Function} Unsubscribe function.
*/
const subscribe = ( listener ) => {
return emitter.subscribe( listener );
const subscribe = ( listener, storeNameOrDescriptor ) => {
// subscribe to all stores
if ( ! storeNameOrDescriptor ) {
return emitter.subscribe( listener );
}

// subscribe to one store
const storeName = getStoreName( storeNameOrDescriptor );
const store = stores[ storeName ];
if ( store ) {
return store.subscribe( listener );
}

// Trying to access a store that hasn't been registered,
// this is a pattern rarely used but seen in some places.
// We fallback to global `subscribe` here for backward-compatibility for now.
// See https://github.com/WordPress/gutenberg/pull/27466 for more info.
if ( ! parent ) {
return emitter.subscribe( listener );
}

return parent.subscribe( listener, storeNameOrDescriptor );
};

/**
Expand All @@ -88,9 +111,7 @@ export function createRegistry( storeConfigs = {}, parent = null ) {
* @return {*} The selector's returned value.
*/
function select( storeNameOrDescriptor ) {
const storeName = isObject( storeNameOrDescriptor )
? storeNameOrDescriptor.name
: storeNameOrDescriptor;
const storeName = getStoreName( storeNameOrDescriptor );
listeningStores.add( storeName );
const store = stores[ storeName ];
if ( store ) {
Expand Down Expand Up @@ -121,9 +142,7 @@ export function createRegistry( storeConfigs = {}, parent = null ) {
* @return {Object} Each key of the object matches the name of a selector.
*/
function resolveSelect( storeNameOrDescriptor ) {
const storeName = isObject( storeNameOrDescriptor )
? storeNameOrDescriptor.name
: storeNameOrDescriptor;
const storeName = getStoreName( storeNameOrDescriptor );
listeningStores.add( storeName );
const store = stores[ storeName ];
if ( store ) {
Expand All @@ -145,9 +164,7 @@ export function createRegistry( storeConfigs = {}, parent = null ) {
* @return {Object} Object containing the store's suspense-wrapped selectors.
*/
function suspendSelect( storeNameOrDescriptor ) {
const storeName = isObject( storeNameOrDescriptor )
? storeNameOrDescriptor.name
: storeNameOrDescriptor;
const storeName = getStoreName( storeNameOrDescriptor );
listeningStores.add( storeName );
const store = stores[ storeName ];
if ( store ) {
Expand All @@ -166,9 +183,7 @@ export function createRegistry( storeConfigs = {}, parent = null ) {
* @return {*} The action's returned value.
*/
function dispatch( storeNameOrDescriptor ) {
const storeName = isObject( storeNameOrDescriptor )
? storeNameOrDescriptor.name
: storeNameOrDescriptor;
const storeName = getStoreName( storeNameOrDescriptor );
const store = stores[ storeName ];
if ( store ) {
return store.getActions();
Expand Down Expand Up @@ -268,37 +283,6 @@ export function createRegistry( storeConfigs = {}, parent = null ) {
return store.store;
}

/**
* Subscribe handler to a store.
*
* @param {string|StoreDescriptor} storeNameOrDescriptor The store name.
* @param {Function} handler The function subscribed to the store.
* @return {Function} A function to unsubscribe the handler.
*/
function __unstableSubscribeStore( storeNameOrDescriptor, handler ) {
const storeName = isObject( storeNameOrDescriptor )
? storeNameOrDescriptor.name
: storeNameOrDescriptor;

const store = stores[ storeName ];
if ( store ) {
return store.subscribe( handler );
}

// Trying to access a store that hasn't been registered,
// this is a pattern rarely used but seen in some places.
// We fallback to regular `subscribe` here for backward-compatibility for now.
// See https://github.com/WordPress/gutenberg/pull/27466 for more info.
if ( ! parent ) {
return subscribe( handler );
}

return parent.__unstableSubscribeStore(
storeNameOrDescriptor,
handler
);
}

function batch( callback ) {
emitter.pause();
Object.values( stores ).forEach( ( store ) => store.emitter.pause() );
Expand All @@ -321,7 +305,6 @@ export function createRegistry( storeConfigs = {}, parent = null ) {
registerGenericStore,
registerStore,
__unstableMarkListeningStores,
__unstableSubscribeStore,
};

//
Expand Down
7 changes: 3 additions & 4 deletions packages/data/src/test/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -719,16 +719,15 @@ describe( 'createRegistry', () => {
const listener2 = jest.fn();
// useSelect subscribes to the stores differently,
// This test ensures batching works in this case as well.
const unsubscribe = registry.__unstableSubscribeStore(
'myAwesomeReducer',
listener2
const unsubscribe = registry.subscribe(
listener2,
'myAwesomeReducer'
);
registry.batch( () => {
store.dispatch( { type: 'dummy' } );
store.dispatch( { type: 'dummy' } );
} );
unsubscribe();

expect( listener2 ).toHaveBeenCalledTimes( 1 );
} );
} );
Expand Down

0 comments on commit 9b136bb

Please sign in to comment.