Action Streams and Redux

How redux-observable Handles Complex Data Flow

Markus Coetzee
6 min readJan 5, 2017

A couple of months ago I experimented with using RxJS for unidirectional data flow. The focus was on showcasing how action streams could be used to form state streams via plain old reducer functions, and how we could then use those state streams to push new state to our components. I was happy with the discussion it created on Twitter, where the likes of Ben Lesh and André Staltz took part. What stood out to me from that exchange, was that RxJS didn’t necessarily have to replace Redux, but that they could work together. The ducks could swim in the stream… *cough*

Meanwhile, at that stage Mr. Lesh and Jay Phelps had already been working undercover on making action streams the main approach for redux-observable (as opposed to their original thunk-servable style). It was welcome news when they unveiled it as action streams is the workhorse when composing unidirectional data flow with RxJS. At the time of writing we’ve been using it in production for 5 months and we are really happy with the results.

This post is about showcasing how RxJS through redux-observable handles more complex data flow in comparison to an imperative approach. The following are some semi-contrived examples.

Note: Error handling has been left out to simplify things

E.g. A list of items that should reload after certain points

Let’s say we’ve got a list of items where each item can have a number of operations performed on it incl. UPDATE_ITEM, DELETE_ITEM and PUBLISH_ITEM. When we have successfully performed an operation on a list item, we want to reload the list. With a typical imperative setup, we would have to dispatch a LOAD_ITEMS action on the successful response of each operation. Here is how this code might look like redux-thunk style:

With this code it’s not immediately obvious how this list of items behaves. Basically we have to search for all the instances where we dispatch the LOAD_ITEMS action in order to build up our mental model. This adds to the programmer’s cognitive load.

Furthermore, with this code each operation is also responsible for reloading the list — i.e., we’re mixing concerns, which I don’t think is inherently wrong, but in this case it can easily result in spaghetti code if the screen’s complexity increases. I think many of us have dealt with screens like this, a maintenance headache that adds that special spice to our days.

Now let’s take a look at how we could write the previous code with redux-observable. If you’re in need of an intro I recommend giving Mr. Phelp’s talk on redux-observable a watch: https://www.youtube.com/watch?v=AslncyG8whg The shortest intro I can give is that an epic (the functions below), receives a stream of actions, and returns a stream of actions. Actions in, actions out.

Side notes:

  1. Epics have been named according to what actions they spit out
  2. The implementation details of the update, delete and publish epics have been left out, because they’re no longer relevant

With this code the question of ‘How does this list of items behave’ becomes easy to answer, because it has been explicitly declared and we no longer have to search the code to build up our mental model. We have separated our concerns and I think it’s uncontroversial to say that we have reduced the cognitive load (assuming you’re familiar with epics).

Furthermore, because we can separate our concerns this way, we can avoid spaghetti code if the screen’s complexity increases (each side effect can be declared separately).

At this point you might be wondering -

Yeah thats pretty cool, but the tradeoff is that you loose the easy answer of ‘What side effects does operation X have?’.

* where operation X is one of update / delete / publish

I’ve got two things to say about this:

  1. For me, the question of how does the primary data that I draw my screen with react over time is the more important question. It gives you the ability to quickly grasp how your screen behaves, which should improve the screen’s maintainability
  2. This is where your unit tests come in handy, because your test can answer this question. Eg. you can test UPDATE_ITEM_RESPONSE should have a LOAD_ITEMS side effect etc. So if you need to answer that question, have a look at your tests

E.g. A list item operation that should timeout

Let’s say we have that same list of items, each with its own PUBLISH_ITEM action. In some cases this operation might take way longer than expected, in which case we want to timeout after 15s, hide the busy publishing indicator and rather display a message showing it’s taking longer than expected. To do this imperatively, I think you’ll have to do something like this (probably buggy code):

This code seems fine, but we did introduce state management to the function, which has resulted in some control flow. These things tend to add to the programmer’s cognitive load.

Now let’s again have a look at how this code might look like with redux-observable:

With this code we were able to produce this timeout behaviour without introducing state management and without the need for explicit control flow. Not having to manage those sorts of things I think will improve the maintainability of the code.

Also, and I guess it’s because I’ve become used to it, but this epic seems easier to follow in comparison to the imperative solution. Brian Lonsdorf’s talk on dot chaining comes to mind, which is a great talk b.t.w. https://www.youtube.com/watch?v=SfWR3dKnFIo

E.g. Integrating with an older flux implementation

Let’s say we’re working on an app that has been around for a while that still has a number of modules using an older implementation of flux. If we need to imperatively integrate with the data flow of an older module, you would likely need to manually subscribe to it and dispatch a new Redux action when it triggers. Which means you’ll need the Redux store handy to dispatch and this is where I’m actually not sure what a decent imperative solution might look like, so…

Let’s rather have a look at how an integration with an implementation like Reflux might look like with redux-observable:

RxJS has no problem integrating with event emitters because it is sort of like the lodash for events. This makes integrating with Reflux painless and our reducers can now act as if the action originated in Redux, setting us up for a smooth transition the day that older module gets Reduxified.

Conclusion

With all of that said, if you are writing an application with a simpler data flow, then I think something like redux-thunk will work just fine. It’s when things get more complicated when redux-observable really shines and I hope that the above examples have shown a bit of that. In reality we’ve only scratched the surface — a tweet of Mr. Phelps comes to mind:

--

--

Markus Coetzee

Software developer working with ReactJS and Ruby on Rails (worked a lot with Java in the past).