Created
December 20, 2016 14:31
-
-
Save aliaksandr-master/31b0a6af755b89fce4acae863faaf6c6 to your computer and use it in GitHub Desktop.
Connect UI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { showSystemHiddenError } from './log'; | |
import React, { Component, PropTypes } from 'react'; | |
import { connect } from 'react-redux'; | |
import compose from 'recompose/compose'; | |
import reduce from 'lodash/reduce'; | |
import { UI_ACTION_PREFIX, global } from './action'; | |
import isFunction from 'lodash/isFunction'; | |
const MOUNT = global('UI_MOUNT_COMPONENT'); | |
const UNMOUNT = global('UI_UNMOUNT_COMPONENT'); | |
const reducers = {}; | |
const uiReducer = (state = {}, action) => { | |
if (!action.type.startsWith(UI_ACTION_PREFIX) && action.type !== MOUNT && action.type !== UNMOUNT) { | |
return state; | |
} | |
if (!action.meta || !action.meta.uiID) { | |
showSystemHiddenError(`invalid action.meta (action.type="${action.type}"). it must have uiID property`); | |
return state; | |
} | |
if (process.env.NODE_ENV === 'production') { | |
if (!reducers.hasOwnProperty(action.meta.uiID)) { | |
showSystemHiddenError(`invalid uiID="${action.meta.uiID}"`); | |
return state; | |
} | |
} else { | |
if (!reducers.hasOwnProperty(action.meta.uiID) && action.type === UNMOUNT) { | |
return state; | |
} | |
} | |
return { | |
...state, | |
[action.meta.uiID]: reducers[action.meta.uiID](state[action.meta.uiID], action) | |
}; | |
}; | |
export { uiReducer as reducer }; | |
const registerUIReducer = (uiID, reducer) => { | |
if (reducers.hasOwnProperty(uiID)) { | |
throw new Error(`duplicate uiID "${uiID}"`); | |
} | |
reducers[uiID] = reducer; | |
}; | |
const unregisterUIReducer = (uiID) => { | |
delete reducers[uiID]; | |
}; | |
const mountUIComponent = (uiID) => ({ | |
type: MOUNT, | |
meta: { uiID }, | |
payload: {} | |
}); | |
const unmountUIComponent = (uiID) => ({ | |
type: UNMOUNT, | |
meta: { uiID }, | |
payload: {} | |
}); | |
let counter = 0; | |
const wrapDispatchToPtopsFunction = (map) => | |
reduce(map, (map, func, name) => { | |
map[name] = wrapDispatchToPtopsFunction(func); | |
return map; | |
}, {}); | |
const wrapDispatch = (dispatch, uiID) => | |
(action) => { | |
if (isFunction(action)) { | |
return dispatch((dispatch, ...args) => | |
action(wrapDispatch(dispatch, uiID), ...args) // thunk | |
); | |
} | |
if (!action.meta) { | |
action = { | |
...action, | |
meta: {} | |
}; | |
} | |
if (!action.meta.uiID) { | |
action.meta = { | |
...action.meta, | |
uiID | |
}; | |
} | |
return dispatch(action); | |
}; | |
export const connectUI = (reducer, mapStateToProps = null, mapDispatchToProps = null) => { | |
if (!isFunction(reducer)) { | |
throw new Error('reducer must be function'); | |
} | |
return compose( | |
(Target) => { | |
const name = (Target.displayName || Target.name).replace(/^(?:[a-zA-Z0-9]+\()+([^)]+)\)+$/, '$1'); | |
const displayName = `ConnectUI(${Target.displayName || Target.name})`; | |
return class extends Component { | |
static contextTypes = { | |
store: PropTypes.shape({ dispatch: PropTypes.func.isRequired }) | |
}; | |
static propTypes = { | |
uiID: PropTypes.string | |
}; | |
static displayName = displayName; | |
componentWillMount () { | |
this.uiID = `${name}-${this.props.uiID || ++counter}`; | |
registerUIReducer(this.uiID, reducer); | |
this.context.store.dispatch(mountUIComponent(this.uiID)); | |
} | |
componentWillUnmount () { | |
unregisterUIReducer(this.uiID); | |
this.context.store.dispatch(unmountUIComponent(this.uiID)); | |
} | |
render () { | |
return (<Target {...this.props} uiID={this.uiID} />); | |
} | |
}; | |
}, | |
connect( | |
mapStateToProps ? (state, ownProps) => mapStateToProps(state.ui[ownProps.uiID], state, ownProps) : null, | |
!mapDispatchToProps ? null : (dispatch, props, ...args) => { | |
const wrappedDispatch = wrapDispatch(dispatch, props.uiID); | |
if (isFunction(mapDispatchToProps)) { | |
return mapDispatchToProps(wrappedDispatch, props, ...args); | |
} | |
return reduce(mapDispatchToProps, (map, func, name) => { | |
if (!isFunction(func)) { | |
throw new Error(`mapDispatchToProps[${name}] is not a function`); | |
} | |
map[name] = (...args) => wrappedDispatch(func(...args)); | |
return map; | |
}, {}); | |
} | |
) | |
); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment