Using normalizr.js in a Redux store

Recently we moved to using more React in our app, but as the app grew we realised that passing data around via Props was not a scaleable way to structure your app. It was a pain to maintain and adding extra functionality or refactoring was even harder. We therefore decided to incorporate Redux into app and we’ve spent the last couple of sprints refactoring our code and so I thought I would share some of our learnings. The first of which was how to structure our data in our Redux store.

The Problem

Our app is an e-commerce app so has the concept of Orders and Products. Linking these together are Line Items. A Line Item links a Product to an Order and is used to store how many of each Product is in a customers Order.

In order to make sure our app is performant we wanted to make sure no entities were duplicated in memory while the app is running. In our previous Angular app, we had duplicated products in memory in a similar way to this:

// BAD - notice how the 'Carrots' object is duplicated.
{
order: {
id: 10,
line_items: [
{
id: 483,
quantity: 2,
product: {
id: 924,
name: 'Carrots',
price: '1.50',
}
}
]
},
products: [
{
id: 924,
name: 'Carrots',
price: '1.50'
}
]
}

But how should we do this using Redux and what should the reducers look like?

The solution

After coming across this great article we realised that normalizr.js would be a great fit for what we wanted. Essentially, normalizr takes a deeply nested javascript object (like the Order above) and flattens it out. Checkout the Github repo for more details about exactly how it works.

To get normalizr to work we had to create the schemas for the different entities we wanted to store in our Redux store.

// schemas.js
import { Schema, arrayOf } from 'normalizr';
const orderSchema     = Schema('orders');
const lineItemSchema = Schema('lineItems');
const productSchema = Schema('products');
// An Order has an array of line items
orderSchema.define({
lineItems: arrayOf(lineItemSchema)
});
// A Line Item has one product attached
lineItems.define({
product: productSchema
});
export { orderSchema, lineItemSchema, productSchema };

We then need to set up a simple action in order to deserialize our order correctly into the redux store:

// shop_actions.js
module.exports = {
deserializeOrder: (order) => {
return {
type: 'DESERIALIZE_ORDER',
order: order
}
}
};

Once you’ve got the schema and the actions, the reducer is surprisingly simple:

// shop_reducer.js
import { normalize } from 'normalizr';
import { orderSchema } from './schemas';
// We use seamless-immutable but thats for another time.
import Immutable from 'seamless-immutable';
const defaultState = Immutable({
order: [],
product: [],
lineItem: []
});
export default function shopReducer(state = defaultState, action) {
switch (action.type) {
case 'DESERIALIZE_ORDER':
// This is the magic part - the normalize method will flatten
// my deeply nested order according to my schemas defined
// above.
var normalizedOrder = normalize(action, {
order: orderSchema
});
      // Due to using seamless-immutable we have to merge the new
// entities into the state.
return state.merge(normalizedOrder.entities);
default:
return state;
}
}

And we can now easily test that the actions and reducers working together:

import reducer from './path/to/reducer';
import actions from './path/to/actions';
const fakeOrder = {
id: 10,
lineItems: [
{
id: 483,
quantity: 2,
product: {
id: 924,
name: 'Carrots',
price: 1.50
}
}
]
};
describe('shopReducer', () => {
describe('DESERIALIZE_ORDER', () => {
let state;
    beforeEach(() => {
state = reducer(
undefined,
actions.deserializeOrder(fakeOrder)
);
});

it('should deserialize the order correctly', () => {
expect(state.orders[10]).toEqual({
id: 10,
lineItems: [ 483]
});
});

it('should deserialize the lineItems correctly', () => {
expect(state.lineItems[483]).toEqual({
id: 483,
quantity: 2,
product: 924
});
});

it('should desialize the product correctly', () => {
expect(state.products[924]).toEqual({
id: 924,
name: 'Carrots',
price: 1.50
});
});
});
});

So now if you passed in the order from the start of the article to be deserialised, the redux store will look like:

{  
orders: {
10: {
id: 10,
line_items: [ 483 ]
}
},
lineItems: {
483: {
id: 483,
quantity: 2,
product: 924
}
},
products: {
924: {
id: 924,
name: 'Carrots',
price: 1.50
}
}
}

The result of all this is a javascript store that acts like a very simple database. Now as long as we know the id of the entity we want to update, we can easily find it within the store. For example, with this new store if we wanted to update the quantity of a line item we no longer need to know the id of the order it is related to.

This is all great, but what if we wanted to reconstruct an order out of the store? Ours solution was to create some helper classes. While the example below is fairly simple, these classes help perform some more complicated methods.

export default class orderHelper {
constructor(store) {
this.store = store;
// We store the current order id on the store too
this.currentOrder = store.orders[store.currentOrderId];
}
  currentLineItems() {
return this.currentOrder.lineItems.map(lineItemId =>
return (this.store.lineItems[liId])
);
}
}

Let say we wanted to make a component that shows the total number of items in your order you could set it up like:

import React       from 'react';
import { connect } from 'react-redux';
import OrderHelper from './path/to/orderHelper';
// First of all we create the React Component
class OrderCount extends React.Component {
static propTypes = {
lineItems: React.PropTypes.array.isRequired,
}
  totalQuantity() {
return this.props.lineItems.reduce((total, lineItem) => {
return (total + lineItem.quantity);
}, 0);
}
  render() {
// We're using the jsx format
return (
<div>{ this.totalQuantity() }</div>
}
}
// We now bind the component to the redux store
export default connect((state) => {
const helper = new OrderHelper(state);
return {
lineItems: helper.currentLineItems(),
}
})(OrderCount);

If some other component updated any of the line items that are connected to this store, this component will update.

So far we have found that using this format to be very simple to use. Our reducers only care about how to structure data in the store and are very easy to test as they are vanilla JS and don’t interact with the DOM or anything. We could change up React for some other framework and still use the same reducers and actions.

Conclusion

Moving to using Redux has made us think differently about how to structure our app. The seperation of the Redux (and its reducers and actions) from the rest of the app gives us the confidence that what we’re building is going to be around for a while, even if React is not. Unit testing what we have buit has been a breeze and using normalizr.js has improved how we structure our data.

While this project is still a work in progress, what we have done so far has been working great.

Show your support

Clapping shows how much you appreciated Myles Cowper-Coles’s story.