A scalable dataflow technique for your Redux apps

In this article you’ll learn how to setup a scalable, simple and consistent technique for your dataflow in your Redux apps.

 

redux

 

 

So you’ve created some stuff for the interwebs with React and Redux, super great, awesome well done. Maybe you’ve even created an app that has some users: even more awesome.

I’ve been there and after working on a few projects that had over 100k lines of code frontend wise, I found myself struggling with handling data in Redux in a scalable and consistent way.

I’ve tried ducks and numerous of other ways of getting it right. When I landed in a complex e-commerce platform, me and my frontend besties discussed and discussed and came up with a solution: the entities reducer.

The entities reducer

So let’s talk about the entities reducer, see it as a mini database for your frontend app. It’s a place where all the data of your application lives. All queries and modifications will be in one place, so you and your team will know exactly where to look for handling data.

The best way of explaining the entities reducer is by example. Let’s dive right in.

The example we are using will be a chat application, thinking about a chat application what kind of data “entities” do we have:

  • Users
  • Rooms
  • Messages

Now you can imagine where the name “entities” comes from.

There are a few dependencies that are optional, but used throughout all the examples in this article. I highly recommended them for managing your data.

  • Immutable.js – This will give you a clean API for querying your data and can even offer performance benefits
  • Normalizr – Flatten your data structures, we don’t want nested entities. Everything should be flattened.

Let’s start with creating an empty entities reducer.

entities.js

import { fromJS } from 'immutable'

// TODO: you should put this in your `utils`, and make a helper, so you can reuse this because it's awesome
function createReducer (initialState, handlers) {
  return function reducer (state = initialState, action) {
    if (handlers.hasOwnProperty(action.type)) {
      return handlers[action.type](state, action)
    }

    return state
  }
}

// The chat application has: `users`, `rooms` and `messages` as data entities
// We use `fromJS` to make it an immutable object
export const initialState = fromJS({
  users: {},
  rooms: {},
  messages: {}
})

export default createReducer(initialState, {
  // The reducer will go here...
})

 

Now what kind of actions do we have in our mega awesome chat app?

  • Login
  • Logout
  • Create room
  • Join room
  • Send message
  • Receive message

Just imagine these actions working, by using thunk or sagas. It does not matter for this article how they work, just pretend they work and get triggered.

IMPORTANT NOTE: All the actions should have a payload created by normalizr.

So remember: by using this technique, all the data of your application will be in ONE place only. So all actions that do something with your data, should also be handled in this reducer.

First we need to add the following helper function to our fresh new entities reducer. This helper function will help us easily set data on our entities reducer.

entities.js

const mergeEntities = (state, { payload }) => {
  return state.withMutations(state =>
    Object.keys(payload.entities).reduce(
      (_state, entity) => _state.mergeDeepIn([entity], payload.entities[entity]),
      state
    )
  )
}

 

If you take a good look at mergeEntities you can see that it basically handles an action for us, the first argument is the state, and the second destructs the payload from an action.

If you take an even closer look: you can see it accesses payload.entities this comes from using normalizr.