If you're new to RxJS, this isn't so simple :)
Couple things up front:
Operator chains
An Epic is a function which takes a stream of actions and returns a stream of actions. Actions in, actions out. The functions you chain to transform matching actions are called operators. Chaining operators is a lot like chaining garden hoses or power cords--the values flow from one to the other. It's also very similar to just chaining regular functions like third(second(first()))
except that Observables have an additional dimension of time, so the operators are applied on each value that flows through them.
So if you say stream.mapTo(x).mapTo(y)
the fact that you firsted mapped to x
is made meaningless when you .mapTo(y)
since mapTo
ignores the source's values and instead just maps it to the one provided.
If instead you used map
, it might become more apparant:
stream.map(value => 'a message').map(message => message + '!!!')
Just to be claer, this chaining of operators stuff is RxJS, not specific to redux-observable, which is more a pattern of using idiomatic RxJS with a tiny amount of glue into redux.
action$
is an Observable (technically ActionsObservable)
The argument action$
is an Observable of actions, not an actual action itself. So action$.notification
will be undefined
. That's one of the reasons people commonly use the dollar sign suffix, to denote it is a stream of those things.
Consider only have 2 actions, not 3
Your example shows you using three actions NOTIFICATION_DISPLAY_REQUESTED
and two others to show and hide the notifications. In this case, the original intent action is basically the same as displayNotification()
because it would be dispatched synchronously after the other.
Consider only have two actions, one for "show this notification" and another for "hide this notification". While this isn't a rule, it can often simplify your code and increase performance since your reducers don't have to run twice.
This is what it would look like in your case (name things however you'd like, of course):
export const displayNotificationEpic = (action$, store) =>
action$.ofType(DISPLAY_NOTIFICATION)
.delay(3000)
.map(action => hideNotification(action.notification));
// UI code kicks it off some how...
store.dispatch(displayNotification('hello world'));
Your reducers would then receive DISPLAY_NOTIFICATION
and then 3 seconds later HIDE_NOTIFICATION
(order whatever).
Also, cruicial to remember rom the redux-observable docs:
REMEMBER: Epics run alongside the normal Redux dispatch channel, after the reducers have already received them. When you map an action to another one, you are not preventing the original action from reaching the reducers; that action has already been through them!
Solution
Although I suggest using only two actions in this case (see above), I do want to directly answer your question! Since RxJS is a very flexible library there are many ways of accomplishing what you're asking for.
Here a couple:
One epic, using concat
The concat
operator is used subscribe to all the provided Observables one at a time, moving onto the next one only when the current one completes. It "drains" each Observable one at a time.
If we wanted to create a stream that emits one action, waits 3000 ms then emits a different one, you could do this:
Observable.of(displayNotification(action.notification))
.concat(
Observable.of(hideNotification(action.notification))
.delay(3000)
)
Or this:
Observable.concat(
Observable.of(displayNotification(action.notification)),
Observable.of(hideNotification(action.notification))
.delay(3000)
)
In this case, they have the exact same effect. The key is that we are applying the delay
to different Observable than the first--because we only want to delay the second action. We isolate them.
To use inside your epic, you'll need a merging strategy operator like mergeMap
, switchMap
, etc. These are very important to learn well as they're used very often in RxJS.
export const requestNotificationEpic = (action$, store) =>
action$.ofType(NOTIFICATION_DISPLAY_REQUESTED)
.mergeMap(action =>
Observable.concat(
Observable.of(displayNotification(action.notification)),
Observable.of(hideNotification(action.notification))
.delay(3000)
)
);
Two different epics
Another way of doing this would be to create two different epics. One is responsible for maping the first second to the second, the other for waiting 3 seconds before hiding.
export const requestNotificationEpic = (action$, store) =>
action$.ofType(NOTIFICATION_DISPLAY_REQUESTED)
.map(action => displayNotification(action.notification));
export const displayNotificationEpic = (action$, store) =>
action$.ofType(DISPLAY_NOTIFICATION)
.delay(3000)
.map(action => hideNotification(action.notification));
This works because epics can match against all actions, even ones that other epics have emitted! This allows clean separation, composition, and testing.
This example (to me) better demonstrates that having two intent actions is unneccesary for this example, but there may be requirements you didn't provide that justify it.
If this was very confusing, I would recommend diving deep into RxJS first. Tutorials, videos, workshops, etc. This is only skimming the surface, it gets much much deeper, but the payout is great for most people who stick with it.