Skip to content

Commit d374104

Browse files
committed
add implementation
Description: - implement Connector, Connection and Reductor components - update package.json and .babelrc to make a build
1 parent c8a6edb commit d374104

File tree

7 files changed

+246
-1
lines changed

7 files changed

+246
-1
lines changed

.babelrc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
presets: [
3+
'es2015',
4+
'react',
5+
'stage-1'
6+
]
7+
}

package.json

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,19 @@
44
"description": "Connector component to hoist all Redux-related logic of your app",
55
"main": "./lib/index.js",
66
"scripts": {
7+
"build": "babel src --out-dir lib",
8+
"clear": "rimraf lib",
9+
"rebuild": "npm run clear && npm run build",
710
"test": "echo \"Error: no test specified\" && exit 1"
811
},
912
"repository": {
1013
"type": "git",
1114
"url": "https://github.com/akuzko/react-redux-connector.git"
1215
},
16+
"files": [
17+
"lib",
18+
"src"
19+
],
1320
"keywords": [
1421
"react",
1522
"reactjs",
@@ -20,7 +27,16 @@
2027
"license": "MIT",
2128
"homepage": "https://github.com/akuzko/react-redux-connector",
2229
"devDependencies": {
30+
"babel-cli": "^6.16.0",
31+
"babel-core": "^6.16.0",
32+
"babel-preset-es2015": "^6.16.0",
33+
"babel-preset-react": "^6.16.0",
34+
"babel-preset-stage-1": "^6.16.0",
2335
"react": "^15.3.2",
24-
"redux": "^3.6.0"
36+
"redux": "^3.6.0",
37+
"rimraf": "^2.5.4"
38+
},
39+
"dependencies": {
40+
"lodash": "^4.16.2"
2541
}
2642
}

src/components/Connection.jsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import React, { PropTypes, Component } from 'react';
2+
3+
export default class Connection extends Component {
4+
static contextTypes = {
5+
on: PropTypes.object
6+
};
7+
8+
constructor(props, context) {
9+
super(props, context);
10+
11+
Object.assign(this, this.context.on);
12+
}
13+
}

src/components/Connector.jsx

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import React, { PropTypes, Component } from 'react';
2+
import PureRenderMixin from 'react-addons-pure-render-mixin';
3+
import get from 'lodash/get';
4+
import storeShape from '../utils/storeShape';
5+
6+
const ownProps = Object.getOwnPropertyNames;
7+
const proto = Object.getPrototypeOf;
8+
9+
export default class Connector extends Component {
10+
static reduce(namespace, stateToHandlers) {
11+
this.$namespace = namespace;
12+
const initialState = this.$state;
13+
const actionNames = ownProps(stateToHandlers());
14+
this.__generateDispatchers(actionNames);
15+
16+
return function(state = initialState, action) {
17+
const [actionNamespace, actionType] = action.type.split('/');
18+
19+
if (actionNamespace === namespace) {
20+
const handler = stateToHandlers(state)[actionType];
21+
if (handler) {
22+
return handler(action.data);
23+
}
24+
}
25+
return state;
26+
};
27+
}
28+
29+
static __generateDispatchers(actionNames) {
30+
actionNames.forEach(name => {
31+
const type = this.action(name);
32+
if (typeof this.prototype[`$${name}`] !== 'function') {
33+
this.prototype[`$${name}`] = function(data) {
34+
this.dispatch(type, data);
35+
};
36+
}
37+
});
38+
}
39+
40+
static $namespace = 'global';
41+
42+
static action(name) {
43+
return `${this.$namespace}/${name}`;
44+
}
45+
46+
static get displayName() {
47+
const viewName = this.$connection.displayName || this.$connection.name || 'Component';
48+
49+
return `Connector(${viewName})`;
50+
}
51+
52+
static contextTypes = {
53+
store: storeShape
54+
};
55+
56+
static propTypes = {
57+
store: storeShape
58+
};
59+
60+
static childContextTypes = {
61+
on: PropTypes.object
62+
};
63+
64+
constructor(props, context) {
65+
super(props, context);
66+
this.store = props.store || context.store;
67+
this.state = this.getExposedState();
68+
69+
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
70+
}
71+
72+
getChildContext() {
73+
return { on: this.getEventHandlers() };
74+
}
75+
76+
componentDidMount() {
77+
this.trySubscribe();
78+
}
79+
80+
componentWillUnmount() {
81+
this.tryUnsubscribe();
82+
}
83+
84+
trySubscribe() {
85+
if (!this.unsubscribe) {
86+
this.unsubscribe = this.store.subscribe(this.handleChange.bind(this));
87+
}
88+
}
89+
90+
tryUnsubscribe() {
91+
if (this.unsubscribe) {
92+
this.unsubscribe();
93+
this.unsubscribe = null;
94+
}
95+
}
96+
97+
handleChange() {
98+
if (!this.unsubscribe) return;
99+
100+
const state = this.getExposedState();
101+
this.setState(state);
102+
}
103+
104+
getExposedState() {
105+
const state = this.store.getState();
106+
return this.$expose(get(state, this.constructor.$namespace) || this.constructor.$state, state);
107+
}
108+
109+
dispatch(type, data) {
110+
return this.store.dispatch({ type, data });
111+
}
112+
113+
$expose($state) {
114+
return $state || {};
115+
}
116+
117+
getEventHandlers() {
118+
return ownProps(proto(this)).reduce((handlers, key) => {
119+
if (key[0] === '$' && key[1] != '$' && typeof this[key] === 'function' && key != '$expose') {
120+
handlers[key] = this[key].bind(this);
121+
}
122+
return handlers;
123+
}, {});
124+
}
125+
126+
getConnection() {
127+
return this.constructor.$connection;
128+
}
129+
130+
render() {
131+
return React.createElement(this.getConnection(), { ...this.props, ...this.state });
132+
}
133+
}

