UNPKG

react-updating-hoc

Version:
167 lines (123 loc) 4.61 kB
import { compose, lifecycle } from 'recompose'; import isEqual from 'lodash/isEqual'; import groupBy from 'lodash/groupBy'; import get from 'lodash/get'; const assertFunction = (func, message = 'is not a function') => { if (typeof func !== 'function') { throw new TypeError(`updating-hoc assert: ${message}`); } }; const base = Date.now(); export const INIT = `@INIT@${base}@`; export const AFTER_UPDATE = `@AFTER_UPDATE@${base}@`; export const BEFORE_UPDATE = `@BEFORE_UPDATE@${base}@`; export const AFTER_INIT_AND_BEFORE_UPDATE = `@AFTER_INIT_AND_BEFORE_UPDATE@${base}@`; export const BEFORE_UNMOUNT = `@BEFORE_UNMOUNT@${base}@`; const methodsMap = { [INIT]: [ 'componentDidMount' ], [BEFORE_UPDATE]: [ 'UNSAFE_componentWillUpdate' ], [AFTER_UPDATE]: [ 'componentDidUpdate' ], [AFTER_INIT_AND_BEFORE_UPDATE]: [ 'componentDidMount', 'UNSAFE_componentWillUpdate' ], [BEFORE_UNMOUNT] : [ 'componentWillUnmount' ] }; const callFuncs = (funcs, currentProps, otherProps) => { funcs.forEach((func) => { func(currentProps, otherProps); }); }; const methodsToFunction = { componentDidMount: (funcs) => function () { callFuncs(funcs, this.props, null); }, UNSAFE_componentWillUpdate: (funcs) => function (nextProps) { callFuncs(funcs, nextProps, this.props); }, componentDidUpdate: (funcs) => function (prevProps) { callFuncs(funcs, this.props, prevProps); }, componentWillUnmount: (funcs) => function () { callFuncs(funcs, this.props, null); } }; const availableWhenValues = Object.keys(methodsMap); export const callWhen = (condition, executor, when = AFTER_INIT_AND_BEFORE_UPDATE) => { assertFunction(condition, 'condition is not a function'); assertFunction(executor, 'executor is not a function'); if (!availableWhenValues.includes(when)) { throw new ReferenceError(`when "${when}" is undefined. must be [${availableWhenValues.join(', ')}]`); } return { when, condition, executor }; }; const selectSimple = (props, prop) => props[prop]; const selectLodashGet = (props, prop) => get(props, prop); const eqShallow = (prop1, prop2) => prop1 === prop2; export const propsAreUpdated = (propNames, deep = false, useGet = false) => { if (!Array.isArray(propNames)) { throw new TypeError(`propNames must be array of strings`); } let getProp = useGet ? selectLodashGet : selectSimple; const isEq = deep ? isEqual : eqShallow; return (props, prevProps) => prevProps === null ? false : propNames.some((propName) => !isEq(getProp(prevProps, propName), getProp(props, propName))); }; export const callWhenPropsAreUpdated = (propNames, executor, useGet = false) => callWhen( propsAreUpdated(propNames, false, useGet), executor, BEFORE_UPDATE ); export const callWhenPropsAreUpdatedDeeply = (propNames, executor, useGet = false) => callWhen( propsAreUpdated(propNames, true, useGet), executor, BEFORE_UPDATE ); export const callWhenAreInitialized = (condition, executor = null) => { if (executor === null) { executor = condition; condition = () => true; } return callWhen(condition, executor, INIT); }; export const callWhenAreUnmounted = (condition, executor = null) => { if (executor === null) { executor = condition; condition = () => true; } return callWhen(condition, executor, BEFORE_UNMOUNT); }; export default (...triggers) => (Target) => { triggers = triggers .map((trigger) => { if (trigger && typeof trigger === 'object' && trigger.when && trigger.condition && trigger.executor) { return trigger; } throw new Error('invalid type of trigger'); }); const triggerByWhen = groupBy(triggers, 'when'); const triggersByMethods = Object.keys(triggerByWhen).reduce((triggersByMethods, when) => methodsMap[when].reduce((methods, methodName) => { methods[methodName] = methods.hasOwnProperty(methodName) ? [ ...methods[methodName], ...triggerByWhen[when] ] : [ ...triggerByWhen[when] ]; return methods; }, triggersByMethods) , {}); const methodsNames = Object.keys(triggersByMethods); if (!methodsNames.length) { return Target; } const methods = methodsNames.reduce((methods, method) => { const funcs = triggersByMethods[method].map(({ condition, executor }) => (props, otherProps) => { const reason = condition(props, otherProps); if (!reason) { return; } executor(props, reason); }); methods[method] = methodsToFunction[method](funcs); return methods; }, {}); return compose( lifecycle(methods) )(Target); };