Using PropTypes for Redux State Validation

When using Redux with React, one gets the idea if it is possible to use React’s PropTypes for state validation. Why should one want to do such thing?

First of all, such an explicit definition of shape of some state is a great reference and documentation. For example imagine simple application for managing browser bookmarks:

const stateShape = {
  data: PropTypes.shape({
    bookmarks: PropTypes.arrayOf(PropTypes.shape({
      title: PropTypes.string.isRequired,
      url: PropTypes.string.isRequired,
      labels: PropTypes.arrayOf(PropTypes.string).isRequired
    })).isRequired,
    fetching: PropTypes.bool.isRequired, // data are currently requested from a server
    error: PropTypes.string // null or error string
  }).isRequired,
}

Now we have nice and very explicit documentation of the state. And because we are using it to validate the state we know that it really conforms the state. I mean, documentation in a form of comments or some web page is nice but soon or later it might get out of sync with the real code.

And, of course, the second, obvious benefit of validation is that we will catch many possible errors much sooner.

It was obvious that I was not the first one to have such idea. And yes indeed, a little bit of googling found the Type-checking Redux State with propTypes (without React) article.

The solution presented in the article was nearly exactly what I wanted. However it used the validation in a form of an high order reducer and I wanted to have it in a form of a Redux middleware.

So for the given example we can define such validator as a:

stateValidator.js

import PropTypes from 'prop-types'


const stateShape = {
  data: PropTypes.shape({
    bookmarks: PropTypes.arrayOf(PropTypes.shape({
      title: PropTypes.string.isRequired,
      url: PropTypes.string.isRequired,
      labels: PropTypes.arrayOf(PropTypes.string).isRequired
    })).isRequired,
    fetching: PropTypes.bool.isRequired, // data are currently requested from a server
    error: PropTypes.string // null or error string
  }).isRequired,
}


const checkShape = (state, actionDescription) => {
  PropTypes.checkPropTypes(
    stateShape,
    state,
    "Redux state validation",
    actionDescription
  )
}


const validateStateMiddleware = store => next => action   ​=> {
  checkShape(store.getState(), `Before action: ${action.type}`)
  const result = next(action)
  checkShape(store.getState(), `After action: ${action.type}`)
  return result
}
export default validateStateMiddleware

And use it e.g. like this:

store.js

import { createStore, applyMiddleware } from "redux"
import { createLogger } from 'redux-logger'
import thunkMiddleware from 'redux-thunk'

import applicationReducer from "./reducer"
import { debug } from "../config"
import validateStateMiddleware from "./stateValidator"


const middleware = [ thunkMiddleware ]

if (debug.validateState) {
  middleware.push(validateStateMiddleware)
}

if (debug.reduxActionLogging) {
  const loggerMiddleware = createLogger()
  middleware.push(loggerMiddleware)
}

const store = createStore(
  applicationReducer,
  applyMiddleware(...middleware)
)
export default store

Now, when we run the application and some reducer creates invalid state we will see Warning in the browser console looking like this:


To view or add a comment, sign in

More articles by Michal Molhanec

Explore content categories