Building the UI -- pure-functionality and side-effects with redux

As I ended the last post with a bit of an unsatisfying state, this post illustrates my quest to find the right framework in the world of redux.

Redux, which evolved from the ideas of Facebook's flux, features an immutable state atom (a simple object), which is "modified by" reducers which take the current state and an action and produce a new state atom. This can then be fed to a virtual-dom renderer to produce the user interface. (If this is too short of an explanation for you check out its docs! I'll explain some of the more important ideas as I go, though.)

What's nice about this model of state, actions and pure-functional reducers is the ability to time-travel, to replay user interaction and the promise of never coding yourself into a corner as every state in your app will be in redux and every state transition will be an action. Additionally, it allows for extensibility, since you can easily register multiple reducers or middleware (as you'll see below) that listen for the same action, but do different things.

What about side-effects, though? Impurities arise e.g. when loading stuff from a remote server, from localStorage or from cookies. These are not part of the formula state, action => new state.

I looked at a few libraries that allow the modelling of side-effects like talking to a server API and the like. They all integrate with redux as middleware. Redux middleware is executed after dispatching an action and before invoking reducers.

Side-effects inside action creators

redux-promise

I found redux-promise, the idea of which is to put your side-effects in your action creators. For example you have the following (simplified and wrong) code:

el.addEventListener('click', function(evt) {  
  var user = evt.currentTarget.getAttribute('id')
    , action = action_showFriendList(user)
  store.dispatch(action)
})

action_showFriendList is an action creator. These usually return an action object describing the changes to the state atom, which are then executed by the reducers. In this example however, we want to invoke a side-effect: we want to fetch the list of friends from the server. Thus redux-promise tells us to return a promise from our action creator. It will recognize the promise and wait for its result, feeding it to the reducer. Thus you we have our side-effects inside our action creators.

action_fetchFriendList would then look like this:

function action_showFriendList(id) {  
  return new Promise(function(resolve, reject) {
    request('/user/'+id+'/friends', function(er, res) {
      if(er) return reject(er)
      resolve({
        type: 'SHOW_FRIENDLIST'
      , payload: {
          friends: res
         , user: id
      })
    })
  })
}

redux-thunk

The "standard" way to do side-efects, according to the redux docs, is redux-thunk. With thunk middleware applied, our above example would look like this:

function action_showFriendList(id) {  
  return function(dispatch, getState) {
    dispatch({type: 'FETCH_FRIENDLIST, pending: true, payload: {user: id}})
    return new Promise(function(resolve, reject) {
      request('/user/'+id+'/friends', function(er, res) {
        if(er) {
          dispatch({type: 'FETCH_FRIENDLIST', error: true})
          return resolve()
        }
        dispatch({
          type: 'FETCH_FRIENDLIST'
        , error: false
        , payload: {
            friends: res.body
           , user: id
        })
        resolve()
      })
    })
  })
}

Things arguably get more complex, it seems. What's important to understand about this concept is that we could have just done this outside of redux's realms, dispatching actions now and then to update the user interface. Actions are not essential here, but are just side notes, short updates to keep the state updated.

My opinion about the above approaches is that they give action creators way to much power, when they were actually conceived to be as dumb as possible. Additionally, you have no control over your side effects as they're basically hidden from the redux stack, banned into action creators.

Side-effects as middleware responing to actions

redux-gen

I also found redux-gen which features action creators as generator functions which yield actions that are then dispatched:

function action_showFriendList*(id) {  
  var list = yield {type: 'FETCH_FRIENDLIST', payload: {user: id}}
  yield {
    type: 'SHOW_FRIENDLIST'
  , payload: {
      friends: list
    , user: id
  }
}

This requires a fetch middleware to be present in the redux middleware stack, that handles the fetching and returns a promise that resolves to the result. An nice side-effect (no pun intended) of this approach is that you are forced to have all side-effect actions "on record".

Additionally, this approach allows interception of actions without any API buy-in, e.g. you could write a caching middleware that intercepts FETCH actions and returns the last cached result.

redux-effects and declarative promises

A utility belt that provides useful side-effects middleware is redux-effects, which works splendidly with redux-gen, although it advocates the use of so-called declarative promises, which are basically action trees that tell the middleware which action to dispatch when the one before was successful (or unsuccessful): Instead of an opaque generator that holds the agenda, the agenda is explicitly passed along to every side-effect action:

function action_showFriendList(id) {  
  return {type: 'EFFECT_COMPOSE'
  , payload: {type: 'FETCH_FRIENDLIST', payload: {user: id}}
  , meta: {
      steps: [
        // On success
        [list => ({
          type: 'SHOW_FRIENDLIST'
        , payload: {
            friends: list
          , user: id
          }
        },
        // on error
        er => ({
          type: 'SHOW_FRIENDLIST'
        , payload: er
        , error: true
        }]
      ]
}

One advantage this action tree has over generators is that you get a concrete and thus manipulable representation of the action stack. For example, you could add middleware that does something else when the friend list has been fetched (you can do that with generators as well), you could add middleware that adds a filter for the friend list (possible with redux-gen, too) or even replaces the "on success" action above with your own.
An advantage of generators is that they're less cumbersome to write.

Update: redux-saga

So, I ended up using redux-gen, because it's simpler than declarative promises. However, I always felt that requiring side-effects middleware to directly return a promise to be able to react to it in your action creators is both a rough edge and a one-way street, because you can't react to it in your reducers and you have to deal with two types of values now, actions and promises.

Since I wrote the original version of this article, another alternative has emerged that I find very attractive: redux-saga. Instead of requiring you to blindly wait for promises you can wait for any action to be dispatched. Side-effects are activated when the action is dispatched and dispatch another action when they are done -- it's a two-way street now. However, this allows for much more: You can also wait for an action that is not a side effect and thus beautifully model the flow of your application. You could e.g. load a document, then dispatch an action that eventually triggers an options dialog, then wait for a user to choose an editor, then load the editor, etc. all in one succinctly defined generator function. Check out their examples!

So, to wrap this up, I'm probably going to rewrite hive-ui to use redux-saga, which I'm sure will be a joy. And, as a bit of foreshaddowing, wouldn't this kind of thing also be useful on the server?

Stay tuned and happy collaborating!