diff options
Diffstat (limited to 'front/odiparpack/app/utils')
| -rw-r--r-- | front/odiparpack/app/utils/checkStore.js | 21 | ||||
| -rw-r--r-- | front/odiparpack/app/utils/constants.js | 3 | ||||
| -rw-r--r-- | front/odiparpack/app/utils/history.js | 3 | ||||
| -rw-r--r-- | front/odiparpack/app/utils/injectReducer.js | 45 | ||||
| -rw-r--r-- | front/odiparpack/app/utils/injectSaga.js | 59 | ||||
| -rw-r--r-- | front/odiparpack/app/utils/reducerInjectors.js | 33 | ||||
| -rw-r--r-- | front/odiparpack/app/utils/sagaInjectors.js | 92 | ||||
| -rw-r--r-- | front/odiparpack/app/utils/sagas.js | 15 | ||||
| -rw-r--r-- | front/odiparpack/app/utils/tests/.DS_Store | bin | 0 -> 6148 bytes | 
9 files changed, 271 insertions, 0 deletions
| diff --git a/front/odiparpack/app/utils/checkStore.js b/front/odiparpack/app/utils/checkStore.js new file mode 100644 index 0000000..610ea0f --- /dev/null +++ b/front/odiparpack/app/utils/checkStore.js @@ -0,0 +1,21 @@ +import { conformsTo, isFunction, isObject } from 'lodash'; +import invariant from 'invariant'; + +/** + * Validate the shape of redux store + */ +export default function checkStore(store) { +  const shape = { +    dispatch: isFunction, +    subscribe: isFunction, +    getState: isFunction, +    replaceReducer: isFunction, +    runSaga: isFunction, +    injectedReducers: isObject, +    injectedSagas: isObject, +  }; +  invariant( +    conformsTo(store, shape), +    '(app/utils...) injectors: Expected a valid redux store', +  ); +} diff --git a/front/odiparpack/app/utils/constants.js b/front/odiparpack/app/utils/constants.js new file mode 100644 index 0000000..97ece0f --- /dev/null +++ b/front/odiparpack/app/utils/constants.js @@ -0,0 +1,3 @@ +export const RESTART_ON_REMOUNT = '@@saga-injector/restart-on-remount'; +export const DAEMON = '@@saga-injector/daemon'; +export const ONCE_TILL_UNMOUNT = '@@saga-injector/once-till-unmount'; diff --git a/front/odiparpack/app/utils/history.js b/front/odiparpack/app/utils/history.js new file mode 100644 index 0000000..ee3abb7 --- /dev/null +++ b/front/odiparpack/app/utils/history.js @@ -0,0 +1,3 @@ +import { createBrowserHistory } from 'history'; +const history = createBrowserHistory(); +export default history; diff --git a/front/odiparpack/app/utils/injectReducer.js b/front/odiparpack/app/utils/injectReducer.js new file mode 100644 index 0000000..13833c2 --- /dev/null +++ b/front/odiparpack/app/utils/injectReducer.js @@ -0,0 +1,45 @@ +import React from 'react'; +import hoistNonReactStatics from 'hoist-non-react-statics'; +import { ReactReduxContext } from 'react-redux'; + +import getInjectors from './reducerInjectors'; + +/** + * Dynamically injects a reducer + * + * @param {string} key A key of the reducer + * @param {function} reducer A reducer that will be injected + * + */ +export default ({ key, reducer }) => WrappedComponent => { +  class ReducerInjector extends React.Component { +    static WrappedComponent = WrappedComponent; + +    static contextType = ReactReduxContext; + +    static displayName = `withReducer(${WrappedComponent.displayName +      || WrappedComponent.name +      || 'Component'})`; + +    constructor(props, context) { +      super(props, context); + +      getInjectors(context.store).injectReducer(key, reducer); +    } + +    render() { +      return <WrappedComponent {...this.props} />; +    } +  } + +  return hoistNonReactStatics(ReducerInjector, WrappedComponent); +}; + +const useInjectReducer = ({ key, reducer }) => { +  const context = React.useContext(ReactReduxContext); +  React.useEffect(() => { +    getInjectors(context.store).injectReducer(key, reducer); +  }, []); +}; + +export { useInjectReducer }; diff --git a/front/odiparpack/app/utils/injectSaga.js b/front/odiparpack/app/utils/injectSaga.js new file mode 100644 index 0000000..3f8752b --- /dev/null +++ b/front/odiparpack/app/utils/injectSaga.js @@ -0,0 +1,59 @@ +import React from 'react'; +import hoistNonReactStatics from 'hoist-non-react-statics'; +import { ReactReduxContext } from 'react-redux'; + +import getInjectors from './sagaInjectors'; + +/** + * Dynamically injects a saga, passes component's props as saga arguments + * + * @param {string} key A key of the saga + * @param {function} saga A root saga that will be injected + * @param {string} [mode] By default (constants.DAEMON) the saga will be started + * on component mount and never canceled or started again. Another two options: + *   - constants.RESTART_ON_REMOUNT — the saga will be started on component mount and + *   cancelled with `task.cancel()` on component unmount for improved performance, + *   - constants.ONCE_TILL_UNMOUNT — behaves like 'RESTART_ON_REMOUNT' but never runs it again. + * + */ +export default ({ key, saga, mode }) => WrappedComponent => { +  class InjectSaga extends React.Component { +    static WrappedComponent = WrappedComponent; + +    static contextType = ReactReduxContext; + +    static displayName = `withSaga(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`; + +    constructor(props, context) { +      super(props, context); + +      this.injectors = getInjectors(context.store); + +      this.injectors.injectSaga(key, { saga, mode }, this.props); +    } + +    componentWillUnmount() { +      this.injectors.ejectSaga(key); +    } + +    render() { +      return <WrappedComponent {...this.props} />; +    } +  } + +  return hoistNonReactStatics(InjectSaga, WrappedComponent); +}; + +const useInjectSaga = ({ key, saga, mode }) => { +  const context = React.useContext(ReactReduxContext); +  React.useEffect(() => { +    const injectors = getInjectors(context.store); +    injectors.injectSaga(key, { saga, mode }); + +    return () => { +      injectors.ejectSaga(key); +    }; +  }, []); +}; + +export { useInjectSaga }; diff --git a/front/odiparpack/app/utils/reducerInjectors.js b/front/odiparpack/app/utils/reducerInjectors.js new file mode 100644 index 0000000..d664680 --- /dev/null +++ b/front/odiparpack/app/utils/reducerInjectors.js @@ -0,0 +1,33 @@ +import invariant from 'invariant'; +import { isEmpty, isFunction, isString } from 'lodash'; + +import checkStore from './checkStore'; +import createReducer from '../redux/reducers'; + +export function injectReducerFactory(store, isValid) { +  return function injectReducer(key, reducer) { +    if (!isValid) checkStore(store); + +    invariant( +      isString(key) && !isEmpty(key) && isFunction(reducer), +      '(app/utils...) injectReducer: Expected `reducer` to be a reducer function', +    ); + +    // Check `store.injectedReducers[key] === reducer` for hot reloading when a key is the same but a reducer is different +    if ( +      Reflect.has(store.injectedReducers, key) +      && store.injectedReducers[key] === reducer +    ) return; + +    store.injectedReducers[key] = reducer; // eslint-disable-line no-param-reassign +    store.replaceReducer(createReducer(store.injectedReducers)); +  }; +} + +export default function getInjectors(store) { +  checkStore(store); + +  return { +    injectReducer: injectReducerFactory(store, true), +  }; +} diff --git a/front/odiparpack/app/utils/sagaInjectors.js b/front/odiparpack/app/utils/sagaInjectors.js new file mode 100644 index 0000000..edb4626 --- /dev/null +++ b/front/odiparpack/app/utils/sagaInjectors.js @@ -0,0 +1,92 @@ +import invariant from 'invariant'; +import { +  isEmpty, isFunction, isString, conformsTo +} from 'lodash'; + +import checkStore from './checkStore'; +import { DAEMON, ONCE_TILL_UNMOUNT, RESTART_ON_REMOUNT } from './constants'; + +const allowedModes = [RESTART_ON_REMOUNT, DAEMON, ONCE_TILL_UNMOUNT]; + +const checkKey = key => invariant( +  isString(key) && !isEmpty(key), +  '(app/utils...) injectSaga: Expected `key` to be a non empty string', +); + +const checkDescriptor = descriptor => { +  const shape = { +    saga: isFunction, +    mode: mode => isString(mode) && allowedModes.includes(mode), +  }; +  invariant( +    conformsTo(descriptor, shape), +    '(app/utils...) injectSaga: Expected a valid saga descriptor', +  ); +}; + +export function injectSagaFactory(store, isValid) { +  return function injectSaga(key, descriptor = {}, args) { +    if (!isValid) checkStore(store); + +    const newDescriptor = { +      ...descriptor, +      mode: descriptor.mode || DAEMON, +    }; +    const { saga, mode } = newDescriptor; + +    checkKey(key); +    checkDescriptor(newDescriptor); + +    let hasSaga = Reflect.has(store.injectedSagas, key); + +    if (process.env.NODE_ENV !== 'production') { +      const oldDescriptor = store.injectedSagas[key]; +      // enable hot reloading of daemon and once-till-unmount sagas +      if (hasSaga && oldDescriptor.saga !== saga) { +        oldDescriptor.task.cancel(); +        hasSaga = false; +      } +    } + +    if ( +      !hasSaga +      || (hasSaga && mode !== DAEMON && mode !== ONCE_TILL_UNMOUNT) +    ) { +      /* eslint-disable no-param-reassign */ +      store.injectedSagas[key] = { +        ...newDescriptor, +        task: store.runSaga(saga, args), +      }; +      /* eslint-enable no-param-reassign */ +    } +  }; +} + +export function ejectSagaFactory(store, isValid) { +  return function ejectSaga(key) { +    if (!isValid) checkStore(store); + +    checkKey(key); + +    if (Reflect.has(store.injectedSagas, key)) { +      const descriptor = store.injectedSagas[key]; +      if (descriptor.mode && descriptor.mode !== DAEMON) { +        descriptor.task.cancel(); +        // Clean up in production; in development we need `descriptor.saga` for hot reloading +        if (process.env.NODE_ENV === 'production') { +          // Need some value to be able to detect `ONCE_TILL_UNMOUNT` sagas in `injectSaga` +          store.injectedSagas[key] = 'done'; // eslint-disable-line no-param-reassign +        } +      } +    } +  }; +} + +export default function getInjectors(store) { +  checkStore(store); + +  return { +    injectSaga: injectSagaFactory(store, true), +    ejectSaga: ejectSagaFactory(store, true), +  }; +} diff --git a/front/odiparpack/app/utils/sagas.js b/front/odiparpack/app/utils/sagas.js new file mode 100644 index 0000000..f5d3e78 --- /dev/null +++ b/front/odiparpack/app/utils/sagas.js @@ -0,0 +1,15 @@ +import { all } from 'redux-saga/effects'; +import taskSagas from 'enl-containers/SampleFullstackApps//Todo/reducers/todoSagas'; +import contactSagas from 'enl-containers/SampleFullstackApps/Contact/reducers/contactSagas'; +import emailSagas from 'enl-containers/SampleFullstackApps/Email/reducers/emailSagas'; +import authSagas from 'enl-redux/modules/authSagas'; + + +export default function* sagas() { +  yield all([ +    ...authSagas, +    ...contactSagas, +    ...taskSagas, +    ...emailSagas +  ]); +} diff --git a/front/odiparpack/app/utils/tests/.DS_Store b/front/odiparpack/app/utils/tests/.DS_StoreBinary files differ new file mode 100644 index 0000000..5008ddf --- /dev/null +++ b/front/odiparpack/app/utils/tests/.DS_Store | 
