diff --git a/src/dispatchOn.js b/src/dispatchOn.js new file mode 100644 index 0000000..7141d59 --- /dev/null +++ b/src/dispatchOn.js @@ -0,0 +1,70 @@ +import React, { Component, PropTypes } from 'react'; +import { Subscription } from 'rxjs/Subscription'; + +const $$reduxObservableSubscription = '@@reduxObservableSubscription'; + +const dispatchFactories = (subscription, store, factories, args) => { + factories.map(factory => store.dispatch(factory(...args))) + .forEach(sub => sub && subscription.add(sub)); +}; + +export function dispatchOn({ + willMount = null, + mount = null, + update = null, + willRecieveProps = null +}) { + return (ComposedComponent) => + class DispatchOnMountComponent extends Component { + constructor(props) { + super(props); + } + + static contextTypes = { + store: PropTypes.object.isRequired + } + + getSubscription() { + const subscription = this[$$reduxObservableSubscription]; + if (!subscription || subscription.isUnsubscribed) { + this[$$reduxObservableSubscription] = new Subscription(); + } + return this[$$reduxObservableSubscription]; + } + + componentWillMount() { + if (willMount) { + dispatchFactories(this.getSubscription(), this.context.store, willMount, [this.props]); + } + } + + componentDidMount() { + if (mount) { + dispatchFactories(this.getSubscription(), this.context.store, mount, [this.props]); + } + } + + componentDidUpdate(prevProps) { + if (update) { + dispatchFactories(this.getSubscription(), this.context.store, update, [this.props, prevProps]); + } + } + + componentWillReceiveProps(nextProps) { + if (willRecieveProps) { + dispatchFactories(this.getSubscription(), this.context.store, willRecieveProps, [this.props, nextProps]); + } + } + + componentWillUnmount() { + const subscription = this[$$reduxObservableSubscription]; + if (subscription) { + subscription.unsubscribe(); + } + } + + render() { + return (); + } + }; +} diff --git a/src/dispatchOnMount.js b/src/dispatchOnMount.js deleted file mode 100644 index 65f30de..0000000 --- a/src/dispatchOnMount.js +++ /dev/null @@ -1,27 +0,0 @@ -import React, { Component, PropTypes } from 'react'; -import { Subscription } from 'rxjs/Subscription'; - -const $$reduxObservableSubscription = '@@reduxObservableSubscription'; - -export function dispatchOnMount(...factories) { - return (ComposedComponent) => - class DispatchOnMountComponent extends Component { - static contextTypes = { - store: PropTypes.object.isRequired - } - - componentDidMount() { - this[$$reduxObservableSubscription] = new Subscription(); - factories.map(factory => this.context.store.dispatch(factory(this.props))) - .forEach(sub => sub && this[$$reduxObservableSubscription].add(sub)); - } - - componentWillUnmount() { - this[$$reduxObservableSubscription].unsubscribe(); - } - - render() { - return (); - } - }; -} diff --git a/src/index.js b/src/index.js index 4c38f42..b196323 100644 --- a/src/index.js +++ b/src/index.js @@ -1 +1 @@ -export { dispatchOnMount } from './dispatchOnMount'; +export { dispatchOn } from './dispatchOn'; diff --git a/test/dispatchOnMount-spec.js b/test/dipatchOn-spec.js similarity index 58% rename from test/dispatchOnMount-spec.js rename to test/dipatchOn-spec.js index c1cadd8..0a86b68 100644 --- a/test/dispatchOnMount-spec.js +++ b/test/dipatchOn-spec.js @@ -1,7 +1,7 @@ /* global describe, it */ import { expect } from 'chai'; import { reduxObservable } from 'redux-observable'; -import { dispatchOnMount } from '../'; +import { dispatchOn } from '../'; import { Component } from 'react'; import { createStore, applyMiddleware } from 'redux'; import * as Rx from 'rxjs'; @@ -9,9 +9,29 @@ import 'babel-polyfill'; const { Observable } = Rx; -describe('dispatchOnMount', () => { +describe('dispatchOn', () => { it('should exist', () => { - expect(dispatchOnMount).to.be.a('function'); + expect(dispatchOn).to.be.a('function'); + }); + + it('should wire a thunkservable to dispatch on componentWillMount', () => { + let reducedActions = []; + let reducer = (state, action) => { + reducedActions.push(action); + return state; + }; + let store = createStore(reducer, applyMiddleware(reduxObservable())); + + @dispatchOn({ willMount: [() => () => Observable.of({ type: 'TEST' })] }) + class TestComponent extends Component { + } + + let comp = new TestComponent(); + // fake connection? + comp.context = { store }; + comp.componentWillMount(); + + expect(reducedActions).to.deep.equal([{ type: '@@redux/INIT' }, { type: 'TEST' }]); }); it('should wire a thunkservable to dispatch on componentDidMount', () => { @@ -22,7 +42,7 @@ describe('dispatchOnMount', () => { }; let store = createStore(reducer, applyMiddleware(reduxObservable())); - @dispatchOnMount(() => () => Observable.of({ type: 'TEST' })) + @dispatchOn({ mount: [() => () => Observable.of({ type: 'TEST' })] }) class TestComponent extends Component { } @@ -34,6 +54,65 @@ describe('dispatchOnMount', () => { expect(reducedActions).to.deep.equal([{ type: '@@redux/INIT' }, { type: 'TEST' }]); }); + it('should wire a thunkservable to dispatch on componentDidUpdate', () => { + let reducedActions = []; + let reducer = (state, action) => { + reducedActions.push(action); + return state; + }; + let store = createStore(reducer, applyMiddleware(reduxObservable())); + let _prevProps; + let _thisProps; + + @dispatchOn({ update: [(thisProps, prevProps) => { + _thisProps = thisProps; + _prevProps = prevProps; + return () => Observable.of({ type: 'TEST' }); + }] }) + class TestComponent extends Component { + } + + let comp = new TestComponent(); + // fake connection? + comp.context = { store }; + comp.props = { some: 'props' }; + comp.componentDidUpdate({ prev: 'props' }); + + expect(_thisProps).to.deep.equal({ some: 'props' }); + expect(_prevProps).to.deep.equal({ prev: 'props' }); + expect(reducedActions).to.deep.equal([{ type: '@@redux/INIT' }, { type: 'TEST' }]); + }); + + it('should wire a thunkservable to dispatch on componentWillReceiveProps', () => { + let reducedActions = []; + let reducer = (state, action) => { + reducedActions.push(action); + return state; + }; + let store = createStore(reducer, applyMiddleware(reduxObservable())); + + let _thisProps; + let _nextProps; + + @dispatchOn({ willRecieveProps: [(thisProps, nextProps) => { + _thisProps = thisProps; + _nextProps = nextProps; + return () => Observable.of({ type: 'TEST' }); + }] }) + class TestComponent extends Component { + } + + let comp = new TestComponent(); + // fake connection? + comp.context = { store }; + comp.props = { some: 'props' }; + comp.componentWillReceiveProps({ next: 'props' }); + + expect(_thisProps).to.deep.equal({ some: 'props' }); + expect(_nextProps).to.deep.equal({ next: 'props' }); + expect(reducedActions).to.deep.equal([{ type: '@@redux/INIT' }, { type: 'TEST' }]); + }); + it('should unsubscribe on componentWillUnmount', () => { let reducedActions = []; let reducer = (state, action) => { @@ -56,7 +135,10 @@ describe('dispatchOnMount', () => { }; }); - @dispatchOnMount(() => () => source1, () => () => source2) + @dispatchOn({ mount: [ + () => () => source1, + () => () => source2 + ] }) class TestComponent extends Component { } @@ -85,7 +167,10 @@ describe('dispatchOnMount', () => { let source1 = Observable.of({ type: 'SOURCE1' }); let source2 = Observable.of({ type: 'SOURCE2' }); - @dispatchOnMount(() => () => source1, () => () => source2) + @dispatchOn({ mount: [ + () => () => source1, + () => () => source2 + ] }) class TestComponent extends Component { } @@ -111,7 +196,7 @@ describe('dispatchOnMount', () => { let source2 = Observable.of({ type: 'SOURCE2' }); - @dispatchOnMount(() => ({ type: 'PLAIN_ACTION' }), () => () => source2) + @dispatchOn({ mount: [() => ({ type: 'PLAIN_ACTION' }), () => () => source2] }) class TestComponent extends Component { } @@ -139,9 +224,12 @@ describe('dispatchOnMount', () => { }; let store = createStore(reducer, applyMiddleware(reduxObservable())); - @dispatchOnMount( - (props) => ({ type: 'PLAIN_ACTION', value: props.value }), - (props) => () => Observable.of({ type: 'SOURCE2', value: props.value })) + @dispatchOn({ + mount: [ + (props) => ({ type: 'PLAIN_ACTION', value: props.value }), + (props) => () => Observable.of({ type: 'SOURCE2', value: props.value }) + ] + }) class TestComponent extends Component { }