Redux observable

In the previous post we looked at one way to handle application side effects, such as asynchronous loading of data using Redux sagas.

There’s absolutely nothing wrong with redux saga’s but there’s a few alternatives. The alternative we’re going to look at is redux-observable.

So what’s the big difference between redux-sagas and redux-observables? I’ve not run any performance or effeciency testing against the two so I’m going to solely comment on their usages. Saga’s use generator functions whilst Epic’s (the term used within redux-observable as something analogous to Saga in redux-saga) uses, well you guessed it, Observables, i.e. rxjs.

Instead of yield put etc. in a Saga, we return a Observable.create and call next to pass data to the redux store. I’ve been asked “which should I choose?” by other developers and there really isn’t a clear reason to choose one over the other (if I get chance to try to check performance etc. I may amend this post).

I would say, if you’re already including rxjs in your application or you have a good understanding of rxjs, then redux-observables will probably be the best choice. If you’ve not really got an understanding of rxjs or don’t wish to bring in a dependency on rxjs, then stick to sagas.

I could (and probably will) write a post on using rxjs, but to summarise – rxjs (Reactive Extensions) came originally from .NET and offered a push style paradigm for development along with better concurrency capabilities and composability in a declarative manner. Whilst rxjs is not an exact copy (i.e. it uses a different way to compose observable data) it does offer similar capabilities. When abused, Observables can be hard to understand, but the powerful nature of the functionality/operators you get makes them far more powerful than saga’s – but then again if you have a good library of functions you can implement similar functionality to rxjs in sagas.

Okay, enough talk, let’s write code. I’m going to layout things just like the redux-saga post (and yes, even copy and paste some text) to give a sort of comparison of writing the two.

Assuming you have a React application created, we need run the following

  • yarn add react-redux
  • yarn add redux-observable

To create a simple demo, we’ll change the App.js file to

import React from 'react';
import store from './store';
import { Fetch } from './rootReducer';

function App() {

  function handleDoFetch() {
    store.dispatch({type: Fetch});
  }

  return (
    <div className="App">
      <button onClick={handleDoFetch}>Do Fetch</button>
    </div>
  );
}

export default App;

So this will simply dispatch an action which will ultimately be handled by our observable. Before this happens let’s create a redux store and set up the redux observable middleware, here’s my store.js file

import { createStore, applyMiddleware, combineReducers } from "redux";
import { createEpicMiddleware } from 'redux-observable';
import rootReducer from "./rootReducer";
import rootEpic from "./rootEpic";

export const epicMiddleware = createEpicMiddleware();

const store = applyMiddleware(epicMiddleware)(createStore)(
  combineReducers({
    rootReducer,
  })
);

epicMiddleware.run(rootEpic);

export default store;

We don’t need to combineReducers as there’s only one, but it’s there for an example of setting up multiple reducers. Let’s now create a very basic reducer named rootReducer.js

export const Fetch = "FETCH";
export const FetchEnded = "FETCH_ENDED";

export default (state = {}, action) => {
  switch (action.type) {
    case FetchEnded:
      console.log("Fetch Ended");
      return {
        ...state,
        data: "Fetch Ended"
      }
    default:
      break;
  }   
  return state;
}

Notice we’ve got two actions exported, Fetch and FetchEnded but there’s nothing handling Fetch in this case. This is because redux middleware will in essence pass this through to the redux-observable we’re about to create. We could also handle Fetch here and still handle it also within the epic, the point being the epic (via the observable and ofType) is going to handle this action when it see’s it.

Now we’ve got everything in place, let’s put the final piece in place, the epic will be stored in rootEpic.js and here it is

import { Fetch, FetchEnded } from "./rootReducer";
import { Observable } from "rxjs/internal/Observable";
import { mergeMap } from "rxjs/operators";
import { ofType } from "redux-observable";

function rootEpic(
    action$,
    _state$,
    _dependencies) {
  
    return action$.pipe(
      ofType(Fetch),
      mergeMap(action => { 
        return Observable.create(o => {
            console.log("fetchData")
            o.next({ type: FetchEnded });  
        })
    }));
}

export default rootEpic;

Notice that the rootEpic function returns an Observable via Observable.create and it uses next to inform any subscribers (in this case the middleware) to changes of state. Obviously this example is stupidly simple in that it just dispatches FetchEnded to the subscriber(s).

It might be the observable calls next many time for different values but in this example we’ve kept things simple. Running the application will display a button and using the browser’s dev tools, when the button is pressed the Fetch action is detected by the epic and the returned pipe, which itself uses the observable which then dispatches a FetchEnded action, which is handled by the reducer.

As stated, our example is very simple but in a real world scenario this function could be acting as a websocket client and for every value returned would placed into the next function until cancelled or maybe an error occurred.

Another thing to be aware of is that whilst the rootEpic pipe will be created once (in our case when added to the middleware for example) the pip is called for each action through redux and hence we must filter the actions we want to handle using ofType and even actions dispatched via the observable will come through this epic.