Example #1
0
  const response = function (Klass) {
    if (!Klass) {
      console.error('[KEA] Logic stores must be wrapped around React Components or stateless functions!', input, output)
      return
    }

    // inject the propTypes to the class
    Klass.propTypes = Object.assign({}, output.propTypes, Klass.propTypes || {})

    // dealing with a Component
    if (!isStateless(Klass)) {
      // inject to the component something that
      // converts this.props.actions to this.actions
      if (!Object.getOwnPropertyDescriptor(Klass.prototype, 'actions')) {
        Object.defineProperty(Klass.prototype, 'actions', {
          get: function actions () {
            return this.props.actions
          }
        })
      }

      // Since Klass == Component, tell the plugins to add themselves to it.
      // if it's a stateless functional component, we'll do it in the end with Redux's Connect class
      plugins.injectToClass.forEach(f => f(input, output, Klass))
    }

    const selectorFactory = (dispatch, factoryOptions) => {
      let lastProps = {}
      let result = null

      if (!isSyncedWithStore()) {
        dispatch({ type: hydrationAction })
      }

      return (nextState, nextOwnProps) => {
        // get the key if it's defined
        const key = input.key ? input.key(nextOwnProps) : null

        // if the key function was defined and returns undefined, something is up. give an error
        if (typeof key === 'undefined') {
          console.error(`"key" can't be undefined in path: ${input.path('undefined').join('.')}`)
        }

        // get the path of this logic store
        const path = input.path(key)
        const joinedPath = path.join('.')

        // get a selector to the root of the path in redux. cache it so it's only created once
        let selector = getCache(joinedPath, 'selector')
        if (!selector) {
          selector = (state) => pathSelector(path, state)
          setCache(joinedPath, { selector })
        }

        let selectors = getCache(joinedPath, 'selectors')

        // add data from the connected selectors into nextProps
        // only do this if we have no own reducers/selectors, otherwise it gets done later
        if (hasConnect && !hasLogic && !selectors) {
          selectors = output.connected.selectors

          // store in the cache. kea-saga wants to access this. TODO: find a better way?
          setCache(joinedPath, { selectors: output.connected.selectors })
        }

        // did we create any reducers/selectors inside the logic store?
        if (hasLogic) {
          // now we must check if the reducer is already in redux, or we need to add it
          // if we need to add it, create "dummy" selectors for the default values until then

          // is the reducer created? if we have "true" in the cache, it's definitely created
          let reduxMounted = shouldMountReducer && !!getCache(joinedPath, 'reduxMounted')

          // if it's not and should eventually be, let's double check. maybe it is now?
          if (shouldMountReducer && !reduxMounted) {
            try {
              reduxMounted = typeof selector(nextState) !== 'undefined'
            } catch (e) {
              reduxMounted = false
            }
          }

          // we don't have the selectors cached with the current reduxMounted state!
          if (getCache(joinedPath, 'reduxMounted') !== reduxMounted) {
            // create a new "output" that also contains { path, key, props }
            // this will be used as /input/ to create the reducers and selectors
            const wrappedOutput = Object.assign({}, output, { path, key, props: nextOwnProps })

            // we can't just recycle this from the singleton, as the reducers can have defaults that depend on props
            let reducerObjects = input.reducers ? convertReducerArrays(input.reducers(wrappedOutput)) : {}

            // run plugins on the created reducer objects
            plugins.mutateReducerObjects.forEach(f => f(input, output, reducerObjects))

            // not in redux, so add the reducer!
            if (shouldMountReducer && !reduxMounted) {
              let reducer = combineReducerObjects(path, reducerObjects)

              // run plugins on the created reducer
              plugins.mutateReducer.forEach(f => f(input, output, reducer))

              addReducer(path, reducer)
            }

            // send a hydration action to redux, to make sure that the store is up to date on the next render
            if (!isSyncedWithStore()) {
              dispatch({ type: hydrationAction })
            }

            // get connected selectors and selectors created from the reducer
            const connectedSelectors = output.connected ? output.connected.selectors : {}
            const createdSelectors = createSelectors(path, Object.keys(reducerObjects))

            // if the reducer is in redux, get real reducer selectors. otherwise add dummies that return defaults
            if (reduxMounted) {
              selectors = Object.assign({}, connectedSelectors, createdSelectors)
            } else {
              // if we don't know for sure that the reducer is in the current store output,
              // then fallback to giving the default value
              selectors = Object.assign({}, connectedSelectors || {})
              Object.keys(reducerObjects).forEach(key => {
                selectors[key] = (state) => {
                  try {
                    return createdSelectors[key](state, nextOwnProps)
                  } catch (error) {
                    return reducerObjects[key].value
                  }
                }
              })
            }

            // create the additional selectors
            const selectorResponse = input.selectors ? input.selectors(Object.assign({}, wrappedOutput, { selectors })) : {}
            const selectorKeys = Object.keys(selectorResponse)

            // proxy via builtSelectors as the order of selectors is not defined and earlier defined ones might depend on later ones
            let builtSelectors = {}
            selectorKeys.forEach(selectorKey => {
              selectors[selectorKey] = (...args) => builtSelectors[selectorKey](...args)
            })

            selectorKeys.forEach(selectorKey => {
              const [getSelectorArgs, selectorFunction, propType] = selectorResponse[selectorKey] // eslint-disable-line
              const args = getSelectorArgs()
              builtSelectors[selectorKey] = createSelector(...args, selectorFunction)
              selectors[selectorKey] = builtSelectors[selectorKey]
            })

            // store in the cache
            setCache(joinedPath, {
              reduxMounted,
              selectors
            })
          }
        }

        // store the props given to the component in nextProps.
        // already fill it with the props passed to the component from above
        let nextProps = Object.assign({}, nextOwnProps)

        // and add to props
        if (selectors) {
          Object.keys(selectors).forEach(selectorKey => {
            nextProps[selectorKey] = selectors[selectorKey](nextState, nextOwnProps)
          })
        }

        // actions need to be created just once, see if they are cached
        let actions = getCache(joinedPath, 'actions')

        // nothing was in the cache, so create them
        if (!actions) {
          actions = nextOwnProps.actions ? Object.assign({}, nextOwnProps.actions) : {}

          // pass conneted actions as they are, just wrap with dispatch
          // ... unless they have a "keyCreator" that was added with "logic.withKey(keyCreator)"
          const connectedActionKeys = Object.keys(output.connected.actions)
          connectedActionKeys.forEach(actionKey => {
            const connectedAction = output.connected.actions[actionKey]
            // we need to inject the latest key before calling the action
            if (connectedAction._keaKeyCreator) {
              actions[actionKey] = (...args) => {
                const actionResponse = connectedAction(...args)
                // an object! add the key and dispatch
                if (typeof actionResponse === 'object') {
                  const connectedKey = typeof connectedAction._keaKeyCreator === 'function' ? connectedAction._keaKeyCreator(nextOwnProps) : connectedAction._keaKeyCreator
                  return dispatch(Object.assign({}, actionResponse, { payload: Object.assign({ key: connectedKey }, actionResponse.payload) }))
                } else { // a function? a string? return it!
                  return dispatch(actionResponse)
                }
              }
            } else {
              actions[actionKey] = (...args) => dispatch(connectedAction(...args))
            }
          })

          // inject key to the payload of created actions, if there is a key
          const createdActionKeys = Object.keys(output.created.actions)
          createdActionKeys.forEach(actionKey => {
            if (key) {
              actions[actionKey] = (...args) => {
                const actionResponse = output.created.actions[actionKey](...args)

                // an object! add the key and dispatch
                if (typeof actionResponse === 'object') {
                  return dispatch(Object.assign({}, actionResponse, { payload: Object.assign({ key: key }, actionResponse.payload) }))
                } else { // a function? a string? return it!
                  return dispatch(actionResponse)
                }
              }
            } else {
              actions[actionKey] = (...args) => dispatch(output.created.actions[actionKey](...args))
            }
          })

          setCache(joinedPath, { actions })
        }

        // if the props did not change, return the old cached output
        if (!result || !shallowEqual(lastProps, nextProps)) {
          lastProps = nextProps
          result = Object.assign({}, nextProps, { actions, dispatch })
        }

        return result
      }
    }

    // connect this function to Redux
    const KonnektedKlass = connectAdvanced(selectorFactory, { methodName: 'kea' })(Klass)

    // If we were wrapping a stateless functional React component, add the plugin code to the connected component.
    if (isStateless(Klass)) {
      plugins.injectToConnectedClass.forEach(f => f(input, output, KonnektedKlass))
    }

    return KonnektedKlass
  }
Example #2
0
            </div>
        )
    }
}

function selectorFactory(dispatch) {
    let state = {};
    let ownProps = {};
    let result = {};
    const actions = bindActionCreators(Actions, dispatch);
    return (nextState, nextOwnProps) => {
        const nextResult = {
            currentLayout: nextState.layout,
            widgets: nextState.storedWidgets,
            session: nextState.session,
            layouts: nextState.storedLayouts,
            reduxActions: actions,
            ...nextOwnProps
        };
        state = nextState;
        ownProps = nextOwnProps;
        if (!shallowEqual(result,nextResult)){
            result = nextResult;
        }
        return result
    }
}

var ConnectedLayout = connectAdvanced(selectorFactory)(Layout);
export default connectAdvanced(selectorFactory)(DisplayPane);