Skip to content

Relax the constraint on actions #437

Closed
@gcanti

Description

@gcanti

First of all thanks to all the contributors, in particular to @gaearon and @acdlite for your work.

The problem

Currently actions must be plain objects due to this invariant in the dispatch function:

// createStore.js

invariant(
  isPlainObject(action),
  'Actions must be plain objects. Use custom middleware for async actions.'
);

This prevents to use constructors to build instances of actions.
Scanning the issues I found this comment by @gaearon in #355 (comment)

record/replay and associated features won't work with an action class. We don't want to encourage this so people don't lock themselves out of great features

But I can't figure out why.

Relaxing the constraint seems to bring some benefits:

  • define actions as models (domain driven design)
  • possibly simplify the reducer and get rid of constants
  • type check the actions (for example defining actions with tcomb, TypeScript or Flow)

Example (from the counter app example)

Define actions as models

// CounterAction.js

class IncrementEvent {
  patch(state) {
    return state + 1;
  }
}

class DecrementEvent {
  patch(state) {
    return state - 1;
  }
}

export function increment() {
  return new IncrementEvent();
}

export function decrement() {
  return new DecrementEvent();
}

Simplify the reducer and get rid of constants

// counter.js

export default function counter(state = 0, action) {
  if (typeof action.patch === 'function') { 
    return action.patch(state); // get rid of switch and constants
  }
  return state;
}

The implementation above promotes a patch function (instead of a reduce function) as a possible primitive:

patch :: (state) -> state

Type check the action construction (here with the help of tcomb)

// CounterAction.js

import t from 'tcomb';

//
// use the constructors as (type checked) action creators 
// Instances are also frozen
//

export var IncrementEvent = t.struct({});
IncrementEvent.prototype.patch = function (state) {
  return state + 1;
};

export var DecrementEvent = t.struct({});
DecrementEvent.prototype.patch = function (state) {
  return state - 1;
};

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions