Flux to Redux: Converting Stores to Reducers

In the previous post we converted Flux actions to Redux action creators.

The next part will discuss converting stores into reducers. Stores are more complicated, but they should contain similar logic in both Flux stores and Rexux reducers. In genearl, stores with significant logic will be broken into several reducers.

Convert Stores to Reducers

The implementation for your stores will likely greatly vary from the one below. The example below uses the raw dispatcher from Flux and uses the standard Node.js EventEmitter.

In my stores, I create an initialize function that sets the default state and registers the store with the dispatcher so that the store can receive actions.

Additional boilerplate includes the register and unregister methods that are used by consumers to attach to the store's change events.

There are also state mutation functions that get called by the actions. These are responsible for changing the internal state of our store.

Finally, there are state selector functions, which will return pieces of state from the store.

import { EventEmitter } from 'events';  
import dispatcher from '../dispatcher');  
import { USERS_LOADED } from '../constants/action-types';

let store = Object.assign({}, EventEmitter.prototype, {

  initialize: function() {
    this.users = [];
    this.paging = { page: 1, limit: 24, total: 0 };

    dispatcher.register((action) => {
      switch(action.type) {
        case USERS_LOADED:
          this.onUserLoadSuccess(action);
          break;
      }
    });
  },

  register: function(callback) {
    this.on('change', callback);
  },

  unregister: function(callback) {
    this.removeListener('change', callback);
  },

  getUsers: function() {
    return this.users;
  },

  getPaging: function() {
      return this.paging;
  },

  onUserLoadSuccess: function({ users, paging }) {
    this.paging = paging;
    this.users = users;
    this.emit('change');
  }

});

store.initialize();  
export default store;  

The above example is a typical object store found in my Flux applications. You'll notice that the only real logic in there is in the onUserLoadSuccess method. This method accepts an object, the action, as a parameter and destructures the users and paging datafrom the action. These values are then used to update the state of the store before emitting the change event.

To convert this into reducers, we'll actually break it into two reducers to manage the individual pieces of state (users array and paging object) seperately.

import { combineReducers } from 'react-redux';  
import { USERS_LOADED } from 'admin/constants/action-types';

const usersReducer = (state = [], action) => {  
  switch(action.type) {
    case USERS_LOADED:
      return action.users.slice();
    default:
      return state;
  }
};

const pagingReducer = (state = { page: 1, limit: 24, total: 0}, action) => {  
  switch(action.type) {
    case USERS_LOADED:
      return {...action.paging};
    default:
      return state;
  }
};

export default combineReducers({  
  users: usersReducer,
  paging: pagingReducer
});

The above reducers are straight forward. The first reducer, usersReducer manages the state for the array of user objects. By default it returns an empty array. When the USERS_LOADED action type is executed, it will creates a slice of the action's users array and return that slice as the new array. This ensures that the array is a copy of the array and not the array included in the action. This important to maintaining immutability of your reducers. This would also be a good time to switch to a library like Immutable.js.

The second reducer that is created is the pagingReducer. This manages the state of the paging object. The reason that multiple reducers are r created is to make the mutation logic simpler. If this was a more complex example, the code inside your reducer to mutate state in an immutable way can become cumbersome when your state object is too deep. The goal is to keep our reducers as simple as possible. In this case, we simply create a copy of the paging object supplied by the action and use that as our new state.

Finally, the export is the result of calling combineReducers with both of our stores. This function will create a single reducer function with the combined state of both reducers. The users state will be stored in the users property and the paging state will be in the paging property.

comments powered by Disqus