redux-loop
Version:
Sequence your effects naturally and purely by returning them from your reducers.
83 lines (69 loc) • 2.23 kB
JavaScript
import { liftState } from './loop';
import { executeCmd } from './cmd';
import { loopPromiseCaughtError } from './errors';
const defaultLoopConfig = {
DONT_LOG_ERRORS_ON_HANDLED_FAILURES: false,
ENABLE_THUNK_MIGRATION: false,
};
export function install(config = {}) {
const loopConfig = Object.assign({}, defaultLoopConfig, config);
return (next) => (reducer, initialState, enhancer) => {
const [initialModel, initialCmd] = liftState(initialState);
let cmdsQueue = [];
const liftReducer = (reducer) => (state, action) => {
const result = reducer(state, action);
const [model, cmd] = liftState(result);
cmdsQueue.push({ originalAction: action, cmd });
return model;
};
const store = next(liftReducer(reducer), initialModel, enhancer);
function runCmds(queue) {
const promises = queue.map(runCmd).filter((x) => x);
if (promises.length === 0) {
return Promise.resolve();
} else if (promises.length === 1) {
return promises[0];
} else {
return Promise.all(promises).then(() => {});
}
}
function runCmd({ originalAction, cmd }) {
const cmdPromise = executeCmd(cmd, dispatch, store.getState, loopConfig);
if (!cmdPromise) {
return null;
}
return cmdPromise
.then((actions) => {
if (!actions.length) {
return;
}
return Promise.all(actions.map(dispatch));
})
.catch((error) => {
console.error(loopPromiseCaughtError(originalAction.type, error));
throw error;
});
}
function dispatch(action) {
if (loopConfig.ENABLE_THUNK_MIGRATION && typeof action === 'function') {
return action(dispatch, store.getState);
}
const result = store.dispatch(action);
const cmdsToRun = cmdsQueue;
cmdsQueue = [];
return runCmds(cmdsToRun).then(() => result);
}
function replaceReducer(reducer) {
return store.replaceReducer(liftReducer(reducer));
}
runCmd({
originalAction: { type: '@@ReduxLoop/INIT' },
cmd: initialCmd,
});
return {
...store,
dispatch,
replaceReducer,
};
};
}