How to do a simple notification system with redux-observable?

observable, redux, redux-observable, rxjs

Solution

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.

Problem

I'm trying to do a simple notification system with redux-observable. I'm new to `rxjs` so I'm having a hard time doing it. What I'm trying to do is: - Dispatch an intent to display a notification - Detect the intent with an `Epic` - Dispatch the action that inserts the new notification - Wait 3 seconds - Dispatch another action that deletes the old notification This is my Epic: ``` import { NOTIFICATION_DISPLAY_REQUESTED } from '../actions/actionTypes'; import { displayNotification, hideNotification } from '../actions/notifications'; export const requestNotificationEpic = (action$, store) => action$.ofType(NOTIFICATION_DISPLAY_REQUESTED) .mapTo(displayNotification(action$.notification)) .delay(3000) .mapTo(hideNotification(action$.notification)); ``` What really happens is that `NOTIFICATION_DISPLAY_REQUESTED` is dispatched, and 3 seconds later, `hideNotification` is dispatched. `displayNotification` never happens. I could just dispatch `displayNotification` from the view, delay 3 seconds and then dispatch `hideNotification`. But later I want to delete the last notification before adding a new one if there are more than 3 active notifications. That's why I dispatch `displayNotification` manually from inside the epic in this simple case. So, how do I achieve this? Sorry if this is super simple question, I'm just new to all this and need some help. Note: I know `redux-saga` exists, is just that `redux-obsevable` made more sense to me.

Original source

Related problems