redux-observable

Created by Jay Phelps and myself for use on our projects at Netflix, redux-observable is middleware for redux that is inspired by redux-thunk.

redux-observable allows developers to dispatch a function that returns an observable, promise or iterable of action(s). When the observable emits an action, or the promise resolves an action, or the iterable gives an action out, that action is then dispatched as usual.

This provides:

  • The means to use reactive programming and composition to create async effects that dispatch actions to your redux reducer(s).
  • A straight-forward path for canceling those async actions via reactive composition.
  • A more explicit path to cancellation by calling `unsubscribe` on the subscription object returned from the dispatch.
  • A means of composing any actions together as individual streams with complex join operations like `zip` and `combineWith`.

redux-observable is written with RxJS 5 under the hood and as such works with any observable type that implements `Symbol.observable`, such as RxJS, Kefir and (soon) Most. However RxJS 5 is a peer dependency of redux-observable.

NOTE: Some of what is below is outdated, please refer to the documentation on Github

Technical Overview

For a complete example I recommend checking out the example provided in the repository, but to cover basic usage, this is the general idea:

You can dispatch a function that returns any observable:

const test = () => 
Observable.of({ type: ‘TEST’, data: ‘hello world’ });
dispatch(test);

or any promise:

const test = () => 
Promise.resolve({ type: ‘TEST’, data: ‘hello world’ });
dispatch(test);

or any iterable (such as an array or generator):

const manyActions = () => [{ type: 'TEST1' }, { type: 'TEST2' }];
dispatch(manyActions);

When you dispatch a function, it returns a subscription object which can be used to cancel the async action. This allows you to explicitly unsubscribe from the async action at a later time and cancel it.

const startTicking = () => 
Observable.interval(1000)
.map((i) => ({ type: 'TICK', i });
const sub = dispatch(startTicking);

// later you can cancel the dispatched async action
sub.unsubscribe();

However, that might not be the best approach. redux-observable also provides two arguments to the function you pass to `dispatch`. The first argument is an observable stream of all future actions dispatched. This enables the developer to compose a variety of complicated async behaviors together, including cancellation, which can be done via RxJS’s `takeUntil` operator. For ergonomics, the actions observable has an `ofType` operator to allow for cleaner filtering of specific types of actions dispatched.

const startTicking = (actions, store) => 
Observable.interval(1000)
.map((i) => ({ type: 'TICK', i }))
.takeUntil(actions.ofType('STOP_TICK'));
dispatch(startTicking);

// to stop the ticking actions at a later point
dispatch({ type: 'STOP_TICK' });

To be a little more holistic, here is an example of creating an ajax request that can be aborted by dispatching an abort action:

// Just like normal redux, we're now using an action factory
// so that we can create a one-off observable that relies on userId
const fetchUserById = (userId) => (
(actions) => (
Observable.ajax(`/api/users/${userId}`)
.map(
(payload) => ({ type: 'FETCH_USER_FULFILLED', payload })
)
.takeUntil(actions.ofType('FETCH_USER_ABORT'))
.startWith({ type: 'FETCH_USER_PENDING' })
)
);

const subscription = dispatch(fetchUserById(1));

// To cancel the AJAX request you can dispatch an abort action
// from anywhere in your app
dispatch({ type: 'FETCH_USER_ABORT' });

// or if it happens to be more ergonomic, just unsubscribe
// directly. Sometimes you want to abort all of these,
// sometimes just this single one.
subscription.unsubscribe();

Origins

Originally the react-based dataviz apps that Jay and I were working on here at Netflix were using plain React idioms like passing props and `setState` to manage state in our views. While this worked and was reasonably ergonomic, we quickly found ourselves in the weeds as some of our more complicated views and visualizations grew.

We looked into other solutions like redux-thunk and redux-saga, but given that we really love Rx and like to use reactive programming to solve the complicated async problems presented by real-time dataviz apps, we decided to try to create a new middleware that cleanly solved our problem using RxJS 5.

Why Redux? Why not RxJS 5?

Given that “Redux can be written in a few lines of Rx using the scan operator”, why use Redux at all?

The answer to this is pretty simple: Redux has a well defined existing pattern and guidance for use within React. More importantly, Redux has a lot of nice community-driven tooling that we wanted to be able to take advantage of. At the end of the day, it doesn’t matter if the reducers are run via an Rx `scan` or Redux. What matters is the productivity and performance and Redux and Redux’s tooling provides those two things well.

Show your support

Clapping shows how much you appreciated Ben Lesh’s story.