Netflix JavaScript Talks - RxJS + Redux + React = Amazing!

By: Netflix UI Engineering

2286   24   152938

Uploaded on 09/16/2016

Jay Phelps (@_jayphelps ) talks about why Netflix loves reactive programming with Rx. In this talk he shares the basics of RxJS 5 Observables and how they can be used with React and Redux to manage asynchronous effects using redux-observable.

http://redux-observable.js.org

Slides: http://www.slideshare.net/jayphelps/rxjs-redux-react-amazing

Comments (6):

By nathan_f77    2017-09-20

Oh, that's a shame. I've spent the last few hours reading up on RxJS, and I just finished watching this talk on redux-observable [1], so I'm totally ready to dive in and rewrite some parts of my app. I was actually about to rewrite it with redux-saga, but I've decided that redux-observable and RxJS is far more awesome.

So are you saying that there are no flow types for redux-observable? I guess that's not a deal-breaker, since flow-typed will just generate stubs. Also, it looks like react-observable is a super high level abstraction, so I'm not even going to be writing that much code.

[1] https://www.youtube.com/watch?v=AslncyG8whg&feature=youtu.be

Original Thread

By anonymous    2017-09-20

Netflix make extensive use of the observable pattern both client and server side - anything you try to do with their events needs to take account of that.

In particular events become 'collections in time' that they then apply collection functions (such as map) across.

This design pattern doesn't leave event handlers lying around (it's one of its big advantages) so you won't be able to hold on to them, you'll need to hook in to the core observers or add your own.

This means using Rx methods, rather than indexes, as the index will constantly change as new events happen and old ones are discarded.

Something like this:

netflix.cadmium.UiEvents.events.resize.subscribe(
    // This will repeat every time resize happens
    r => r.scope.events.dragend.subscribe(
        // This will repeat every time dragend happens
        d => d.handler(null, {value: 123, pointerEventData: {playing: true}})
    )
);

Netflix have been quite open about using Rx, and I strongly recommend that you watch their videos (they have a whole YouTube channel for their UI) if you're planning to build anything on top of it.

Original Thread

By anonymous    2017-09-20

If I got it right, you want your epic to produce the following sequence of actions in response to each SUBMIT_LOGIN:

GENERATE_DEVICE_ID -- RESOLVE_LOGIN -- LOAD_ABOUT

Also, I guess that GENERATE_DEVICE_ID needs to be issued immediately after SUBMIT_LOGIN is received, while RESOLVE_LOGIN and LOAD_ABOUT should be issued only after a stream returned by login() emits.

If my guess is correct, then you just need to start the nested observable (the one created per each SUBMIT_LOGIN) with GENERATE_DEVICE_ID action and startWith operator does exactly that:

const submitLoginEpic = action$ =>
    action$.ofType(SUBMIT_LOGIN)
        .mergeMap(({ payload }) =>
            login(payload.email, payload.password)
                .mergeMap(({ response }) => Rx.Observable.of(resolveLogin(response.content), loadAbout()))
                .startWith(generateDeviceId(uuidv1()))
        );

Update: one possible alternative could be to use concat operator: obs1.concat(obs2) subscribes to the obs2 only when obs1 has completed.

Note also that if login() needs to be called after GENERATE_DEVICE_ID has been dispatched, you might want to wrap it in a "cold" observable:

const login$ = payload =>
    Rx.Observable.create(observer => {
        return login(payload.email, payload.password).subscribe(observer);
    });

const submitLoginEpic = action$ =>
    action$.ofType(SUBMIT_LOGIN)
        .mergeMap(({ payload }) =>
            Rx.Observable.of(generateDeviceId(uuidv1()))
                .concat(login$(payload).map(({ response }) => resolveLogin(response.content)))
                .concat(Rx.Observable.of(loadAbout()))
        );

This way GENERATE_DEVICE_ID is emitted before login() is called, i.e. the sequence would be

GENERATE_DEVICE_ID -- login() -- RESOLVE_LOGIN -- LOAD_ABOUT

Update 2: The reason why login() works not as expected is because it depends on an external state (const state = getCurrentState()) which is different at the points in time when login() is called and when an observable returned by login() is subscribed to. AjaxRequest captures the state at the point when login() is called, which happens before GENERATE_DEVICE_ID is dispatched to the store. At that point no network request is performed yet, but ajax observable is already configured based on a wrong state.

