Skip to content

Instantly share code, notes, and snippets.

@tomkis
Created September 2, 2016 11:32
Show Gist options
  • Save tomkis/b93f653045a27c51b360ddc7487be713 to your computer and use it in GitHub Desktop.
Save tomkis/b93f653045a27c51b360ddc7487be713 to your computer and use it in GitHub Desktop.
redux-observable epic hot reloading
// main.js
import { Subject } from 'rxjs';
import { createStore, compose, applyMiddleware } from 'redux';
import pingPongReducer from './pingPongReducer';
import pingPongEpic from './pingPongEpic';
const buildEpicSubscriber = () => {
let subscription = null;
return (subject, epic, store) => {
if (subscription) {
subscription.unsubscribe();
}
subscription = epic(subject)
.subscribe(store.dispatch);
}
};
const reduxObservable = epic => createStore => (reducer, ...rest) => {
let subject = new Subject();
let store = createStore(reducer, ...rest);
const subscribeEpic = buildEpicSubscriber();
const devTools = window.devToolsExtension.connect();
const originalDispatch = store.dispatch;
store = {
...store,
dispatch: action => {
const result = originalDispatch(action);
devTools.send(action, store.getState());
return result;
}
};
subscribeEpic(
subject,
epic,
store
);
const eventLog = [];
let lastReducer = reducer;
let lastEpic = epic;
const replace = (newReducer, newEpic) => {
subject = new Subject();
subscribeEpic(
subject,
newEpic,
store
);
store.replaceReducer(() => undefined);
store.replaceReducer(newReducer);
devTools.init(store.getState());
eventLog.forEach(action => {
store.dispatch(action);
subject.next(action);
});
};
return {
...store,
dispatch: action => {
const result = store.dispatch(action);
eventLog.push(action);
subject.next(action);
return result;
},
replaceReducer: newReducer => {
replace(newReducer, lastEpic);
lastReducer = reducer;
},
replaceEpic: newEpic => {
replace(lastReducer, newEpic);
lastEpic = epic;
}
};
};
const store = createStore(
pingPongReducer,
compose(
reduxObservable(pingPongEpic)
)
);
store.dispatch({ type: 'Ping' });
store.dispatch({ type: 'Ping' });
store.dispatch({ type: 'Ping' });
if (module.hot) {
module.hot.accept('./pingPongReducer', () => {
store.replaceReducer(require('./pingPongReducer').default);
});
module.hot.accept('./pingPongEpic', () => {
store.replaceEpic(require('./pingPongEpic').default);
});
}
// pingPingReducer.js
export default (appState = { pinged: 0, ponged: false }, { type }) => {
switch (type) {
case 'Ping':
return {
...appState,
pinged: appState.pinged + 1
};
case 'Pong':
return {
...appState,
ponged: true
};
default:
return appState;
}
};
// pingPongEpic.js
import { Observable } from 'rxjs';
const predicateReducer = (pinged, { type }) => {
if (type === 'Ping') {
return pinged + 1;
} else {
return pinged;
}
};
const businessPredicate = pinged => pinged >= 3;
export default action$ => action$
.scan(predicateReducer, 0)
.filter(businessPredicate)
.debounce(() => Observable.timer(300))
.map(() => ({ type: 'Pong' }))
.take(1);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment