Skip to content

Commit 3a78a8f

Browse files
committed
feat(dispatchOn): replace dispatchOnMount with dispatchOn
dispatchOn is a decorator that allows the user to define many lifecycle hooks that dispatch thunkservables as part of a single higher-order component. Allowed hooks are `willMount` (componentWillMount), `mount` (componentDidMount), `update` (componentDidUpdate), and `willReceiveProps` (componentWillReceiveProps) BREAKING CHANGE: `dispatchOnMount` is removed in favor of `dispatchOn`
1 parent 326c894 commit 3a78a8f

File tree

4 files changed

+169
-38
lines changed

4 files changed

+169
-38
lines changed

Diff for: src/dispatchOn.js

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import React, { Component, PropTypes } from 'react';
2+
import { Subscription } from 'rxjs/Subscription';
3+
4+
const $$reduxObservableSubscription = '@@reduxObservableSubscription';
5+
6+
const dispatchFactories = (subscription, store, factories, args) => {
7+
factories.map(factory => store.dispatch(factory(...args)))
8+
.forEach(sub => sub && subscription.add(sub));
9+
};
10+
11+
export function dispatchOn({
12+
willMount = null,
13+
mount = null,
14+
update = null,
15+
willRecieveProps = null
16+
}) {
17+
return (ComposedComponent) =>
18+
class DispatchOnMountComponent extends Component {
19+
constructor(props) {
20+
super(props);
21+
}
22+
23+
static contextTypes = {
24+
store: PropTypes.object.isRequired
25+
}
26+
27+
getSubscription() {
28+
const subscription = this[$$reduxObservableSubscription];
29+
if (!subscription || subscription.isUnsubscribed) {
30+
this[$$reduxObservableSubscription] = new Subscription();
31+
}
32+
return this[$$reduxObservableSubscription];
33+
}
34+
35+
componentWillMount() {
36+
if (willMount) {
37+
dispatchFactories(this.getSubscription(), this.context.store, willMount, [this.props]);
38+
}
39+
}
40+
41+
componentDidMount() {
42+
if (mount) {
43+
dispatchFactories(this.getSubscription(), this.context.store, mount, [this.props]);
44+
}
45+
}
46+
47+
componentDidUpdate(prevProps) {
48+
if (update) {
49+
dispatchFactories(this.getSubscription(), this.context.store, update, [this.props, prevProps]);
50+
}
51+
}
52+
53+
componentWillReceiveProps(nextProps) {
54+
if (willRecieveProps) {
55+
dispatchFactories(this.getSubscription(), this.context.store, willRecieveProps, [this.props, nextProps]);
56+
}
57+
}
58+
59+
componentWillUnmount() {
60+
const subscription = this[$$reduxObservableSubscription];
61+
if (subscription) {
62+
subscription.unsubscribe();
63+
}
64+
}
65+
66+
render() {
67+
return (<ComposedComponent {...this.props} />);
68+
}
69+
};
70+
}

Diff for: src/dispatchOnMount.js

-27
This file was deleted.

Diff for: src/index.js

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

Diff for: test/dispatchOnMount-spec.js renamed to test/dipatchOn-spec.js

+98-10
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,37 @@
11
/* global describe, it */
22
import { expect } from 'chai';
33
import { reduxObservable } from 'redux-observable';
4-
import { dispatchOnMount } from '../';
4+
import { dispatchOn } from '../';
55
import { Component } from 'react';
66
import { createStore, applyMiddleware } from 'redux';
77
import * as Rx from 'rxjs';
88
import 'babel-polyfill';
99

1010
const { Observable } = Rx;
1111