To see what happens, let's simplify the things a bit and rewrite the epic this way:

const createInnerObservable = submitLoginAction => {
    return Observable.of(generateDeviceId()).concat(login());
}

const submitLoginEpic = action$ =>
    action$.ofType(SUBMIT_LOGIN).mergeMap(createInnerObservable);

When SUBMIT_LOGIN action arrives, mergeMap() first calls createInnerObservable() function. The function needs to create a new observable and to do that it has to call generateDeviceId() and login() functions. When login() is called, the state is still old as at this point the inner observable has not been created and thus there was no chance for GENERATE_DEVICE_ID to be dispatched. Because of that login() returns an ajax observable configured with an old data and it becomes a part of the resulting inner observable. As soon as createInnerObservable() returns, mergeMap() subscribes to the returned inner observable and it starts to emit values. GENERATE_DEVICE_ID comes first, gets dispatched to the store and the state gets changed. After that, ajax observable (which is now a part of the inner observable) is subscribed to and performs a network request. But the new state has no effect on that as ajax observable has already been initialized with an old data.

Wrapping login into an Observable.create postpones the call until an observable returned by Observable.create is subscribed to, and at that point the state is already up-to-date.

An alternative to that could be introducing an extra epic which would react to GENERATE_DEVICE_ID action (or a different one, whichever suits your domain) and send a login request, e.g.:

const submitLogin = payload => ({ type: "SUBMIT_LOGIN", payload });

// SUBMIT_LOGIN_REQUESTED is what used to be called SUBMIT_LOGIN
const submitLoginRequestedEpic = action$ =>
    action$.ofType(SUBMIT_LOGIN_REQUESTED)
        .mergeMap(({ payload }) => Rx.Observable.of(
            generateDeviceId(uuidv1()),
            submitLogin(payload))
        );

const submitLoginEpic = (action$, store) =>
    action$.ofType(SUBMIT_LOGIN)
        .mergeMap(({ payload }) => {
            // explicitly pass all the data required to login
            const { token, deviceId } = store.getState().user;
            return login(payload.email, payload.password, token, deviceId)
                .map(({ response }) => resolveLogin(response.content))
                .concat(loadAbout());
        });

Learning Resources

As redux-observable is based on RxJS, it makes sense to get comfortable with Rx first.

I highly recommend watching "You will learn RxJS" talk by André Staltz. It should give an intuition of what observables are and how they work under the hood.

André has also authored these remarkable lessons on egghead:

Also Jay Phelps has given a brilliant talk on redux-observable, it definitely worth watching.

Original Thread

By anonymous    2018-01-14

I would recommend using something like RxJS + Redux Observables which provides you with cancellable observables.

This solution requires a little bit of learning, but I believe it's a much more elegant way to handle asynchronous action dispatching versus redux-thunk which is only a partial solution to the problem.

I suggest watching Jay Phelps introduction video which may help you understand better the solution I'm about to propose.

A redux-observable epic enables you to dispatch actions to your store while using RxJS Observable functionalities. As you can see below the .takeUntil() operator lets you piggyback onto the ajax observable and stop it if elsewhere in your application the action MY_STOPPING_ACTION is dispatched which could be for instance a route change action that was dispatched by react-router-redux for example:

import { Observable } from 'rxjs';

const GET_OBJECTS = 'GET_OBJECTS';
const GET_OBJECTS_SUCCESS = 'GET_OBJECTS_SUCCESS';
const GET_OBJECTS_ERROR = 'GET_OBJECTS_ERROR';
const MY_STOPPING_ACTION = 'MY_STOPPING_ACTION';

function getObjects(id) {
  return {
    type: GET_OBJECTS,
    id,
  };
}

function getObjectsSuccess(data) {
  return {
    type: GET_OBJECTS_SUCCESS,
    data,
  };
}

function getObjectsError(error) {
  return {
    type: GET_OBJECTS_ERROR,
    data,
  };
}

const getObjectsEpic = (action$, store) = action$
  .ofType(GET_OBJECTS)
  .switchMap(action => Observable.ajax({
      url: `http://example.com?id=${action.id}`,
    })
    .map(response => getObjectsSuccess(response))
    .catch(error => getObjectsError(error))         
    .takeUntil(MY_STOPPING_ACTION)
  );

Original Thread

Popular Videos 308440

cotter548

Submit Your Video

If you have some great dev videos to share, please fill out this form.