Skip to content

Instantly share code, notes, and snippets.

@aliaksandr-master
Created December 20, 2016 14:31
Show Gist options
  • Save aliaksandr-master/31b0a6af755b89fce4acae863faaf6c6 to your computer and use it in GitHub Desktop.
Save aliaksandr-master/31b0a6af755b89fce4acae863faaf6c6 to your computer and use it in GitHub Desktop.
Connect UI
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