How to perform side-effect inside of an epic in redux-observable?
Asked Answered
V

1

10

In redux-observable, epics are accepting stream of actions and returning back new stream of actions. In my use-case I need to send analytics event after some action was dispatched and do nothing after.

With redux-saga, I can just listen that action using takeEvery, and perform a side-effect inside of a saga function:

function* saga() {
  yield takeEvery('SOME_ACTION', function*() {
    sendAnalytics();
  })
}

But how can I achieve the same with redux-observable? There is pretty much side effects, that are not requiring dispatching new actions, like initializing plugins, logging, setting cookies etc...

If it's an anti-pattern for both of these libraries, what solution should be used for these kinds of effects?

Vancevancleave answered 9/8, 2017 at 10:33 Comment(2)
I haven't used either redux-saga or redux-observable, but I've done this particular side effect of tracking by just calling the analytics module inside of mapDispatchToProps, which is a legitimate place to do this in my opinion. So before or after you call dispatch you just do something like googleAnalytics.trackEvent(...).Meagher
Thanks, but in my opinion, mapDispatchToProps functions is not the right place to perform side-effectsVancevancleave
A
22

Definitely not an anti-pattern outright to have an epic which is "read-only"--but I want to caution that it's often a sign a person is doing something less-than-idiomatic, but this is not one of those cases.

There are likely many ways of accomplishing this in Rx. Here are two:

do + ignoreElements

I think this is the most clear of what it does. do is only ever used to perform a side effect (often logging) for next/error/complete without affecting the stream. We then use ignoreElements to prevent the SOME_ACTION from being dispatched again (which would cause infinite recursion). See the rxjs docs for more info on these operators

const someEpic = action$ =>
  action$.ofType('SOME_ACTION')
    .do(() => sendAnalytics())
    .ignoreElements();

Anonymous Observable

This is a somewhat more "lightweight" solution since it doesn't use any operators other than ofType. I would caution against using this one though until you and your teammates have a solid grasp of RxJS and particularly creating custom/anonymous Observables. i.e. don't write code you don't understand.

const someEpic = action$ =>
  new Observable(() =>
    action$.ofType('SOME_ACTION')
      .subscribe(() => sendAnalytics()) // subscription is returned so it can be cleaned up
  );

This use case would be a great addition to the Recipes section of the docs, if someone ever has the time to PR it.


Btw, this is mostly an RxJS question where the stream just happens to be actions. You might find you get answers and better support long-term if you search or phrase your questions in the context of RxJS instead of redux-observable. Nearly all redux-observable questions are actually RxJS questions, which is great cause the knowledge is transferable!

Accouchement answered 9/8, 2017 at 23:53 Comment(3)
I'd just like to say, I really appreciate the thought and effort you put into clearly and completely answering redux-observable questions, @jayphelps! I not only learn redux-observable, but also valuable RxJS tips. So, thanks!Sneer
Why do you use ignoreElements instead of NEVER? Which one is better?Layette
@Layette never() is a static Observable factory--it returns a source Observable that never emits, never completes, never errors. ignoreElements() on the other hand is an operator--meaning it operates on another source observable rather than being a static producer itself--that when applied to an Observable will simply ignore any values that source Observable emits. So not the same :) ignoreElements() will NOT ignore errors/complete.Accouchement

© 2022 - 2024 — McMap. All rights reserved.