Skip to content

Commit

Permalink
StateListenerRegistry
Browse files Browse the repository at this point in the history
"Middleware" redux state changes, not actions.
  • Loading branch information
lyubomir committed May 23, 2018
1 parent 8cd2bd2 commit db21e97
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 1 deletion.
9 changes: 8 additions & 1 deletion react/features/app/components/AbstractApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import Thunk from 'redux-thunk';
import { i18next } from '../../base/i18n';
import { localParticipantLeft } from '../../base/participants';
import { Fragment, RouteRegistry } from '../../base/react';
import { MiddlewareRegistry, ReducerRegistry } from '../../base/redux';
import {
MiddlewareRegistry,
ReducerRegistry,
StateListenerRegistry
} from '../../base/redux';
import { SoundCollection } from '../../base/sounds';
import { PersistenceRegistry } from '../../base/storage';
import { toURLString } from '../../base/util';
Expand Down Expand Up @@ -404,6 +408,9 @@ export class AbstractApp extends Component {
}
}

// StateListenerRegistry
store && StateListenerRegistry.subscribe(store);

return store;
}

Expand Down
159 changes: 159 additions & 0 deletions react/features/base/redux/StateListenerRegistry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// @flow

const logger = require('jitsi-meet-logger').getLogger(__filename);

/**
* The type listener supported for registration with
* {@link StateListenerRegistry} in association with a {@link Selector}.
*
* @param {any} selection - The value derived from the redux store/state by the
* associated {@code Selector}. Immutable!
* @param {Store} store - The redux store. Provided in case the {@code Listener}
* needs to {@code dispatch} or {@code getState}. The latter is advisable only
* if the {@code Listener} is not to respond to changes to that state.
* @param {any} prevSelection - The value previously derived from the redux
* store/state by the associated {@code Selector}. The {@code Listener} is
* invoked only if {@code prevSelection} and {@code selection} are different.
* Immutable!
*/
type Listener = (selection: any, store: Store, prevSelection: any) => void;

/**
* The type selector supported for registration with
* {@link StateListenerRegistry} in association with a {@link Listener}.
*
* @param {Object} state - The redux state from which the {@code Selector} is to
* derive data.
* @param {any} prevSelection - The value previously derived from the redux
* store/state by the {@code Selector}. Provided in case the {@code Selector}
* needs to derive the returned value from the specified {@code state} and
* {@code prevSelection}. Immutable!
* @returns {any} The value derived from the specified {@code state} and/or
* {@code prevSelection}. The associated {@code Listener} will only be invoked
* if the returned value is other than {@code prevSelection}.
*/
type Selector = (state: Object, prevSelection: any) => any;

/**
* A type of a {@link Selector}-{@link Listener} association in which the
* {@code Listener} listens to changes in the values derived from a redux
* store/state by the {@code Selector}.
*/
type SelectorListener = {

/**
* The {@code Listener} which listens to changes in the values selected by
* {@link selector}.
*/
listener: Listener,

/**
* The {@code Selector} which selects values whose changes are listened to
* by {@link listener}.
*/
selector: Selector
};

/**
* A registry listeners which listen to changes in a redux store/state.
*/
class StateListenerRegistry {
/**
* The {@link Listener}s registered with this {@code StateListenerRegistry}
* to be notified when the values derived by associated {@link Selector}s
* from a redux store/state change.
*/
_selectorListeners: Set<SelectorListener> = new Set();

_listener: (Store) => void;

/**
* Invoked by a specific redux store any time an action is dispatched, and
* some part of the state (tree) may potentially have changed.
*
* @param {Object} context - The redux store invoking the listener and the
* private state of this {@code StateListenerRegistry} associated with the
* redux store.
* @returns {void}
*/
_listener({ prevSelections, store }: {
prevSelections: Map<SelectorListener, any>,
store: Store
}) {
for (const selectorListener of this._selectorListeners) {
const prevSelection = prevSelections.get(selectorListener);

try {
const selection
= selectorListener.selector(
store.getState(),
prevSelection);

if (prevSelection !== selection) {
prevSelections.set(selectorListener, selection);
selectorListener.listener(selection, store, prevSelection);
}
} catch (e) {
// Don't let one faulty listener prevent other listeners from
// being notified about their associated changes.
logger.error(e);
}
}
}

/**
* Registers a specific listener to be notified when the value derived by a
* specific {@code selector} from a redux store/state changes.
*
* @param {Function} selector - The pure {@code Function} of the redux
* store/state (and the previous selection of made by {@code selector})
* which selects the value listened to by the specified {@code listener}.
* @param {Function} listener - The listener to register with this
* {@code StateListenerRegistry} so that it gets invoked when the value
* returned by the specified {@code selector} changes.
* @returns {void}
*/
register(selector: Selector, listener: Listener) {
this._selectorListeners.add({
listener,
selector
});
}

/**
* Subscribes to a specific redux store (so that this instance gets notified
* any time an action is dispatched, and some part of the state (tree) of
* the specified redux store may potentially have changed).
*
* @param {Store} store - The redux store to which this
* {@code StateListenerRegistry} is to {@code subscribe}.
* @returns {void}
*/
subscribe(store: Store) {
// XXX If StateListenerRegistry is not utilized by the app to listen to
// state changes, do not bother subscribing to the store at all.
if (this._selectorListeners.size) {
store.subscribe(
this._listener.bind(
this,
{
/**
* The previous selections of the {@code Selector}s
* registered with this {@code StateListenerRegistry}.
*
* @type Map<any>
*/
prevSelections: new Map(),

/**
* The redux store.
*
* @type Store
*/
store
}));
}
}
}

export default new StateListenerRegistry();
1 change: 1 addition & 0 deletions react/features/base/redux/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './functions';
export { default as MiddlewareRegistry } from './MiddlewareRegistry';
export { default as ReducerRegistry } from './ReducerRegistry';
export { default as StateListenerRegistry } from './StateListenerRegistry';

0 comments on commit db21e97

Please sign in to comment.