React PropTypes validation when using normalizr
Working with JSON API responses that have nested data leads to not so simple queries and manipulation to distribute it to React components. Here is an example of a JSON response that represents the areas and their pickup stores, that are later displayed separately in the application.
[{
"id": "CABA",
"area": "Ciudad Autónoma de Buenos Aires",
"stores": [{
"name": "ABASTO",
"abbreviatedName": "AB"
}, {
"name": "MICRO-CENTRO",
"abbreviatedName": "MC"
}, {
"name": "CABALLITO",
"abbreviatedName": "CB"
}, {
"name": "BELGRANO",
"abbreviatedName": "BE"
}]
}, {
"id": "BSAS",
"area": "Provincia de Buenos Aires",
"stores": [{
"name": "RAMOS MEJIA",
"abbreviatedName": "RM"
}, {
"name": "LANUS",
"abbreviatedName": "LN"
}, {
"name": "LA PLATA",
"abbreviatedName": "LP"
}, {
"name": "VICENTE LOPEZ",
"abbreviatedName": "VL"
}]
}, {
"id": "Interior",
"area": "Interior del País",
"stores": [{
"name": "ROSARIO",
"abbreviatedName": "RO"
}, {
"name": "CORDOBA",
"abbreviatedName": "CO"
}]
}]Using this response as it is in a React component, its props validation has a PropTypes.arrayOf validation combined with the shape of the area and an array of stores. Inside of this component in order to get the full list of pickup stores making a map reduce through all the areas is needed in order to get those lists separate.
import React, { PropTypes } from 'react';export default function Pickups(props) {
const pickupStores = areas
.map(area => area.stores)
.reduce((result, areas) => result.concat(areas));
// Component implementation
}Pickups.propTypes = {
areas: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string,
name: PropTypes.string,
stores: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string,
abbreviatedName: PropTypes.string
}))
}))
}Finding a better way with normalizr
Browsing a bit some React and Redux best practices, there’s a better way to transform this data structure with some help of a library called normalizr originally made by Redux’s creator Dan Abramov.
Configuring schemas is very simple, and there are even some options to select which is the id field in the entity schema.
import { arrayOf, normalize, Schema } from 'normalizr';const
store = new Schema('stores', { idAttribute: 'abbreviatedName' }),
area = new Schema('areas');
area.define({ stores: arrayOf(store) });const pickupsNormalized = normalize(pickups, arrayOf(area));
So the data structure flattened ended up like this:
{
"entities": {
"areas": {
"CABA": {
"id": "CABA",
"area": "Ciudad Autónoma de Buenos Aires",
"stores": ["AB", "MC", "CB", "BE"]
},
"BSAS": {
"id": "BSAS",
"area": "Provincia de Buenos Aires",
"stores": ["RM", "LN", "LP", "VL"]
},
"Interior": {
"id": "Interior",
"area": "Interior del País",
"stores": ["RO", "CO"]
}
},
"stores": {
"AB": {
"name": "ABASTO",
"abbreviatedName": "AB"
},
"MC": {
"name": "MICRO-CENTRO",
"abbreviatedName": "MC"
},
"CB": {
"name": "CABALLITO",
"abbreviatedName": "CB"
},
"BE": {
"name": "BELGRANO",
"abbreviatedName": "BE"
},
"RM": {
"name": "RAMOS MEJIA",
"abbreviatedName": "RM"
},
"LN": {
"name": "LANUS",
"abbreviatedName": "LN"
},
"LP": {
"name": "LA PLATA",
"abbreviatedName": "LP"
},
"VL": {
"name": "VICENTE LOPEZ",
"abbreviatedName": "VL"
},
"RO": {
"name": "ROSARIO",
"abbreviatedName": "RO"
},
"CO": {
"name": "CORDOBA",
"abbreviatedName": "CO"
}
}
},
"result": ["CABA", "BSAS", "Interior"]
}Now a change in the PropTypes validation is needed and it’s a bit tricky, since now in the areas and stores entities there are objects that have attribute names that are variable ids, but the shape of them remains constant.
Getting back the PropTypes validation
One way set the validation would be directly to specify areas and stores as PropTypes.object, but this doesn’t make a big deal as validation… So here comes PropTypes.objectOf to the rescue and the shape of the entities can be specified again.
import React, { PropTypes } from 'react';export default function Pickups(props) {
// Component implementation
}Pickups.propTypes = {
areaNames: PropTypes.arrayOf(PropTypes.string),
areas: PropTypes.objectOf(PropTypes.shape({
id: PropTypes.string,
name: PropTypes.string,
stores: PropTypes.arrayOf(PropTypes.string)
})),
stores: PropTypes.objectOf(PropTypes.shape({
name: PropTypes.string,
abbreviatedName: PropTypes.string
}))
}