src/components/Reductor.jsx

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import React, { PropTypes, Children, Component } from 'react';
2+
3+
import flatten from 'lodash/flatten';
4+
import set from 'lodash/set';
5+
import get from 'lodash/get';
6+
import groupBy from 'lodash/groupBy';
7+
import forEach from 'lodash/forEach';
8+
import storeShape from '../utils/storeShape';
9+
10+
export default class Reductor extends Component {
11+
static propTypes = {
12+
createStore: PropTypes.func.isRequired,
13+
children: PropTypes.element.isRequired
14+
};
15+
16+
static childContextTypes = {
17+
store: storeShape.isRequired
18+
};
19+
20+
getChildContext() {
21+
return { store: this.store };
22+
}
23+
24+
constructor(props, context) {
25+
super(props, context);
26+
const reducer = this.getReducer();
27+
this.store = this.props.createStore(reducer);
28+
}
29+
30+
getConnectors(childrenToProcess = this.props.children) {
31+
const tmp = Children.map(childrenToProcess, (child) => {
32+
const connectors = [];
33+
const { component, children } = child.props;
34+
if (component && component.$namespace) {
35+
connectors.push(component);
36+
}
37+
if (children) {
38+
connectors.push(...this.getConnectors(children));
39+
}
40+
// weird thing: for some reason, when array is returned (i.e. return connectors),
41+
// React abandons all elements in it.
42+
return { connectors };
43+
});
44+
return flatten(tmp.map(container => container.connectors));
45+
}
46+
47+
getReducer() {
48+
const connectors = groupBy(this.getConnectors().filter(c => c.$reducer), '$namespace');
49+
50+
return function(state = {}, action) {
51+
const newState = {};
52+
53+
forEach(connectors, (group, namespace) => {
54+
const namespaceState = group.reduce((st, connector) => {
55+
return connector.$reducer(st, action);
56+
}, get(state, namespace));
57+
set(newState, namespace, namespaceState);
58+
});
59+
return newState;
60+
};
61+
}
62+
63+
render() {
64+
return this.props.children;
65+
}
66+
}

src/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export Connector from './components/Connector';
2+
export Connection from './components/Connection';
3+
export Reductor from './components/Reductor';

src/utils/storeShape.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { PropTypes } from 'react';
2+
3+
export default PropTypes.shape({
4+
subscribe: PropTypes.func.isRequired,
5+
dispatch: PropTypes.func.isRequired,
6+
getState: PropTypes.func.isRequired
7+
});

0 commit comments

Comments
 (0)