easy-peasy
Version:
Vegetarian friendly state for React
143 lines (122 loc) • 3.87 kB
JavaScript
import { get, handleEventDispatchErrors, isPromise } from './lib';
export function createEffectsMiddleware(_r) {
return (store) => (next) => (action) => {
if (_r._i._e.length === 0) {
return next(action);
}
const prevState = store.getState();
const result = next(action);
const nextState = store.getState();
if (prevState !== nextState) {
_r._i._e.forEach((def) => {
const prevLocal = get(def.meta.parent, prevState);
const nextLocal = get(def.meta.parent, nextState);
const prevDependencies = def.dependencyResolvers.map((resolver) =>
resolver(prevLocal, prevState),
);
const nextDependencies = def.dependencyResolvers.map((resolver) =>
resolver(nextLocal, nextState),
);
const hasChanged = prevDependencies.some(
(dependency, idx) => dependency !== nextDependencies[idx],
);
if (hasChanged) {
def.actionCreator(prevDependencies, nextDependencies, action);
}
});
}
return result;
};
}
const logEffectError = (err) => {
// As users can't get a handle on effects we need to report the error
// eslint-disable-next-line no-console
console.error(err);
};
export function createEffectHandler(def, _r, injections, _aC) {
let dispose;
return (change) => {
const helpers = {
dispatch: _r.dispatch,
getState: () => get(def.meta.parent, _r.getState()),
getStoreActions: () => _aC,
getStoreState: _r.getState,
injections,
meta: {
key: def.meta.actionName,
parent: def.meta.parent,
path: def.meta.path,
},
};
if (dispose !== undefined) {
const disposeResult = dispose();
dispose = undefined;
if (isPromise(disposeResult)) {
disposeResult.catch(logEffectError);
}
}
const actions = get(def.meta.parent, _aC);
const effectResult = def.fn(actions, change, helpers);
if (isPromise(effectResult)) {
return effectResult.then((resolved) => {
if (typeof resolved === 'function') {
if (process.env.NODE_ENV !== 'production') {
// Dispose functions are not allowed to be resolved asynchronously.
// Doing so would provide inconsistent behaviour around their execution.
// eslint-disable-next-line no-console
console.warn(
'[easy-peasy] Effect is asynchronously resolving a dispose fn.',
);
}
}
});
}
if (typeof effectResult === 'function') {
dispose = effectResult;
}
return undefined;
};
}
export function createEffectActionsCreator(def, _r, effectHandler) {
const actionCreator = (previousDependencies, nextDependencies, action) => {
const change = {
prev: previousDependencies,
current: nextDependencies,
action,
};
const dispatchStart = handleEventDispatchErrors(def.meta.startType, () =>
_r.dispatch({
type: def.meta.startType,
change,
}),
);
const dispatchSuccess = handleEventDispatchErrors(
def.meta.successType,
() =>
_r.dispatch({
type: def.meta.successType,
change,
}),
);
dispatchStart();
try {
const result = _r.dispatch(() => effectHandler(change));
if (isPromise(result)) {
return result.then((resolved) => {
dispatchSuccess(resolved);
return resolved;
}, logEffectError);
}
dispatchSuccess(result);
return result;
} catch (err) {
logEffectError(err);
}
return undefined;
};
actionCreator.type = def.meta.type;
actionCreator.startType = def.meta.startType;
actionCreator.successType = def.meta.successType;
actionCreator.failType = def.meta.failType;
return actionCreator;
}