Angular to React | Logo
 

Make Sure Your Redux Actions have all Expected Properties and Types

Part 2 of 2

Part 1: A Redux Action Assertion Class that can be Extended to Validate All of Your Redux or Flux Actions

As I mentioned in Part 1, this article was inspired by this post from Stack Overflow. I also talked about some of the reasons why you might want to assert or validate your Redux actions. This just happens to be my opinionated approach to solving that problem.

This second part of the article is more technical than philosophical. I will walk through the code and talk about each part.

redux-action-validator : What is it?

redux-action-validator is an extensible class used to validate Redux/Flux action properties and ensure consistency across large projects.

Installation

npm install --save redux-action-validator

Usage

First, import the ReduxAction class at the top of your file.

import ReduxAction from 'redux-action-validator'

Define Your Custom Action Types that extend ReduxAction

Create a new class that extends ReduxAction

class MyAction extends ReduxAction {
  constructor(o) {
    super(MyAction.TYPE, {
      id:         ReduxAction.NUMBER,
      myList:     ReduxAction.ARRAY,
      mySample:   ReduxAction.STRING,
      myMap:      ReduxAction.OBJECT,
      myTest:     ReduxAction.BOOLEAN
    }, o)
  }
}
MyAction.TYPE = 'MY_ACTION'

Let’s break it down.

MyAction is the name of the Redux/Flux action you want to create and validate.

MY_ACTION is the internal string constant you want to associate with MyAction. This will become MyAction.type. Remeber that these constants need to be unique throughout your entire Redux application namespace.

id, myList, mySample, myMap, myTest are all properties that you want to force each MyAction instance to contain. These are mapped to type constants. redux-action-validator contains the following property type constants:

  • ReduxAction.NUMBER
  • ReduxAction.ARRAY
  • ReduxAction.STRING
  • ReduxAction.OBJECT
  • ReduxAction.BOOLEAN

If you don’t see a constant for the type you’re trying to validate, don’t worry! You can also use a string. The code will be looking for the result of typeof someProp to validate it.

Notice the o variable that’s part of the constructor. This is the raw Action object that will get passed to the constructor of MyAction. Let’s take a closer look at how you’ll use that:

Inside your Action Creator

Every Redux action must return a standard javascript object with at least a type property. Without redux-action-validator it would look like this:

function myActionCreator(id) {
  return {
    type: 'MY_ACTION',
    id: id
  }
}

With redux-action-validator you end up with:

function myActionCreator(id) {
  return new MyAction({ id: id }).toObject()
}

The benefit is that you’ve already established a list of expected properties and types in the MyAction class definition. Under the hood, redux-action-validator will test all of the properties of your action to make sure you’re following the contract you decided on up front. If not, it will throw an Error with a detailed description of why it failed:

Error: Invalid Action Property for Type [MY_ACTION]: [id] expected to be type [number] but saw [array] instead.

This works if you pass the wrong value type of an action property. And it also works if you fail to include a specific property.

Error: Invalid Action Property for Type [MY_ACTION]: [id] expected to be type [number] but saw [undefined] instead.

Inside your Reducer

switch (action.type) {
  case type(MyAction.TYPE):
    const myAction = new MyAction(action).toObject()
}

Here, you have a convenient static class value TYPE to use with switch (or whatever method you use). Then, inside the case block you just need to pass the raw action from Redux into the constructor of your custom MyAction class. If any of the action properties are missing or of the wrong type, it will throw an error. This should help keep your Redux/Flux actions structured in a consistent and predictable way. This is especially useful if you’re working on a large project with multiple contributors.

A Closer Look at the Validator

When you setup your new Action Type class that extends ReduxAction, you pass in a map of property names and their expected values. Inside the isValid static method of the ReduxAction class, we’ll just loop through the keys in the map and compare the types listed there to the type of the value of the same key in the action object. It looks like this:

static isValid(action, propMap) {
  for (const key in propMap) {
    const expectedType = propMap[key]
    const actualType = (Array.isArray(action[key])) ? ReduxAction.ARRAY : typeof action[key]
    if (actualType !== expectedType) {
      return { result: false, key, expectedType, actualType }
    }
  }
  return { result: true }
}

The only trick here is the typeof someArray is object, not array. So, when we check for actualType we’ll use Array.isArray() to test if action[key] is an array. If not, then we’ll use the return value of typeof action[key] as the value for actualType.

To summarize:

  1. Create all of your Action Types in a top-level file. Be sure to import the ReduxAction class and have all of your custom action types extend that base class.
  2. Import these Action Type definitions into your Redux Action Creators and Reducers
  3. Have each creator return an object from the toObject() method of your custom Action class
  4. Have each reducer use the TYPE constant of your custom Action class in the switch statement
  5. Create an instance of your custom action type by passing the raw action object to the constructor of your action type class.

At any point in this process, if an invalid action comes through an exception will be thrown.

If you ideas, suggestions or bug reports feel free to create an Issue or Pull Request on this repo. You can get redux-action-validator directly from Github or use npm to install it using the steps outlined above.

Are Building something cool that just begging for it's own domain? You can register your own for as low as $0.88/year. Claim your name before squatting domain pirates grab it up and hold it for ransom.
Interested in Writing for Us?

Website developed by Michael Martin  (michael@angulartoreact.com
© 2015-16, All Rights Reserved