Handling Redux Side-Effects — the RxJS way


Hello stranger ! If you are working with Redux you are probably walked into the same issue we had at Orfium FrontEnd team with redux side effects (maybe that’s why you are reading this as well ). If not, then wait for it, it will happen soon. Let me guide you through our story and findings.

Side-Effects and why we need them

Regular actions are being dispatched and as a result some state is being changed.

Imagine now that you have a list that you need to fetch information from a server to fill it. In that case you would need to dispatch an action — request from the server — on response (success/error) dispatch an other action to update the state (loading, results etc). This right there, is a side-effect.

Right now though you have an action that you can’t actually handle. You can trigger the same action if the user clicks on that button and have multiple requests and most importantly multiple state updates that can ruin your perfect application. What if you could cancel, wait, debounce and generally handle those actions. Spoiler alert, you can!

There are two major options to handle side effects actions with the above advantages (thunks are not included):

  • redux observable — based on RxJS when it uses observables to listen for actions
  • redux saga — it uses newly added javascript generators to handle side effects

RX side effects

Using redux observable you can change the way you operate. I will not go into details on how you can install it as you can easily read this here. I will go a bit deeper to the complicated parts of it like

  • how to form an epic
  • redux forms (how to handle them)
  • testing with state
  • testing actions with ease

Let’s imagine we have a simple scenario. A request to the server that on success or on fail we will dispatch some actions to update our reducer.

Let’s see how such case would be in code.

Let me break it down a bit.

In the above example we define a new epic. Then using ofType we are waiting the action with type ACTION_REQUEST to get fired. When that is triggered we go to switchMap that here we limit the request that we are getting. Switch map run one request per time so anyone else will be ignored, this way you avoid multiple requests. Then with from we are transforming the promise request to an observable. Lastly we are using mergeMap to fetch the response (imagine a .then on a promise) and we return the set of actions and we do the same with the Error.

What if you want to first show a loading to indicate the loading/fetching of the data?

Now you can easily add an action before requesting data.

We are using concat that will run the two actions in it (loadingList and then the request) in the order defined. Getting easier?

Now you have all that ready and you are using redux forms to handle your data. How you will know when you press submit in that beautiful form of yours that is getting submitted status to show that loading animation? Well, the idea remains the same. Let see how something like this can be accomplished.

Here we change a bit the logic. Again we are waiting for the same action on the ofType section. At this example, we will want the formName to be passed to the Epic in order to do the actions shown above. In the switchMap we are expecting two things, a payload and a meta! Oh yes, a meta. Meta hold the logic that payload doesn’t have to know about. You can see here more details on why and when to use it. So again using concat and the redux form action startSubmit we can define that our form started submitting something. Don’t forget to stopSubmit on both success and error. That’s it!

I would suggest that you would always have a request action dispatched with that name like the examples ACTION_REQUEST or FETCH_BLAH_LIST_REQUEST. This way you would always know that this is an epic based action for side effects.

You can now use takeUntil and stop listening to any success event after that event occur. This helps with the classic Netflix problem. When you are navigating to a details page start fetching you are going back and to another details page and the first page start resolving and messing your current page.

This problem is well explained here from Jay Phelps and I ‘d recommend to take a look.

Now we have the same example as before but we only put the takeUntiloperator. Now if this listens for the PAGE_CHANGED action it will not cancel the request but it will ignore any resolving of the current request. Yay !! Now if you want you can implement a cancelable request with axios, fetch or anything.

Testing on RxJS with ease

I found the redux-observable tutorials a bit advanced and confusing on testing. I can see why but in most of the cases you would not need such thing so i will propose a simple solution for testing.

Redux observable gives us from the modules two things ActionsObservable and StateObservable. You can use them to create observables for testing. We are using those because on the library the actions that are passing through the epics are created with those actions so because we will try to compare those two they need to be exactly the same.

Let’s try to test our first example.

Here we define two things an action$ that would be an observable with the action that triggers the epic and the expected variable that would be the array of actions that we expect that the epic will return. Furthermore we mock an axios (fetch, axios whatever you have for http requests) resolve with a mocked response. Lastly and this is where it gets interesting we pass that action$ to the epic we want to test, we transform the results of that epic to an array with .pipe(toArray()) and then we make it a promise with .toPromise() so we can await for it to finish with async await. Now we can easily compare what epic returned to what we are expecting !

Here is what a test on the error of that request should look like:

With this technique, you can easily test any epic and fix the expectations of each.

Hope you liked my examples and that redux observables make a bit more sense with those examples for a day to day use.

If you have more examples on how you use it please share on comments below.


Panagiotis Vourtsis

Head of Front End Engineering @ ORFIUM

https://www.linkedin.com/in/panvourtsis

https://github.com/panvourtsis