When using redux-observable with react-router, would it make sense to asynchronously add new epics as per the instructions in the documentation here inside onEnter hooks of
In this case, would epic3 get added again to the rootEpic every time we navigate to /some-path?
If you're using react-router and doing code splitting, you'll actually want to add the necessary epics inside your getComponent
hook, not the onEnter
hook. This is because the getComponent
hook allows async loading of resources necessary for that route, not just the component but also any epics, reducers, etc. If you use require.ensure()
, this tells webpack to split these items into a separate bundle so they are not included in the original entry one--so don't import those files anywhere else, otherwise you'll negate this!
So roughly this is an example of what that might look like:
import { registerEpic } from 'where/ever/this/is/epics/index.js';
export default {
path: 'some-path',
getComponent(location, done) {
require.ensure(['./components/SomePath', 'path/to/epics/epic3'], require => {
// this is where you register epics, reducers, etc
// that are part of this separate bundle
const epic = require('path/to/epics/epic3').epic3;
registerEpic(epic);
done(null, require('./components/SomePath'));
});
}
};
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { combineEpics } from 'redux-observable';
import { epic1 } from './epic1';
import { epic2 } from './epic2';
const epicRegistry = [epic1, epic2];
const epic$ = new BehaviorSubject(combineEpics(...epicRegistry));
export const registerEpic = (epic) => {
// don't add an epic that is already registered/running
if (epicRegistry.indexOf(epic) === -1) {
epicRegistry.push(epic);
epic$.next(epic);
}
};
// your root epic needs to use `mergeMap` on the epic$ so any
// new epics are composed with the others
export const rootEpic = (action$, store) =>
epic$.mergeMap(epic =>
epic(action$, store)
);
I created a registration function that makes sure you're not adding an epic that already has been added; react-router will call getComponent
every time you enter that route, so this is important so you don't have two running instances of the same epic.
Take note however, my code makes some assumptions that might be wrong out of box, when it comes to things like require('path/to/epics/epic3').epic3
. Is your epic3 actually exported as a named property? I presume so, but I noticed you are doing this done(null, require('./components/SomePath'))
which my gut tells me might not be working correctly if you're exporting that component using export default
with ES2015/ES6 modules. If that's true, it's actually found at require('./components/SomePath').default
--note the .default
! So while my code is the general idea, you should take special note of the require paths, exported properties, etc. You said it seems to be working, so perhaps your setup is different (or I'm just wrong hehe)
- If I wanted to console.log which epics have been added into the rootEpic, how would I do that?
The easiest would just be to log inside the registerEpic
utility:
export const registerEpic = (epic) => {
// don't add an epic that is already registered/running
if (epicRegistry.indexOf(epic) === -1) {
epicRegistry.push(epic);
console.log(`new epic added: ${epic.name}`);
epic$.next(epic);
}
};
But you can also do this more concretely using the RxJS .do()
operator on your epic$
subject:
export const rootEpic = (action$, store) =>
epic$
.do(epic => console.log(`new epic added: ${epic.name || '<anonymous>'}`))
.mergeMap(epic =>
epic(action$, store)
);
However, this will log <anonymous>
the first time, because the first one that comes through is the result of combineEpics(...epicRegistry)
which combines multiple epics into a single anonymous epic.