redux-create-action-types

Easily create immutable, strict, and well formed action types

Downloads in past

Stats

StarsIssuesVersionUpdatedCreatedSize
redux-create-action-types
1542.1.07 years ago7 years agoMinified + gzip package size for redux-create-action-types in KB

Readme

redux-create-action-types

Helps you create Redux action types, safely & easily
Build Status

Motivation

You write Redux boilerplate every-single-day and this sucks.
export FETCHING_RESOURCE_FRAMES = 'FETCHING_RESOURCE_FRAMES';
export FAILED_FETCHING_RESOURCE_FRAMES = 'FAILED_FETCHING_RESOURCE_FRAMES';
export FETCHED_RESOURCE_FRAMES = 'FETCHED_RESOURCE_FRAMES';
export FETCHING_RESOURCE_IMAGE = 'FETCHING_RESOURCE_IMAGE';
export FAILED_FETCHING_RESOURCE_IMAGE = 'FAILED_FETCHING_RESOURCE_IMAGE';
export FETCHED_RESOURCE_IMAGE = 'FETCHED_RESOURCE_IMAGE';

One of the interesting design decisions around Redux, to me, was that it used plain strings as action type identifiers. This decision is not strictly imposed, but it does offer good serialization/deserialization, visual clues, and works very well inside switch statements which are a defining trait of Redux reducers.
The downsides of strings are that they are completely free-form and not validated in any way besides your unit tests. Nothing guarantees your types will be unique (except for you and your well coordinated team), and this could introduce very strange behavior in your reducers when two actions are dispatched with the same type, but different action payloads. Lastly, they are very tedious to write out. You typically want to export them with the same name they represent, which incurs twice the typing. For instance: `export const MYACTIONTYPE = 'MYACTIONTYPE'`
There are many common solutions to some of these problems, which I'll outline below, but no solution (that I'm aware of) that provides the beneficial niche features:

Symbols

Symbols are string-like objects in JavaScript that are guaranteed unique, work well with switch statements, are easy to write, and can serialize to plain text strings. They seem like the perfect & ideal match for this use case. While they break down with the same limitations of strings in terms of verbosity, they really break down when it comes to Redux middleware (logger needs a special serializer) and with being able to playback your actions from a saved session (no way to deserialize back into the correct Symbol).

keyMirror

A Node module that helps with the tediousness of defining mirrored key/value pairs on an object. It works really well, but suffers from not throwing during development when you access non-existent keys.

What this module does for you

  • Provides a very clear way of defining multiple action types

  • In development, will throw when accessing types that were not previously declared
  • In development, will throw when using the same type more than once
  • In development, will throw when assigning a non-string type
  • In development, will throw when assigning anything to the types object

  • In production, silences all errors and does nothing fancy other than a single
loop that turns your strings into key/values on a plain object.

How to use

Before using, you'll need to install via npm or yarn:
# Sorry for the long names, but I was late to the party...
npm install redux-create-action-types
yarn install redux-create-action-types

Then you can import using ES Modules:
import createTypes from 'redux-create-action-types'

or CJS, if you like:
const createTypes = require('redux-create-action-types')

Now you can create your types objects:
const Types = createTypes(
  'FETCHING_RESOURCE',
  'FETCHED_RESOURCE',
  'FAILED_FETCHING_RESOURCE'
)

For all intents, it will return a plain object that looks like this:
{
  'FETCHING_RESOURCE': 'FETCHING_RESOURCE',
  'FETCHED_RESOURCE': 'FETCHED_RESOURCE',
  'FAILED_FETCHING_RESOURCE': 'FAILED_FETCHING_RESOURCE',
}

Eliminate undefined types

The special features of this module are only noticeable during development. For instance if you were writing a reducer and tried to access a type that was never defined before:
// This would be defined in another file...
const Types = createTypes(
  'FETCHING_RESOURCE',
  'FETCHED_RESOURCE',
  'FAILED_FETCHING_RESOURCE'
)

// A typically reducer.
function resource(state = {}, action) {
  switch (action.type) {
    case Types.FETCHING_SOME_RESOURCE: {
      return Object.assign({}, state, action)
    }

    default: { return state }
  }
}

The above will throw an error in development, because you've tried to access a property that was never defined. Had you not used this, it's possible for an undefined type to match your case check and put your app into an inconsistent state.

Prevent duplicate values

While keyMirror and this module make it easy to keep your key and values consistent, the same can not be said with simple objects. The following will show how this module prevents duplicate values:
const Types = createTypes(
  "TYPE_ONE"
);

// Produces: { "TYPE_ONE": "TYPE_ONE" }

If you attempt to modify this value, not that you would, but mistakes happen:
Types.TYPE_ONE = 'TYPE_TWO';

This will immediately error in development, letting you know something tried to assign to this object. Since this module uses a new JavaScript feature, called proxies, it can prevent all property setting behind an exception being thrown.
Another way this prevents duplicates is through use of a global cache:
// In one file...
const Types = createTypes(
  "TYPE_ONE"
);

// In another file...
const Types = createTypes(
  "TYPE_ONE"
);

The above will error as you have already used a type, and the system will not let you reuse it, since your actions are dispatched to all reducers.