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 react-router during route changes?
./epics/index.js
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { combineEpics } from 'redux-observable';
import { epic1 } from './epic1'
import { epic2 } from './epic2'
export const epic$ = new BehaviorSubject(combineEpics(epic1, epic2));
export const rootEpic = (action$, store) =>
epic$.mergeMap(epic =>
epic(action$, store)
);
export {
rootEpic
};
...routes/SomePath/index.js
import { epic$ } from './../../../../epics/index'
import { epic3 } from './../../../../epics/epic3'
module.exports = {
path: 'some-path',
getComponent( location, cb ) {
require.ensure( [], ( require ) => {
cb( null, require( './components/SomePath' ) )
} )
},
onEnter() {
epic$.next(epic3)
}
}
Very new to Rxjs and redux-observable. This seems to work, but wondering: 1. In this case, would epic3 get added again to the rootEpic every time we navigate to /some-path ? 2. If I wanted to console.log which epics have been added into the rootEpic, how would I do that?
Edited in response to @JayPhelps
May I request clarification on a few points?
The registerEpic fn works great. I had been using the .distinct() operator to address the duplicate epic registering issue like so:
export const epic$ = new BehaviorSubject(combineEpics(epic1, epic2)).distinct()
Is this an equally valid/good way of handling the lazy epic registering, and if not, could you please explain why?
- I am using create-react-app which has require.ensure as well as ES6 import (they are different ways of importing right?), and basically, I copy-pasted react-router's huge-apps example which had the
require.ensure
code, but everywhere else, I'm using import statements at the top of the file.
So importing the epics and the registerEpic fn at the top seems to work, while putting the paths inside the require.ensure first argument also seems to work. Does it muddy things up if I use require.ensure AND import statements? If I use require.ensure to load EVERYTHING the route needs, does that mean I remove all import statements inside nested (route-less) components of that route's component?
import { epic3 } from './../../epics/epic3'
import { epic4 } from './../../epics/epic4'
import { registerEpic } from './../../epics/index'
module.exports = {
path: 'my-path',
getComponent( location, done ) {
require.ensure( [], ( require ) => {
registerEpic(addYoutubeEpic)
registerEpic(linkYourSiteEpic)
done( null, require( './components/Edit' ) )
} )
}
}
Regarding registering epics in the 'getComponent' fn for async loading - I thought it was actually desirable to have synchronous loading here, so that the route's component doesn't render until the epic has been registered. What happens if the user tries to do something on the route, like fetch some detailed info or submit a form, that requires the epic to be registered, and the epic isn't registered yet?
Is there any other place outside of react-router module.export declarations that would be appropriate to lazily register epics? Since many routes in my project don't require login, logging in doesn't always trigger a route change. However, there are a bunch of epics that should be registered after a user logs in. Currently, I'm doing it in a really stupid and probably wrong way by setting a token variable in my authReducer, but all it does is call a function that registers the new epics:
'./../reducers/authReducer.js' import { greatEpic } from './../epics/fetchFollowListEpic' import { usefulEpic } from './../epics/fetchMyCollectionsEpic' // and a few more import { registerEpic } from './../epics/index' const addEpics = () => { registerEpic(greatEpic) registerEpic(usefulEpic) ...etc } export default function reducer( state = { loggingIn: false, loggedIn: false, error: '', }, action ) { switch ( action.type ) { case "LOGIN_SEQUENCE_DONE": { return { ...state, aid: setAuthToken(action.payload), loginFailed: false, addEpics: addEpics(), } }
I would imagine that the loginEpic would be a better place to register the epics, instead of the reducer. (I found your login example on github so it might look familiar). Is that correct, or am I totally off base? I know .concat() is synchronous, and I know that redux is synchronous - I'm not sure if registerEpics() is synchronous, but using the reducer to register the epics SEEMS to work okay - does that mean that registerEpics is synchronous? Or does it just mean that things are firing off so quickly that it doesn't matter? Or does it only work due to some essential misunderstanding I have about import statements and how webpack handles code splitting that I have to research more?
./../epics/loginEpic
export const loginEpic = action$ =>
action$.ofType("LOGIN")
.mergeMap(action =>
Observable.fromPromise(axios.post('webapi/login', action.payload))
.flatMap(payload =>
// Concat multiple observables so they fire sequentially
Observable.concat(
// after LOGIN_FULILLED
Observable.of({ type: "LOGIN_FULFILLED", payload: payload.data.aid }),
// ****This is where I think I should be registering the epics right after logging in. "ANOTHER_ACTION" below depends upon one of these epics****
Observable.of({type: "ANOTHER_ACTION", payload: 'webapi/following'}),
Observable.of({type: "LOGIN_SEQUENCE_DONE"}),
)
)
.startWith({ type: "LOGIN_PENDING" })
.takeUntil(action$.ofType("LOGIN_CANCELLED"))
.catch(error => Observable.of({
type: "LOGIN_REJECTED",
payload: error,
error: true
}))
);