Ben
Ben

Reputation: 16534

asynchronously add epic to middleware in redux-observable

I'm trying to evaluate redux-observable. Just looking through the doc and I'm trying to get the async epic loading thing going. I created a fork of the jsbin from the docs which basically attempts to add the async usage of the BehaviorSubject stuff.

http://jsbin.com/bazoqemiqu/edit?html,js,output

In that 'PING PONG' example, I added an 'OTHER' action and then use BehaviorSubject.next (as described in the docs) to add that epic. However, when I run the example, what happens is that the PING action is fired, followed by an endless stream of 'OTHER' actions, but never the PONG action. To see this, I added the reduxLogger. View it in the dev tools console as the jsbin console doesn't render it correctly.

My question is what am I doing wrong? Why does the PONG action never get dispatched?

Upvotes: 0

Views: 1210

Answers (1)

jayphelps
jayphelps

Reputation: 15401

Your otherEpic is an infinite "loop" (over time)

const otherEpic$ = action$ => 
  action$
    .delay(1000)
    .mapTo({ type: OTHER });

This epic has the behavior "when any action at all is received, wait 1000ms and then emit another action of type OTHER". And since the actions your Epics emit go through the normal store.dispatch cycle like any other action, that means after the first PING is received, it will emit an OTHER after 1000ms, which will then be recursively received by the same epic again, wait another 1000ms and emit another OTHER, repeat forever.

I'm not sure if this was known, but wanted to point it out.

You next() into the BehaviorSubject of epic$ before your rootEpic has started running/been subscribed to it.

BehaviorSubjects will keep the last value emitted and provide that immediately when someone subscribes. Since your rootEpic has not yet been called and subscribed to the by the middleware, you're replacing the initial value, so only the otherEpic is emitted and ran through the epic$.mergeMap stuff.

In a real application with async/bundle splitting, when you would call epic$.next(newEpic) should always be after the middleware has subscribed to your rootEpic and received the initial epic you provided to your BehaviorSubject.

Here's a demo of that working: http://jsbin.com/zaniviz/edit?js,output

const epic$ = new BehaviorSubject(combineEpics(epic1, epic2, ...etc));
const rootEpic = (action$, store) =>
  epic$.mergeMap(epic => {console.log(epic)
    return epic(action$, store)
  });

const otherEpic = action$ =>
  action$.ofType(PONG)
    .delay(1000)
    .mapTo({ type: OTHER });

const epicMiddleware = createEpicMiddleware(rootEpic);
const store = createStore(rootReducer,
  applyMiddleware(loggerMiddleware, epicMiddleware)
);

// any time AFTER the epicMiddleware
// has received the rootEpic
epic$.next(otherEpic);

The documentation says "sometime later" in the example, which I now see isn't clear enough. I'll try and clarify this further.


You may also find this other question on async loading of Epics useful if you're using react-router with Webpack's require.enquire() splitting.

Let me know if I can clarify any of these further 🖖

Upvotes: 4

Related Questions