Notes

React Safe Actions

08 Jul 2016

There’s a notion of the Flux Standard Action where an action has a type, payload and optionally an error flag (where the payload is the error / exception).

For example:

{
  "type": "ADD_TODO",
  "payload": {
    "text": "Do something."
  }
}

One thing I’ve found with flux is that sometimes it’s tricky to track down an error due to the dispatcher separating the view from the stores, a problem with the payload in the action causes an error to occur in the reducer but you have to trace back through to see what caused the payload to be problematic.

React components allow you to specify a schema for their properties via the propTypes attribute, which results in warnings being written to the console for transgressions.

var MyComponent = React.createClass({
  propTypes: {
    foo: React.PropTypes.string.isRequired,
  },

  render: function () {
    return (
      <div>{this.props.foo} // This must be a string or it will warn.</div>
    );
  },
});

So my idea here is to re-use react’s PropTypes to use them to specify a schema for action payloads, so you can express an action in the form:

var rsa = require("react-safe-actions");

var myAction = rsa.create("MY_ACTION_TYPE", {
  foo: rsa.types.string.isRequired,
  bar: rsa.types.number,
});

This is creating a function which will in turn create the actions. The function will check the payload passed in to make sure it conforms to the schema passed in (unless NODE_ENV is production), so catching the error before generating the action. The function will return a flux standard action.

var action1 = myAction(); // BAD
var action2 = myAction({ foo: "ok" }); // OK
var action3 = myAction({ foo: "fail", bar: "not a number" }); // BAD
var action4 = myAction({ foo: "ok", bar: 21 }); // OK
var action5 = myAction(new Error("Yikes!")); // OK

There’s a special case for actions that take only one argument, it’s just passed to the action function:

var myAction = rsa.create("MY_ACTION_TYPE", {
  foo: rsa.types.string.isRequired,
});

var action2 = myAction("ok");
// { type: 'MY_ACTION_TYPE', payload: {foo: 'ok'} }

It’s only 40 or so lines of code but I’ve still put the source in a github repo and published a module to npm as I think others may find it useful.

I’ve updated the redux ToDo MVC example app, to show how the library works in practice, you can see the change list here.

When I first made the changes I made a mistake, with source maps turned on this how the error looks in the console:

Error: Required prop `id` was not specified in `EDIT_TODO`.

You can see that it’s reporting which property is missing from which action and with source maps enabled we can see the exact line where the problem is (Line 21 of TodoItem.js).

So in the example I hadn’t switched the editTodo call over to the options style method invocation.

this.props.editTodo(id, text)

Needed to become:

this.props.editTodo({ id, text })