Skip to content

Commit ff2173d

Browse files
committed
Tweak the decorator API to my liking
1 parent b57886a commit ff2173d

File tree

11 files changed

+79
-55
lines changed

11 files changed

+79
-55
lines changed

examples/counter/App.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import React, { Component } from 'react';
22
import Counter from './Counter';
3-
import flux from 'redux/flux';
3+
import { provides } from 'redux';
44
import dispatcher from './dispatcher';
55

6-
@flux(dispatcher)
6+
@provides(dispatcher)
77
export default class App extends Component {
88
render() {
99
return (

examples/counter/Counter.js

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
import React from 'react';
2-
import connect from 'redux/connect';
2+
import { performs, observes } from 'redux';
33

4-
@connect({
5-
CounterStore: ({ counter }) => ({
6-
value: counter
7-
})
8-
})
4+
@performs('increment', 'decrement')
5+
@observes('CounterStore')
96
export default class Counter {
107
render() {
11-
const { increment, decrement } = this.props.actions;
8+
const { increment, decrement } = this.props;
129
return (
1310
<p>
14-
Clicked: {this.props.value} times
11+
Clicked: {this.props.counter} times
1512
{' '}
1613
<button onClick={() => increment()}>+</button>
1714
{' '}

examples/counter/dispatcher.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as stores from './stores/index';
22
import * as actions from './actions/index';
3-
import createDispatcher from 'redux/createDispatcher';
3+
import { createDispatcher } from 'redux';
44

55
const dispatcher =
66
module.hot && module.hot.data && module.hot.data.dispatcher ||

examples/todo/App.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import React from 'react';
22
import Header from './Header';
33
import Body from './Body';
4-
import flux from 'redux/flux';
4+
import { provides } from 'redux';
55
import dispatcher from './dispatcher';
66

7-
@flux(dispatcher)
7+
@provides(dispatcher)
88
export default class App {
99
render() {
1010
return (

examples/todo/Body.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import React from 'react';
2-
import connect from 'redux/connect';
2+
import { observes } from 'redux';
33

4-
@connect({
5-
TodoStore: ({ todos }) => ({ todos })
6-
})
4+
@observes('TodoStore')
75
export default class Body {
86
render() {
97
return (

examples/todo/Header.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
2-
import connect from 'redux/connect';
2+
import { performs } from 'redux';
33

4-
@connect()
4+
@performs('addTodo')
55
export default class Header {
66
render() {
77
return (
@@ -12,6 +12,6 @@ export default class Header {
1212
}
1313

1414
addTodo() {
15-
this.props.actions.addTodo('some text');
15+
this.props.addTodo('some text');
1616
}
1717
}

examples/todo/dispatcher.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as stores from './stores/index';
22
import * as actions from './actions/index';
3-
import createDispatcher from 'redux/createDispatcher';
3+
import { createDispatcher } from 'redux';
44

55
const dispatcher =
66
module.hot && module.hot.data && module.hot.data.dispatcher ||

src/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export createDispatcher from './createDispatcher';
2+
export observes from './observes';
3+
export performs from './performs';
4+
export provides from './provides';

src/connect.js renamed to src/observes.js

Lines changed: 17 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,18 @@
11
import React, { Component, PropTypes } from 'react';
2-
import isPlainObject from 'lodash/lang/isPlainObject';
2+
import pick from 'lodash/object/pick';
3+
import identity from 'lodash/utility/identity';
34

45
const contextTypes = {
5-
observeStores: PropTypes.func.isRequired,
6-
getActions: PropTypes.func.isRequired
6+
observeStores: PropTypes.func.isRequired
77
};
88

9-
function extractShorthandArguments(storeMap) {
10-
const storeKeys = Object.keys(storeMap);
9+
export default function connect(...storeKeys) {
10+
let mapState = identity;
1111

12-
function mapState(stateFromStores, props) {
13-
let state = {};
14-
storeKeys.forEach(key => {
15-
const stateFromStore = stateFromStores[key];
16-
const mapStoreState = storeMap[key];
17-
state = { ...state, ...mapStoreState(stateFromStore, props) };
18-
});
19-
return state;
20-
}
21-
22-
return {
23-
storeKeys,
24-
mapState
25-
};
26-
}
27-
28-
export default function connect(storeKeys = [], mapState = () => {}) {
29-
if (isPlainObject(storeKeys)) {
30-
({ storeKeys, mapState } = extractShorthandArguments(storeKeys));
31-
} else if (!Array.isArray(storeKeys)) {
32-
throw new Error('Pass either a string array or an object as the first argument.');
12+
// Last argument may be a custom mapState function
13+
const lastIndex = storeKeys.length - 1;
14+
if (typeof storeKeys[lastIndex] === 'function') {
15+
[mapState] = storeKeys.splice(lastIndex, 1);
3316
}
3417

3518
return function (DecoratedComponent) {
@@ -39,19 +22,17 @@ export default function connect(storeKeys = [], mapState = () => {}) {
3922
'Component';
4023

4124
return class extends Component {
42-
static displayName = `ReduxConnect(${wrappedDisplayName})`;
25+
static displayName = `ReduxObserves(${wrappedDisplayName})`;
4326
static contextTypes = contextTypes;
4427

4528
constructor(props, context) {
4629
super(props, context);
4730
this.handleChange = this.handleChange.bind(this);
48-
4931
this.unobserve = this.context.observeStores(storeKeys, this.handleChange);
50-
this.actions = this.context.getActions();
5132
}
5233

5334
handleChange(stateFromStores) {
54-
this.currentStateFromStores = stateFromStores;
35+
this.currentStateFromStores = pick(stateFromStores, storeKeys);
5536
this.updateState(stateFromStores, this.props);
5637
}
5738

@@ -60,6 +41,11 @@ export default function connect(storeKeys = [], mapState = () => {}) {
6041
}
6142

6243
updateState(stateFromStores, props) {
44+
if (storeKeys.length === 1) {
45+
// Just give it the particular store state for convenience
46+
stateFromStores = stateFromStores[storeKeys[0]];
47+
}
48+
6349
const state = mapState(stateFromStores, props);
6450
if (this.state) {
6551
this.setState(state);
@@ -75,8 +61,7 @@ export default function connect(storeKeys = [], mapState = () => {}) {
7561
render() {
7662
return (
7763
<DecoratedComponent {...this.props}
78-
{...this.state}
79-
actions={this.actions} />
64+
{...this.state} />
8065
);
8166
}
8267
};

src/performs.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import React, { Component, PropTypes } from 'react';
2+
import pick from 'lodash/object/pick';
3+
import identity from 'lodash/utility/identity';
4+
5+
const contextTypes = {
6+
getActions: PropTypes.func.isRequired
7+
};
8+
9+
export default function performs(...actionKeys) {
10+
let mapActions = identity;
11+
12+
// Last argument may be a custom mapState function
13+
const lastIndex = actionKeys.length - 1;
14+
if (typeof actionKeys[lastIndex] === 'function') {
15+
[mapActions] = actionKeys.splice(lastIndex, 1);
16+
}
17+
18+
return function (DecoratedComponent) {
19+
const wrappedDisplayName =
20+
DecoratedComponent.displayName ||
21+
DecoratedComponent.name ||
22+
'Component';
23+
24+
return class extends Component {
25+
static displayName = `ReduxPerforms(${wrappedDisplayName})`;
26+
static contextTypes = contextTypes;
27+
28+
constructor(props, context) {
29+
super(props, context);
30+
this.actions = mapActions(pick(this.context.getActions(), actionKeys));
31+
}
32+
33+
render() {
34+
return (
35+
<DecoratedComponent {...this.actions} />
36+
);
37+
}
38+
};
39+
};
40+
}

src/flux.js renamed to src/provides.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ const childContextTypes = {
55
getActions: PropTypes.func.isRequired
66
};
77

8-
export default function flux(dispatcher) {
8+
export default function provides(dispatcher) {
99
return function (DecoratedComponent) {
1010
const wrappedDisplayName =
1111
DecoratedComponent.displayName ||
1212
DecoratedComponent.name ||
1313
'Component';
1414

1515
return class {
16-
static displayName = `Redux(${wrappedDisplayName})`;
16+
static displayName = `ReduxProvides(${wrappedDisplayName})`;
1717
static childContextTypes = childContextTypes;
1818

1919
getChildContext() {

0 commit comments

Comments
 (0)