Move to redux-epic
This commit is contained in:
@ -4,6 +4,7 @@ import React from 'react';
|
|||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
import { Router } from 'react-router';
|
import { Router } from 'react-router';
|
||||||
import { routeReducer as routing, syncHistory } from 'react-router-redux';
|
import { routeReducer as routing, syncHistory } from 'react-router-redux';
|
||||||
|
import { render } from 'redux-epic';
|
||||||
import { createHistory } from 'history';
|
import { createHistory } from 'history';
|
||||||
|
|
||||||
import createApp from '../common/app';
|
import createApp from '../common/app';
|
||||||
@ -12,8 +13,6 @@ import provideStore from '../common/app/provide-store';
|
|||||||
// client specific sagas
|
// client specific sagas
|
||||||
import sagas from './sagas';
|
import sagas from './sagas';
|
||||||
|
|
||||||
// render to observable
|
|
||||||
import render from '../common/app/utils/render';
|
|
||||||
import {
|
import {
|
||||||
isColdStored,
|
isColdStored,
|
||||||
getColdStorage,
|
getColdStorage,
|
||||||
|
@ -3,6 +3,7 @@ import { Row } from 'react-bootstrap';
|
|||||||
import { ToastMessage, ToastContainer } from 'react-toastr';
|
import { ToastMessage, ToastContainer } from 'react-toastr';
|
||||||
import { compose } from 'redux';
|
import { compose } from 'redux';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { contain } from 'redux-epic';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -10,7 +11,6 @@ import {
|
|||||||
updateWindowHeight,
|
updateWindowHeight,
|
||||||
updateNavHeight
|
updateNavHeight
|
||||||
} from './redux/actions';
|
} from './redux/actions';
|
||||||
import contain from './utils/professor-x';
|
|
||||||
import getWindowHeight from './utils/get-window-height';
|
import getWindowHeight from './utils/get-window-height';
|
||||||
|
|
||||||
import Nav from './components/Nav';
|
import Nav from './components/Nav';
|
||||||
|
@ -8,7 +8,7 @@ import App from './App.jsx';
|
|||||||
import childRoutes from './routes';
|
import childRoutes from './routes';
|
||||||
|
|
||||||
// redux
|
// redux
|
||||||
import createEpic from './utils/redux-epic';
|
import { createEpic } from 'redux-epic';
|
||||||
import createReducer from './create-reducer';
|
import createReducer from './create-reducer';
|
||||||
import middlewares from './middlewares';
|
import middlewares from './middlewares';
|
||||||
import sagas from './sagas';
|
import sagas from './sagas';
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import { compose } from 'redux';
|
import { compose } from 'redux';
|
||||||
|
import { contain } from 'redux-epic';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PureComponent from 'react-pure-render/component';
|
import PureComponent from 'react-pure-render/component';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
@ -8,7 +9,6 @@ import { createSelector } from 'reselect';
|
|||||||
import HikesMap from './Map.jsx';
|
import HikesMap from './Map.jsx';
|
||||||
import { fetchHikes } from '../redux/actions';
|
import { fetchHikes } from '../redux/actions';
|
||||||
|
|
||||||
import contain from '../../../utils/professor-x';
|
|
||||||
|
|
||||||
// const log = debug('fcc:hikes');
|
// const log = debug('fcc:hikes');
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React, { cloneElement, PropTypes } from 'react';
|
import React, { cloneElement, PropTypes } from 'react';
|
||||||
import { compose } from 'redux';
|
import { compose } from 'redux';
|
||||||
|
import { contain } from 'redux-epic';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { LinkContainer } from 'react-router-bootstrap';
|
import { LinkContainer } from 'react-router-bootstrap';
|
||||||
@ -7,7 +8,6 @@ import { LinkContainer } from 'react-router-bootstrap';
|
|||||||
import PureComponent from 'react-pure-render/component';
|
import PureComponent from 'react-pure-render/component';
|
||||||
import { Button, Row, Col } from 'react-bootstrap';
|
import { Button, Row, Col } from 'react-bootstrap';
|
||||||
|
|
||||||
import contain from '../../../utils/professor-x';
|
|
||||||
import ListJobs from './List.jsx';
|
import ListJobs from './List.jsx';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import { compose } from 'redux';
|
import { compose } from 'redux';
|
||||||
|
import { contain } from 'redux-epic';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { push } from 'react-router-redux';
|
import { push } from 'react-router-redux';
|
||||||
import PureComponent from 'react-pure-render/component';
|
import PureComponent from 'react-pure-render/component';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
import contain from '../../../utils/professor-x';
|
|
||||||
import { fetchJobs } from '../redux/actions';
|
import { fetchJobs } from '../redux/actions';
|
||||||
|
|
||||||
import ShowJob from './ShowJob.jsx';
|
import ShowJob from './ShowJob.jsx';
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import { compose } from 'redux';
|
import { compose } from 'redux';
|
||||||
|
import { contain } from 'redux-epic';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PureComponent from 'react-pure-render/component';
|
import PureComponent from 'react-pure-render/component';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
import Map from './Map.jsx';
|
import Map from './Map.jsx';
|
||||||
import contain from '../../../utils/professor-x';
|
|
||||||
import {
|
import {
|
||||||
clearFilter,
|
clearFilter,
|
||||||
fetchChallenges,
|
fetchChallenges,
|
||||||
|
@ -1,117 +0,0 @@
|
|||||||
import { helpers } from 'rx';
|
|
||||||
import { createElement } from 'react';
|
|
||||||
import PureComponent from 'react-pure-render/component';
|
|
||||||
import debug from 'debug';
|
|
||||||
|
|
||||||
// interface contain {
|
|
||||||
// (options?: Object, Component: ReactComponent) => ReactComponent
|
|
||||||
// (options?: Object) => (Component: ReactComponent) => ReactComponent
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Action: { type: String, payload: Any, ...meta }
|
|
||||||
//
|
|
||||||
// ActionCreator(...args) => Observable
|
|
||||||
//
|
|
||||||
// interface options {
|
|
||||||
// fetchAction?: ActionCreator,
|
|
||||||
// getActionArgs?(props: Object, context: Object) => [],
|
|
||||||
// isPrimed?(props: Object, context: Object) => Boolean,
|
|
||||||
// handleError?(err) => Void
|
|
||||||
// shouldRefetch?(
|
|
||||||
// props: Object,
|
|
||||||
// nextProps: Object,
|
|
||||||
// context: Object,
|
|
||||||
// nextContext: Object
|
|
||||||
// ) => Boolean,
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
const log = debug('fcc:professerx');
|
|
||||||
|
|
||||||
const __DEV__ = process.env.NODE_ENV !== 'production';
|
|
||||||
const { isFunction } = helpers;
|
|
||||||
|
|
||||||
export default function contain(options = {}, Component) {
|
|
||||||
/* istanbul ignore else */
|
|
||||||
if (!Component) {
|
|
||||||
return contain.bind(null, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
let action;
|
|
||||||
let isActionable = false;
|
|
||||||
let hasRefetcher = isFunction(options.shouldRefetch);
|
|
||||||
const getActionArgs = isFunction(options.getActionArgs) ?
|
|
||||||
options.getActionArgs :
|
|
||||||
(() => []);
|
|
||||||
|
|
||||||
const isPrimed = isFunction(options.isPrimed) ?
|
|
||||||
options.isPrimed :
|
|
||||||
(() => false);
|
|
||||||
|
|
||||||
const name = Component.displayName || 'Anon Component';
|
|
||||||
|
|
||||||
function runAction(props, context, action) {
|
|
||||||
const actionArgs = getActionArgs(props, context);
|
|
||||||
if (__DEV__ && !Array.isArray(actionArgs)) {
|
|
||||||
throw new TypeError(
|
|
||||||
`${name} getActionArgs should return an array but got ${actionArgs}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return action.apply(null, actionArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return class Container extends PureComponent {
|
|
||||||
static displayName = `Container(${name})`;
|
|
||||||
|
|
||||||
componentWillMount() {
|
|
||||||
const { props, context } = this;
|
|
||||||
if (!options.fetchAction) {
|
|
||||||
log(`${name} has no fetch action defined`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isPrimed(this.props, this.context)) {
|
|
||||||
log(`${name} container is primed`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
action = props[options.fetchAction];
|
|
||||||
isActionable = typeof action === 'function';
|
|
||||||
|
|
||||||
if (__DEV__ && !isActionable) {
|
|
||||||
throw new Error(
|
|
||||||
`${options.fetchAction} should return a function but got ${action}.
|
|
||||||
Check the fetch options for ${name}.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
runAction(
|
|
||||||
props,
|
|
||||||
context,
|
|
||||||
action
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps, nextContext) {
|
|
||||||
if (
|
|
||||||
!isActionable ||
|
|
||||||
!hasRefetcher ||
|
|
||||||
!options.shouldRefetch(this.props, nextProps, this.context, nextContext)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
runAction(
|
|
||||||
nextProps,
|
|
||||||
nextContext,
|
|
||||||
action
|
|
||||||
);
|
|
||||||
}
|
|
||||||
render() {
|
|
||||||
return createElement(
|
|
||||||
Component,
|
|
||||||
this.props
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
import { CompositeDisposable, Observable, Subject } from 'rx';
|
|
||||||
|
|
||||||
export default (dependencies, ...sagas) => {
|
|
||||||
if (typeof dependencies === 'function') {
|
|
||||||
sagas.push(dependencies);
|
|
||||||
dependencies = {};
|
|
||||||
}
|
|
||||||
let action$;
|
|
||||||
let lifecycle;
|
|
||||||
let compositeDisposable;
|
|
||||||
let start;
|
|
||||||
function sagaMiddleware({ dispatch, getState }) {
|
|
||||||
start = () => {
|
|
||||||
compositeDisposable = new CompositeDisposable();
|
|
||||||
action$ = new Subject();
|
|
||||||
lifecycle = new Subject();
|
|
||||||
const sagaSubscription = Observable
|
|
||||||
.from(sagas)
|
|
||||||
.map(saga => saga(action$, getState, dependencies))
|
|
||||||
.doOnNext(result$ => {
|
|
||||||
if (!Observable.isObservable(result$)) {
|
|
||||||
throw new Error('saga should returned an observable');
|
|
||||||
}
|
|
||||||
if (result$ === action$) {
|
|
||||||
throw new Error('Saga returned original action stream!');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.mergeAll()
|
|
||||||
.filter(action => action && typeof action.type === 'string')
|
|
||||||
.subscribe(
|
|
||||||
action => dispatch(action),
|
|
||||||
err => { throw err; },
|
|
||||||
() => lifecycle.onCompleted()
|
|
||||||
);
|
|
||||||
compositeDisposable.add(sagaSubscription);
|
|
||||||
};
|
|
||||||
start();
|
|
||||||
return next => action => {
|
|
||||||
const result = next(action);
|
|
||||||
action$.onNext(action);
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
sagaMiddleware.subscribe =
|
|
||||||
(...args) => lifecycle.subscribe.apply(lifecycle, args);
|
|
||||||
sagaMiddleware.subscribeOnCompleted =
|
|
||||||
(...args) => lifecycle.subscribeOnCompleted.apply(lifecycle, args);
|
|
||||||
sagaMiddleware.end = () => action$.onCompleted();
|
|
||||||
sagaMiddleware.dispose = () => compositeDisposable.dispose();
|
|
||||||
sagaMiddleware.restart = () => {
|
|
||||||
sagaMiddleware.dispose();
|
|
||||||
action$.dispose();
|
|
||||||
start();
|
|
||||||
};
|
|
||||||
return sagaMiddleware;
|
|
||||||
};
|
|
@ -1,203 +0,0 @@
|
|||||||
import { Observable, Subject } from 'rx';
|
|
||||||
import test from 'tape';
|
|
||||||
import { spy } from 'sinon';
|
|
||||||
import { applyMiddleware, createStore } from 'redux';
|
|
||||||
import createSaga from './redux-epic';
|
|
||||||
|
|
||||||
const setup = (saga, spy) => {
|
|
||||||
const reducer = (state = 0) => state;
|
|
||||||
const sagaMiddleware = createSaga(
|
|
||||||
action$ => action$
|
|
||||||
.filter(({ type }) => type === 'foo')
|
|
||||||
.map(() => ({ type: 'bar' })),
|
|
||||||
action$ => action$
|
|
||||||
.filter(({ type }) => type === 'bar')
|
|
||||||
.map(({ type: 'baz' })),
|
|
||||||
saga ? saga : () => Observable.empty()
|
|
||||||
);
|
|
||||||
const store = applyMiddleware(sagaMiddleware)(createStore)(spy || reducer);
|
|
||||||
return {
|
|
||||||
reducer,
|
|
||||||
sagaMiddleware,
|
|
||||||
store
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
test('createSaga', t => {
|
|
||||||
const sagaMiddleware = createSaga(
|
|
||||||
action$ => action$.map({ type: 'foo' })
|
|
||||||
);
|
|
||||||
t.equal(
|
|
||||||
typeof sagaMiddleware,
|
|
||||||
'function',
|
|
||||||
'sagaMiddleware is not a function'
|
|
||||||
);
|
|
||||||
t.equal(
|
|
||||||
typeof sagaMiddleware.subscribe,
|
|
||||||
'function',
|
|
||||||
'sagaMiddleware does not have a subscription method'
|
|
||||||
);
|
|
||||||
t.equal(
|
|
||||||
typeof sagaMiddleware.subscribeOnCompleted,
|
|
||||||
'function',
|
|
||||||
'sagaMiddleware does not have a subscribeOnCompleted method'
|
|
||||||
);
|
|
||||||
t.equal(
|
|
||||||
typeof sagaMiddleware.end,
|
|
||||||
'function',
|
|
||||||
'sagaMiddleware does not have an end method'
|
|
||||||
);
|
|
||||||
t.equal(
|
|
||||||
typeof sagaMiddleware.restart,
|
|
||||||
'function',
|
|
||||||
'sagaMiddleware does not have an restart method'
|
|
||||||
);
|
|
||||||
t.equal(
|
|
||||||
typeof sagaMiddleware.dispose,
|
|
||||||
'function',
|
|
||||||
'sagaMiddleware does not have a dispose method'
|
|
||||||
);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('dispatching actions', t => {
|
|
||||||
const reducer = spy((state = 0) => state);
|
|
||||||
const { store } = setup(null, reducer);
|
|
||||||
store.dispatch({ type: 'foo' });
|
|
||||||
t.equal(reducer.callCount, 4, 'reducer is called four times');
|
|
||||||
t.assert(
|
|
||||||
reducer.getCall(1).calledWith(0, { type: 'foo' }),
|
|
||||||
'reducer called with initial action'
|
|
||||||
);
|
|
||||||
t.assert(
|
|
||||||
reducer.getCall(2).calledWith(0, { type: 'bar' }),
|
|
||||||
'reducer was not called with saga action'
|
|
||||||
);
|
|
||||||
t.assert(
|
|
||||||
reducer.getCall(3).calledWith(0, { type: 'baz' }),
|
|
||||||
'second saga responded to action from first saga'
|
|
||||||
);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('lifecycle', t => {
|
|
||||||
t.test('subscribe', t => {
|
|
||||||
const { sagaMiddleware } = setup();
|
|
||||||
const subscription = sagaMiddleware.subscribeOnCompleted(() => {});
|
|
||||||
t.assert(
|
|
||||||
subscription,
|
|
||||||
'subscribe did not return a disposable'
|
|
||||||
);
|
|
||||||
t.isEqual(
|
|
||||||
typeof subscription.dispose,
|
|
||||||
'function',
|
|
||||||
'disposable does not have a dispose method'
|
|
||||||
);
|
|
||||||
t.doesNotThrow(
|
|
||||||
() => subscription.dispose(),
|
|
||||||
'disposable is not disposable'
|
|
||||||
);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
t.test('end', t => {
|
|
||||||
const result$ = new Subject();
|
|
||||||
const { sagaMiddleware } = setup(() => result$);
|
|
||||||
sagaMiddleware.subscribeOnCompleted(() => {
|
|
||||||
t.pass('all sagas completed');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
sagaMiddleware.end();
|
|
||||||
t.pass('saga still active');
|
|
||||||
result$.onCompleted();
|
|
||||||
});
|
|
||||||
|
|
||||||
t.test('disposable', t => {
|
|
||||||
const result$ = new Subject();
|
|
||||||
const { sagaMiddleware } = setup(() => result$);
|
|
||||||
t.plan(2);
|
|
||||||
sagaMiddleware.subscribeOnCompleted(() => {
|
|
||||||
t.fail('all sagas completed');
|
|
||||||
});
|
|
||||||
t.assert(
|
|
||||||
result$.hasObservers(),
|
|
||||||
'saga is observed by sagaMiddleware'
|
|
||||||
);
|
|
||||||
sagaMiddleware.dispose();
|
|
||||||
t.false(
|
|
||||||
result$.hasObservers(),
|
|
||||||
'watcher has no observers after sagaMiddleware is disposed'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('restart', t => {
|
|
||||||
const reducer = spy((state = 0) => state);
|
|
||||||
const { sagaMiddleware, store } = setup(null, reducer);
|
|
||||||
store.dispatch({ type: 'foo' });
|
|
||||||
t.assert(
|
|
||||||
reducer.getCall(1).calledWith(0, { type: 'foo' }),
|
|
||||||
'reducer called with initial dispatch'
|
|
||||||
);
|
|
||||||
t.assert(
|
|
||||||
reducer.getCall(2).calledWith(0, { type: 'bar' }),
|
|
||||||
'reducer called with saga action'
|
|
||||||
);
|
|
||||||
t.assert(
|
|
||||||
reducer.getCall(3).calledWith(0, { type: 'baz' }),
|
|
||||||
'second saga responded to action from first saga'
|
|
||||||
);
|
|
||||||
sagaMiddleware.end();
|
|
||||||
t.equal(reducer.callCount, 4, 'saga produced correct amount of actions');
|
|
||||||
sagaMiddleware.restart();
|
|
||||||
store.dispatch({ type: 'foo' });
|
|
||||||
t.equal(
|
|
||||||
reducer.callCount,
|
|
||||||
7,
|
|
||||||
'saga restart and produced correct amount of actions'
|
|
||||||
);
|
|
||||||
t.assert(
|
|
||||||
reducer.getCall(4).calledWith(0, { type: 'foo' }),
|
|
||||||
'reducer called with second dispatch'
|
|
||||||
);
|
|
||||||
t.assert(
|
|
||||||
reducer.getCall(5).calledWith(0, { type: 'bar' }),
|
|
||||||
'reducer called with saga reaction'
|
|
||||||
);
|
|
||||||
t.assert(
|
|
||||||
reducer.getCall(6).calledWith(0, { type: 'baz' }),
|
|
||||||
'second saga responded to action from first saga'
|
|
||||||
);
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('long lived saga', t => {
|
|
||||||
let count = 0;
|
|
||||||
const tickSaga = action$ => action$
|
|
||||||
.filter(({ type }) => type === 'start-tick')
|
|
||||||
.flatMap(() => Observable.interval(500))
|
|
||||||
// make sure long lived saga's do not persist after
|
|
||||||
// action$ has completed
|
|
||||||
.takeUntil(action$.last())
|
|
||||||
.map(({ type: 'tick' }));
|
|
||||||
|
|
||||||
const reducerSpy = spy((state = 0) => state);
|
|
||||||
const { store, sagaMiddleware } = setup(tickSaga, reducerSpy);
|
|
||||||
const unlisten = store.subscribe(() => {
|
|
||||||
count += 1;
|
|
||||||
if (count >= 5) {
|
|
||||||
sagaMiddleware.end();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
sagaMiddleware.subscribeOnCompleted(() => {
|
|
||||||
t.equal(
|
|
||||||
count,
|
|
||||||
5,
|
|
||||||
'saga dispatched correct amount of ticks'
|
|
||||||
);
|
|
||||||
unlisten();
|
|
||||||
t.pass('long lived saga completed');
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
store.dispatch({ type: 'start-tick' });
|
|
||||||
});
|
|
@ -1,24 +0,0 @@
|
|||||||
import { Observable } from 'rx';
|
|
||||||
import ReactDOM from 'react-dom/server';
|
|
||||||
import debug from 'debug';
|
|
||||||
|
|
||||||
const log = debug('fcc:professor');
|
|
||||||
|
|
||||||
export default function renderToString(Component, sagaMiddleware) {
|
|
||||||
try {
|
|
||||||
log('initial render');
|
|
||||||
ReactDOM.renderToStaticMarkup(Component);
|
|
||||||
log('initial render completed');
|
|
||||||
} catch (e) {
|
|
||||||
return Observable.throw(e);
|
|
||||||
}
|
|
||||||
sagaMiddleware.end();
|
|
||||||
return Observable.merge(sagaMiddleware)
|
|
||||||
.last({ defaultValue: null })
|
|
||||||
.delay(0)
|
|
||||||
.map(() => {
|
|
||||||
sagaMiddleware.restart();
|
|
||||||
const markup = ReactDOM.renderToString(Component);
|
|
||||||
return { markup };
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
import ReactDOM from 'react-dom';
|
|
||||||
import { Disposable, Observable } from 'rx';
|
|
||||||
|
|
||||||
export default function render(Component, DOMContainer) {
|
|
||||||
return Observable.create(observer => {
|
|
||||||
try {
|
|
||||||
ReactDOM.render(Component, DOMContainer, function() {
|
|
||||||
observer.onNext(this);
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
return observer.onError(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Disposable.create(() => {
|
|
||||||
return ReactDOM.unmountComponentAtNode(DOMContainer);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
@ -81,9 +81,8 @@
|
|||||||
"pmx": "~0.6.2",
|
"pmx": "~0.6.2",
|
||||||
"react": "^15.0.2",
|
"react": "^15.0.2",
|
||||||
"react-bootstrap": "~0.29.4",
|
"react-bootstrap": "~0.29.4",
|
||||||
|
"react-css-transition-replace": "^1.2.0-beta",
|
||||||
"react-dom": "^15.0.2",
|
"react-dom": "^15.0.2",
|
||||||
"react-addons-css-transition-group": "^0.14.7",
|
|
||||||
"react-css-transition-replace": "^1.1.0",
|
|
||||||
"react-fontawesome": "^0.3.3",
|
"react-fontawesome": "^0.3.3",
|
||||||
"react-motion": "~0.4.2",
|
"react-motion": "~0.4.2",
|
||||||
"react-no-ssr": "^1.0.1",
|
"react-no-ssr": "^1.0.1",
|
||||||
@ -96,6 +95,7 @@
|
|||||||
"react-youtube": "^6.1.0",
|
"react-youtube": "^6.1.0",
|
||||||
"redux": "^3.0.5",
|
"redux": "^3.0.5",
|
||||||
"redux-actions": "^0.9.1",
|
"redux-actions": "^0.9.1",
|
||||||
|
"redux-epic": "^0.1.1",
|
||||||
"redux-form": "^5.2.3",
|
"redux-form": "^5.2.3",
|
||||||
"request": "^2.65.0",
|
"request": "^2.65.0",
|
||||||
"reselect": "^2.0.2",
|
"reselect": "^2.0.2",
|
||||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import { RouterContext } from 'react-router';
|
import { RouterContext } from 'react-router';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
|
|
||||||
import renderToString from '../../common/app/utils/render-to-string';
|
import { renderToString } from 'redux-epic';
|
||||||
import provideStore from '../../common/app/provide-store';
|
import provideStore from '../../common/app/provide-store';
|
||||||
|
|
||||||
import createApp from '../../common/app';
|
import createApp from '../../common/app';
|
||||||
|
Reference in New Issue
Block a user