12-
describe('dispatchOnMount', () => {
12+
describe('dispatchOn', () => {
1313
it('should exist', () => {
14-
expect(dispatchOnMount).to.be.a('function');
14+
expect(dispatchOn).to.be.a('function');
15+
});
16+
17+
it('should wire a thunkservable to dispatch on componentWillMount', () => {
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+
@dispatchOn({ willMount: [() => () => 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.componentWillMount();
33+
34+
expect(reducedActions).to.deep.equal([{ type: '@@redux/INIT' }, { type: 'TEST' }]);
1535
});
1636

1737
it('should wire a thunkservable to dispatch on componentDidMount', () => {
@@ -22,7 +42,7 @@ describe('dispatchOnMount', () => {
2242
};
2343
let store = createStore(reducer, applyMiddleware(reduxObservable()));
2444

25-
@dispatchOnMount(() => () => Observable.of({ type: 'TEST' }))
45+
@dispatchOn({ mount: [() => () => Observable.of({ type: 'TEST' })] })
2646
class TestComponent extends Component {
2747
}
2848

@@ -34,6 +54,65 @@ describe('dispatchOnMount', () => {
3454
expect(reducedActions).to.deep.equal([{ type: '@@redux/INIT' }, { type: 'TEST' }]);
3555
});
3656

57+
it('should wire a thunkservable to dispatch on componentDidUpdate', () => {
58+
let reducedActions = [];
59+
let reducer = (state, action) => {
60+
reducedActions.push(action);
61+
return state;
62+
};
63+
let store = createStore(reducer, applyMiddleware(reduxObservable()));
64+
let _prevProps;
65+
let _thisProps;
66+
67+
@dispatchOn({ update: [(thisProps, prevProps) => {
68+
_thisProps = thisProps;
69+
_prevProps = prevProps;
70+
return () => Observable.of({ type: 'TEST' });
71+
}] })
72+
class TestComponent extends Component {
73+
}
74+
75+
let comp = new TestComponent();
76+
// fake connection?
77+
comp.context = { store };
78+
comp.props = { some: 'props' };
79+
comp.componentDidUpdate({ prev: 'props' });
80+
81+
expect(_thisProps).to.deep.equal({ some: 'props' });
82+
expect(_prevProps).to.deep.equal({ prev: 'props' });
83+
expect(reducedActions).to.deep.equal([{ type: '@@redux/INIT' }, { type: 'TEST' }]);
84+
});
85+
86+
it('should wire a thunkservable to dispatch on componentWillReceiveProps', () => {
87+
let reducedActions = [];
88+
let reducer = (state, action) => {
89+
reducedActions.push(action);
90+
return state;
91+
};
92+
let store = createStore(reducer, applyMiddleware(reduxObservable()));
93+
94+
let _thisProps;
95+
let _nextProps;
96+
97+
@dispatchOn({ willRecieveProps: [(thisProps, nextProps) => {
98+
_thisProps = thisProps;
99+
_nextProps = nextProps;
100+
return () => Observable.of({ type: 'TEST' });
101+
}] })
102+
class TestComponent extends Component {
103+
}
104+
105+
let comp = new TestComponent();
106+
// fake connection?
107+
comp.context = { store };
108+
comp.props = { some: 'props' };
109+
comp.componentWillReceiveProps({ next: 'props' });
110+
111+
expect(_thisProps).to.deep.equal({ some: 'props' });
112+
expect(_nextProps).to.deep.equal({ next: 'props' });
113+
expect(reducedActions).to.deep.equal([{ type: '@@redux/INIT' }, { type: 'TEST' }]);
114+
});
115+
37116
it('should unsubscribe on componentWillUnmount', () => {
38117
let reducedActions = [];
39118
let reducer = (state, action) => {
@@ -56,7 +135,10 @@ describe('dispatchOnMount', () => {
56135
};
57136
});
58137

59-
@dispatchOnMount(() => () => source1, () => () => source2)
138+
@dispatchOn({ mount: [
139+
() => () => source1,
140+
() => () => source2
141+
] })
60142
class TestComponent extends Component {
61143
}
62144

@@ -85,7 +167,10 @@ describe('dispatchOnMount', () => {
85167
let source1 = Observable.of({ type: 'SOURCE1' });
86168
let source2 = Observable.of({ type: 'SOURCE2' });
87169

88-
@dispatchOnMount(() => () => source1, () => () => source2)
170+
@dispatchOn({ mount: [
171+
() => () => source1,
172+
() => () => source2
173+
] })
89174
class TestComponent extends Component {
90175
}
91176

@@ -111,7 +196,7 @@ describe('dispatchOnMount', () => {
111196

112197
let source2 = Observable.of({ type: 'SOURCE2' });
113198

114-
@dispatchOnMount(() => ({ type: 'PLAIN_ACTION' }), () => () => source2)
199+
@dispatchOn({ mount: [() => ({ type: 'PLAIN_ACTION' }), () => () => source2] })
115200
class TestComponent extends Component {
116201
}
117202

@@ -139,9 +224,12 @@ describe('dispatchOnMount', () => {
139224
};
140225
let store = createStore(reducer, applyMiddleware(reduxObservable()));
141226

142-
@dispatchOnMount(
143-
(props) => ({ type: 'PLAIN_ACTION', value: props.value }),
144-
(props) => () => Observable.of({ type: 'SOURCE2', value: props.value }))
227+
@dispatchOn({
228+
mount: [
229+
(props) => ({ type: 'PLAIN_ACTION', value: props.value }),
230+
(props) => () => Observable.of({ type: 'SOURCE2', value: props.value })
231+
]
232+
})
145233
class TestComponent extends Component {
146234
}
147235

0 commit comments

Comments
 (0)