Epic Reactive Programming with Redux-Observable

Tomas Nilsson
Make It New
Published in
7 min readJun 7, 2018

--

Written by Tomas Nilsson and Oscar Axelsson.

Managing state in the world of JavaScript and React is hard, but by using Redux to manage your state, things get a lot easier. Dealing with asynchronous code, however, makes it much more complex. But let’s try to make your life easier by using the power of reactive programming to manage your asynchronous actions.

Photo by Fabian Grohs on Unsplash

Redux - Actions and Reducers

Redux will help you manage data in your application state in a controlled way. We will start off with a quick introduction in case you aren’t familiar with it.

Redux provides predictable state management using actions and reducers. The state can only be changed by dispatching actions to the reducer.

An action is an object that describes what has or should happen, but it doesn’t specify how it should be done. It has a type and may have a payload with additional data. The following example shows an action creator, which is a function that creates and returns an action.

A reducer is a function which decides how the action should change the state. It takes the current state and the action as inputs, and returns a new state. In the following example, isFetching will be changed to true when the action with type FETCH_DATA is sent to the reducer, and this can result in a loader being displayed. The action with type FETCH_DATA_SUCCESS will change isFetching back to false and also add the data from the action payload to the state so that can be shown to the user.

State transitions are handled synchronously by reducers, which means that you have to take care of the data right then and there when the action is dispatched.

What if you want to delay things, or want to change something when you receive a response from a server? How can you manage asynchronous actions and side effects in a good way within a Redux application?

Handling async actions

Redux in itself can not take care of your asynchronous logic, but you can put that logic in middlewares, which are functions that have access to the dispatched actions and potentially may dispatch new actions.

In the example below we will use Redux Thunk, which is a middleware for Redux that allows you to write action creators that return a function instead of an action. This can be used to check a condition before dispatching an action or to delay the dispatch for a certain amount of time.

Thunk allows us to be in control of when and if an action should be dispatched, and works well for many use cases. But for more complex scenarios where we need to have more control, some problems still remain. You can for example not change your mind and cancel things that already have been triggered because promises can’t be cancelled.

But why would you want to cancel a promise?

Let’s look again at the example above where we make a visit to a sandwich shop. You are walking in and order a sandwich but after you have ordered it you regret your decision and would like to cancel the order. But the problem is that the chef can’t just stop preparing your order. When he has received an order, he has to complete it and make the entire sandwich.

Similarly, you would like to cancel a promise triggered by an action that the user regrets. Say for example that the user navigates to a page, which requests some data, but then the user changes her mind and navigates back. Then you still have to handle the response when it eventually arrives even though that component for example may have been unmounted.

So the question then remains, how can we solve a problem like that?

The solution to this can be found within the world of reactive programming. If you have heard of reactive programming before but never understood its place, this will be your aha moment. We will start with a short introduction to reactive programming.

Reactive Programming

Two important terms when it comes to reactive programming are observables and streams. An observable models a stream of events. This could easiest be described by the illustration below. It is a combination of lines and balls. The lines represent the timeline of a stream on which a number of events can occur. These events are represented by the balls.

Observables example (Illustration from the RxJS docs)

Let’s say that we are requesting some data, with every request represented by the balls on the timeline. We want to continue requesting the data until we have received all chunks of data (a-g). Think about the previous described problem where a user navigates to a page, but the user changes her mind and navigates back. We can in this case immediately cancel when the event z occurs even though all the data haven’t been received.

Async code and side effects with Redux-Observable

Redux-Observable is a middleware for Redux which handles cancellation and many other asynchronous side effects by using reactive programming. Complex scenarios such as other actions having an impact on how a pending network request should be handled, can be solved quite easily with Redux-Observable.

RxJS and Most.js are two libraries for reactive programming with which you can handle streams of actions in different ways. RxJS is the more popular of the two, but Redux-Observable can also be used with Most.js, which is both smaller and faster. In the following examples, Most.js will be used.

So how does Redux-Observable combine reactive programming with Redux?

Epic - Actions in, actions out

The concept of an epic is the core part of Redux-Observable. An epic is a function that takes a stream of actions and returns a stream of actions. All of the dispatched actions go into the action stream. You can look at the action type to decide if you want to do something. An action comes in, and new actions may be dispatched. Actions in - actions out.

Let’s have a look at a simple epic from the Redux-Observable documentation:

We have the stream of actions as input. If we receive an action of type PING, we want to output an action of type PONG. To show an easy example of what you can do with reactive programming we have also added a delay of 1000 ms before we dispatch the PONG action.

So how does this work?

The flow for Redux and Redux-Observable (Illustration: Tomas Nilsson)

An action with the type PING is being dispatched. It reaches the Redux store where we have the reducer function, which takes the current state and the action as input and outputs a new state. In this case, when we received the PING action, the reducer will change the state so isPinging will be set to true. Then the action will reach our epic, which after one second will output the PONG action. That action is sent to the reducer, which changes isPinging back to false.

Redux-Observable shines in complex scenarios

The previous example was very simple. To really show the benefits of Redux-Observable we have to make it a bit more complex.

Let’s say that you periodically want to poll a service to check the current status, which could be either IN_PROGRESS, COMPLETE, or ERROR.

The following statusCheckEpic takes the action stream as input and filters on the action type POLL_SERVICE_STATUS. If that action is dispatched, we want to do a request to the service status endpoint. The checkServiceStatus() function will do the request and returns a promise, and an Observable stream will be created in the epic from this promise.

If the promise fulfills, the stream will contain the response. If the received status is IN_PROGRESS, we don’t want to do anything else than continue waiting and periodically ask for the status. We can therefore filter out these responses in the epic. If the response instead has the status COMPLETE, we will use the serviceComplete() action creator, which will create an action with type SERVICE_COMPLETE that will be dispatched and may update the state to show the status to the user in the application.

If the status is something else, an action with type SERVICE_ERROR will be dispatched instead. The same action will be dispatched if an error occurs, but for that we need to use recoverWith to recover from the stream failure and create a new Observable stream.

Repeat the request periodically

If you test the code above and get the IN_PROGRESS status back, nothing more will happen. That’s not what we want! We want to continue asking for the status with a certain interval. It’s easy to fix with Redux-Observable.

By wrapping all our code with most.periodic(1000), we will repeat everything every 1000 ms, and hence get periodic polling. We will also add take(1) at the end, which means that as soon as we get one action as output from the inner flatMap (either SERVICE_COMPLETEor SERVICE_ERROR), we will stop the periodic polling.

Cancel the periodic polling

What if the user navigates back to the previous page? How can we then make sure that the periodic network requests to the service get stopped? That would be very difficult using other alternatives, but with Redux-Observable and Most.js it is as simple as adding one extra line of code:

.takeUntil(action$.filter(action => action.type === NAVIGATE_BACK))

This means that the periodic polling will continue until we get an action of type NAVIGATE_BACK (or until we get SERVICE_COMPLETE or SERVICE_ERROR as before).

The complete epic with both periodic polling and cancellation will then look like this:

Redux-Observable makes async epic

As these examples have shown, you can add complex asynchronous logic quite easily with Redux-Observable and still maintain a readable code structure. You can combine different streams of actions, you can transform them, change the timing by adding delays, handle cancellation, etc. Although there is a bit of a learning curve, you are now one step closer and when you get it, you basically only have to add a few extra lines of code to get a lot of extra functionality.

Redux-Observable makes async epic!

--

--