Skip to content

Commit 3cb8c7e

Browse files
committed
feat(dispatchOnMount): add decorator to dispatch thunkservables on mount
1 parent a078cda commit 3cb8c7e

File tree

5 files changed

+175
-3
lines changed

5 files changed

+175
-3
lines changed

.babelrc

+4-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
"plugins": [
44
"transform-function-bind",
55
"transform-es2015-modules-commonjs",
6-
"transform-object-rest-spread"
6+
"transform-object-rest-spread",
7+
"transform-react-jsx",
8+
"transform-decorators-legacy",
9+
"transform-class-properties"
710
]
811
}

package.json

+10-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"build": "npm run lint && rm -rf lib && babel src -d lib",
99
"build_tests": "rm -rf temp && babel test -d temp",
1010
"clean": "rimraf ./lib; rimraf ./temp;",
11-
"test": "npm run build && npm run build_tests && mocha temp && npm run test_typings",
11+
"test": "npm run build && npm run build_tests && mocha temp",
1212
"test_typings": "tsc test/typings.ts --outFile temp/typings.js --target ES2015 --moduleResolution node",
1313
"prepublish": "npm test"
1414
},
@@ -58,17 +58,26 @@
5858
"devDependencies": {
5959
"babel-cli": "^6.7.5",
6060
"babel-eslint": "^6.0.3",
61+
"babel-plugin-transform-class-properties": "^6.9.0",
62+
"babel-plugin-transform-decorators-legacy": "^1.3.4",
6163
"babel-plugin-transform-es2015-modules-commonjs": "^6.7.4",
6264
"babel-plugin-transform-function-bind": "^6.5.2",
6365
"babel-plugin-transform-object-rest-spread": "^6.6.5",
66+
"babel-plugin-transform-react-jsx": "^6.8.0",
6467
"babel-polyfill": "^6.7.4",
6568
"babel-preset-es2015": "^6.6.0",
6669
"babel-register": "^6.7.2",
6770
"chai": "^3.5.0",
6871
"eslint": "^2.10.2",
6972
"mocha": "^2.4.5",
7073
"promise": "^7.1.1",
74+
"react": "^15.0.2",
75+
"redux": "^3.5.2",
7176
"rimraf": "^2.5.2",
77+
"rxjs": "^5.0.0-beta.7",
7278
"typescript": "^1.8.10"
79+
},
80+
"dependencies": {
81+
"redux-observable": "^0.4.0"
7382
}
7483
}

src/dispatchOnMount.js

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Component, PropTypes } from 'react';
2+
import { Subscription } from 'rxjs/Subscription';
3+
4+
const $$reduxObservableSubscription = '@@reduxObservableSubscription';
5+
6+
export function dispatchOnMount(...toDispatch) {
7+
return (ComposedComponent) =>
8+
class DispatchOnMountComponent extends Component {
9+
static contextTypes = {
10+
store: PropTypes.object.isRequired
11+
}
12+
13+
componentDidMount() {
14+
this[$$reduxObservableSubscription] = new Subscription();
15+
toDispatch.map(a => this.context.store.dispatch(a))
16+
.forEach(sub => sub && this[$$reduxObservableSubscription].add(sub));
17+
}
18+
19+
componentWillUnmount() {
20+
this[$$reduxObservableSubscription].unsubscribe();
21+
}
22+
23+
render() {
24+
return (<ComposedComponent {...this.props}/>);
25+
}
26+
};
27+
}

src/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
// YOLO
1+
export { dispatchOnMount } from './dispatchOnMount';

test/dispatchOnMount-spec.js

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/* global describe, it */
2+
import { expect } from 'chai';
3+
import { reduxObservable } from 'redux-observable';
4+
import { dispatchOnMount } from '../';
5+
import { Component } from 'react';
6+
import { createStore, applyMiddleware } from 'redux';
7+
import * as Rx from 'rxjs';
8+
import 'babel-polyfill';
9+
10+
const { Observable } = Rx;
11+
12+
describe('dispatchOnMount', () => {
13+
it('should exist', () => {
14+
expect(dispatchOnMount).to.be.a('function');
15+
});
16+
17+
it('should wire a thunkservable to dispatch on componentDidMount', () => {
18+
let reducedActions = [];
19+
let reducer = (state, action) => {
20+
reducedActions.push(action);
21+
return state;
22+
};
23+
let store = createStore(reducer, applyMiddleware(reduxObservable()));
24+
25+
@dispatchOnMount(() => Observable.of({ type: 'TEST' }))
26+
class TestComponent extends Component {
27+
}
28+
29+
let comp = new TestComponent();
30+
// fake connection?
31+
comp.context = { store };
32+
comp.componentDidMount();
33+
34+
expect(reducedActions).to.deep.equal([{ type: '@@redux/INIT' }, { type: 'TEST' }]);
35+
});
36+
37+
it('should unsubscribe on componentWillUnmount', () => {
38+
let reducedActions = [];
39+
let reducer = (state, action) => {
40+
reducedActions.push(action);
41+
return state;
42+
};
43+
let store = createStore(reducer, applyMiddleware(reduxObservable()));
44+
45+
let source1Unsubbed = false;
46+
let source1 = new Observable((observer) => {
47+
return () => {
48+
source1Unsubbed = true;
49+
};
50+
});
51+
52+
let source2Unsubbed = false;
53+
let source2 = new Observable((observer) => {
54+
return () => {
55+
source2Unsubbed = true;
56+
};
57+
});
58+
59+
@dispatchOnMount(() => source1, () => source2)
60+
class TestComponent extends Component {
61+
}
62+
63+
let comp = new TestComponent();
64+
// fake connection?
65+
comp.context = { store };
66+
comp.componentDidMount();
67+
68+
expect(source1Unsubbed).to.equal(false);
69+
expect(source2Unsubbed).to.equal(false);
70+
71+
comp.componentWillUnmount();
72+
73+
expect(source1Unsubbed).to.equal(true);
74+
expect(source2Unsubbed).to.equal(true);
75+
});
76+
77+
it('should subscribe to multiple thunkservables', () => {
78+
let reducedActions = [];
79+
let reducer = (state, action) => {
80+
reducedActions.push(action);
81+
return state;
82+
};
83+
let store = createStore(reducer, applyMiddleware(reduxObservable()));
84+
85+
let source1 = Observable.of({ type: 'SOURCE1' });
86+
let source2 = Observable.of({ type: 'SOURCE2' });
87+
88+
@dispatchOnMount(() => source1, () => source2)
89+
class TestComponent extends Component {
90+
}
91+
92+
let comp = new TestComponent();
93+
// fake connection?
94+
comp.context = { store };
95+
comp.componentDidMount();
96+
97+
expect(reducedActions).to.deep.equal([
98+
{ type: '@@redux/INIT' },
99+
{ type: 'SOURCE1' },
100+
{ type: 'SOURCE2' }
101+
]);
102+
});
103+
104+
it('should allow normal actions to dispatch on mount', () => {
105+
let reducedActions = [];
106+
let reducer = (state, action) => {
107+
reducedActions.push(action);
108+
return state;
109+
};
110+
let store = createStore(reducer, applyMiddleware(reduxObservable()));
111+
112+
let source2 = Observable.of({ type: 'SOURCE2' });
113+
114+
@dispatchOnMount({ type: 'PLAIN_ACTION' }, () => source2)
115+
class TestComponent extends Component {
116+
}
117+
118+
let comp = new TestComponent();
119+
// fake connection?
120+
comp.context = { store };
121+
comp.componentDidMount();
122+
123+
expect(reducedActions).to.deep.equal([
124+
{ type: '@@redux/INIT' },
125+
{ type: 'PLAIN_ACTION' },
126+
{ type: 'SOURCE2' }
127+
]);
128+
129+
// since plain actions don't return subscriptions, because they're not functions
130+
// let's just be really sure we don't break the unsub in the componentWillUnmount
131+
expect(() => comp.componentWillUnmount()).not.to.throw();
132+
});
133+
});

0 commit comments

Comments
 (0)