Commit f500b5d5 authored by Robert Knight's avatar Robert Knight

Allow multiple state modules to handle the same action

In some cases multiple state modules may need to handle the same action.
For example, when annotations are loaded or unloaded we may want to
update both the set of loaded annotations and also the current
selection.

This commit changes the `createReducer` helper and the code that uses it
in `reducers/index.js` so that multiple state modules can define update
functions that handle the same action.

Note that all update functions get the same, _previous_ state as input
and there is no guarantee on precedence if they return conflicting
updates.

See also: https://redux.js.org/faq/actions#is-there-always-a-one-to-one-mapping-between-reducers-and-actions
parent 7838cfde
......@@ -37,14 +37,14 @@ function init(settings) {
);
}
var update = util.createReducer(Object.assign(
var update = util.createReducer(...[
annotations.update,
frames.update,
links.update,
selection.update,
session.update,
viewer.update
));
viewer.update,
]);
module.exports = {
init: init,
......
......@@ -68,6 +68,26 @@ describe('reducer utils', function () {
annotations: [{id: 1}],
});
});
it('applies update functions from each input object', () => {
var firstCounterActions = {
INCREMENT_COUNTER(state) {
return { firstCounter: state.firstCounter + 1 };
},
};
var secondCounterActions = {
INCREMENT_COUNTER(state) {
return { secondCounter: state.secondCounter + 1 };
},
};
var reducer = util.createReducer(firstCounterActions, secondCounterActions);
var state = { firstCounter: 5, secondCounter: 10 };
var action = { type: 'INCREMENT_COUNTER' };
var newState = reducer(state, action);
assert.deepEqual(newState, { firstCounter: 6, secondCounter: 11 });
});
});
describe('#bindSelectors', function () {
......
......@@ -11,16 +11,28 @@ function actionTypes(updateFns) {
}
/**
* Given an object which maps action names to update functions, this returns
* a reducer function that can be passed to the redux `createStore` function.
* Given objects which map action names to update functions, this returns a
* reducer function that can be passed to the redux `createStore` function.
*
* @param {Object[]} actionToUpdateFn - Objects mapping action names to update
* functions.
*/
function createReducer(updateFns) {
return function (state, action) {
var fn = updateFns[action.type];
if (!fn) {
function createReducer(...actionToUpdateFn) {
// Combine the (action name => update function) maps together into a single
// (action name => update functions) map.
var actionToUpdateFns = {};
actionToUpdateFn.forEach(map => {
Object.keys(map).forEach(k => {
actionToUpdateFns[k] = (actionToUpdateFns[k] || []).concat(map[k]);
});
});
return (state, action) => {
var fns = actionToUpdateFns[action.type];
if (!fns) {
return state;
}
return Object.assign({}, state, fn(state, action));
return Object.assign({}, state, ...fns.map(f => f(state, action)));
};
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment