Tech Thoughts
technical

How To Manage Side Effects in Redux?

In this article, you'll learn more about dealing with side effects in Redux and how DataCamp integrates side effects in an isomorphic app, created to do server-side rendering.

At DataCamp, we always try to provide the best user experience. To reach this goal, we've decided to rewrite the frontend website from Angular 1.x to Redux with React. We had multiple reasons for making this choice, but the main one was supporting server-side rendering. Server-side rendering will mainly improve the UX because the page will load faster and the user can immediately see the result of the request without having to wait for the javascript bundle to load.

If you want to know more about server-side rendering and how it works, you can find a good explanation on the Redux website.

This article will focus on dealing with side effects in Redux, and explains how we integrate side effects in the isomorphic app we've created to do server-side rendering.

Side Effects in Redux

There are several tools to handle side effects in Redux. Most of the time, you should only include side effects in your middleware. In general, it's a good pattern to encapsulate all side effects as much as you can. The middleware is great for that purpose because it's only a function that will be triggered every time an action is dispatched. It can be fired before the action hits the reducer or after the reducer is hit, i.e. when the state has already been updated.

Most of the tools to handle side effects come down to helpers to create pure functions and use it in a middleware, so your side effects are isolated from the code. You can use a simple approach like redux-thunk written by Dan Abramov, the author of Redux, or some more complex and powerful libraries like redux-saga.

We decided to jump into something new, a library that has gained lots of popularity, redux-observable. This library allows you to use RxJS in your middleware in a very easy way. Since all of us at DataCamp love ReactiveX, we adopted it right away! The only prerequisite is to know rxjs 5!

Now every time we dispatch a new action, after it hits the reducer, our middleware will be fired and some side effects can take place. Here's a simple example of a middleware which takes an action as input and outputs multiple actions (which will again hit the reducer).

(action$, store) => action$.ofType('GET_TIMER').switch(
  Observable.interval(1000).take(5).map(nb => ({ type: 'SHOW_TIME', nb })));

More specifically, it takes an action of type GET_TIMER as input and outputs multiple actions of type SHOW_TIME with a number nb (from 0 to 4 included). Now, what happens if another action of type GET_TIMER is dispatched before the outputs of all the previous SHOW_TIME actions are finished? Because of the switch, the previous observable will be cancelled and the new one will start.

This example shows how easy it is to deal with complex asynchronous calls.

HTTP Requests and Side Effects

Now that we all understand redux-observable, we would like to make calls to our RESTful API. Most of the side effects we have to include in our web app are calls to get or post data to a server and give real-time feedback to users.

We couldn't find an easy and lightweight library to do HTTP requests in an observable stream, so we created our own HTTP wrapper on top of superagent and rxjs 5. We called it "universal-rx-request". The library works on client-side and server-side and is available on npm.

Let's go over a toy example to get your IP address from api.ipify.org:

import rxRequest from 'universal-rx-request';

rxRequest.importRxExtensions(); // will add new operators.

const getMyIp = rxRequest({ method: 'get', url: 'https://api.ipify.org?format=json' })

export default (action$, store) => action$.ofType('SHOW_MY_IP')
    .switchMap(action => getMyIp.mapToAction(action));
};

This file will export an Epic function which can be plugged into the redux-observable middleware.

Suppose a button click causes an action of type SHOW_MY_IP to be dispatched. This action will hit the middleware we've created in the code. This middleware will make the HTTP request using the universal-rx-request library and gets the IP of the user. mapToAction is a special operator from the library which outputs two actions. The first action says that the request is fetching and the second action contains the actual result of the request. This allows us to, for example, put a spinner on the button when the data is loading and subsequently show the IP once the data has arrived.

As a bonus, if the user clicks on the button to get his IP multiple times, the result of only one response will be received. All the others will be cancelled because of the switchMap function.

Let's see how we can handle the different state in the reducer with our Epic function.

import rxRequest from 'universal-rx-request';

const STATUS = rxRequest.STATUS;
const getStatus = rxRequest.getStatus;

export default (state = {}, action = {}) => {
  switch (action.type) {
    case getStatus('SHOW_MY_IP', STATUS.SUCCESS):
      return { ip: action.data.body.ip };
    case getStatus('SHOW_MY_IP', STATUS.FETCHING):
      return { fetching: true };
    case getStatus('SHOW_MY_IP', STATUS.ERROR):
      return { error: true };
    default:
      return state;
  }
}

That's it! All cases are handled in the reducer. Now you can inform the user what's happening, for example show an error message if an error occurred or a spinner during the loading.

Want to learn more? Check out universal-rx-request on GitHub to see it into action with other examples.

Side Effects and Server-Side Rendering

One of the main issues we've encountered while dealing with server-side rendering was the question of asynchronous calls. Since the server has to render a page synchronously to send it to the user, what happens with asynchronous calls?

In the end we solved this issue by sticking to one important rule. All side effects needed when the app is loaded on the client side happen in the middleware. This setup allows us to import the middleware only on the client javascript bundle but not on the server side, which in turn means that the server just has to render the components as Strings (renderToString) and never has to deal with asynchronous calls and side effects.

In the end, if we need to fetch asynchronous data on the server side, we do it in independently, outside of the middlewares and redux. We fetch the data beforehand and give the result as a preloaded state on the creation of the store (see example here).