├── .babelrc ├── .gitignore ├── .npmignore ├── README.md ├── lib └── core.js ├── package.json ├── src └── core.js └── test └── test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 0 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | npm-debug.log -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | __tests__ 2 | src 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [redux-easy-actions](https://github.com/grigory-leonenko/redux-easy-actions) 2 | 3 | Redux/Flux action creation made simple 4 | 5 | ### Install 6 | 7 | ``` 8 | npm install redux-easy-actions 9 | ``` 10 | 11 | ### Important 12 | 13 | Starting version 0.4 library has been completely rewritten. Decorators using deprecated because of [this](https://phabricator.babeljs.io/T2645) and library no more support class as container for action creators methods, only plain objects. 14 | All became even simpler :) 15 | 16 | ### The Problem 17 | 18 | [Redux](http://rackt.github.io/redux) is a great library for JavaScript application building. But there is an inconvenience with the original solution: namely, "ACTION_TYPES" implemented as string constants. 19 | 20 | 21 | ```js 22 | export const ADD_TODO = 'ADD_TODO'; 23 | export const DELETE_TODO = 'DELETE_TODO'; 24 | ``` 25 | 26 | Ideally they are stored in a separate file. Thus, an import is required for action creators: 27 | 28 | ```js 29 | import * as types from '../constants/ActionTypes'; 30 | 31 | export function addTodo(text) { 32 | return { type: types.ADD_TODO, text }; 33 | } 34 | 35 | export function deleteTodo(id) { 36 | return { type: types.DELETE_TODO, id }; 37 | } 38 | ``` 39 | 40 | ...as well as for reducers: 41 | 42 | ```js 43 | import { ADD_TODO, DELETE_TODO } from '../constants/ActionTypes'; 44 | 45 | export default function todos(state = {}, action) { 46 | switch (action.type) { 47 | case ADD_TODO: 48 | //some actions 49 | case DELETE_TODO: 50 | //some actions 51 | } 52 | } 53 | ``` 54 | 55 | ...and wait, didn't you forget about the components themselves? Import again! 56 | 57 | ```js 58 | class TodoForm { 59 | submit(e){ 60 | this.props.dispatch(this.props.addTodo(e.target.value)); 61 | } 62 | } 63 | ``` 64 | 65 | Seems like too much links, isn't it? And if you need to change a single action name, about 6 steps are required! 66 | 67 | ``` 68 | add or rename the string constant -> add or rename action creator -> rename or specify type in the payload -> add or rename the action inside components -> update the reducer's import -> update the reducer's switch statement code -> test it -> be happy 69 | ``` 70 | 71 | It looks very confusing. With [redux-easy-actions](https://github.com/grigory-leonenko/redux-easy-actions) this boilerplate will be much easier: 72 | 73 | ``` 74 | add or rename the action -> update the action inside component -> update switch condition -> test it -> be happy 75 | ``` 76 | 77 | ### How it works 78 | 79 | First write action creators, and import the EasyActions decorator: 80 | 81 | ```js 82 | 83 | import EasyActions from 'redux-easy-actions'; 84 | 85 | const { Actions, Constants } = EasyActions({ 86 | ADD_TODO(type, text){ 87 | return {type, text} 88 | }, 89 | DELETE_TODO(type, id){ 90 | return {type, id} 91 | } 92 | }) 93 | 94 | export { Actions as Actions } 95 | export { Constants as Constants } 96 | 97 | ``` 98 | > Important: As first argument always passed action type, this happens automatically no need to pass it manually. 99 | 100 | That's all! Actions are created. Next connect it to reducer: 101 | 102 | ```js 103 | import { Constants } from '../actions/actions.js'; 104 | 105 | export default function todos(state = {}, action) { 106 | switch (action.type) { 107 | case Constants.ADD_TODO: 108 | //some actions 109 | case Constants.DELETE_TODO: 110 | //some actions 111 | } 112 | } 113 | 114 | ``` 115 | 116 | To trigger the action from a component use: 117 | 118 | ```js 119 | import {Actions} from '../actions/actions.js'; 120 | 121 | class TodoForm extends React.Component { 122 | submit(e){ 123 | this.props.dispatch(Actions.ADD_TODO(e.target.value)); 124 | } 125 | } 126 | ``` 127 | 128 | Great! No strings, easy to change and integrate :) 129 | 130 | ### Is it production-ready? 131 | 132 | Please keep in mind that it's still a very early version. 133 | 134 | ### Inspired by 135 | 136 | * [Redux](http://rackt.github.io/redux) 137 | * [Flux](https://facebook.github.io/flux/) 138 | * [Autobind Decorator](https://github.com/andreypopp/autobind-decorator) 139 | * My own hate of string constants. 140 | 141 | ### License 142 | 143 | MIT 144 | -------------------------------------------------------------------------------- /lib/core.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Library for creating string keys for actions creators without manually writing string constants 3 | * 4 | * @param {...Object} Single or multiple action creators objects as arguments. 5 | * 6 | * @returns {Object} Constants for mathing actions and Actions creaters. 7 | * */ 8 | 'use strict'; 9 | 10 | Object.defineProperty(exports, '__esModule', { 11 | value: true 12 | }); 13 | 14 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 15 | 16 | exports['default'] = function () { 17 | for (var _len = arguments.length, groups = Array(_len), _key = 0; _key < _len; _key++) { 18 | groups[_key] = arguments[_key]; 19 | } 20 | 21 | return groups.reduce(combineGroups, []).reduce(formatActions, { Constants: {}, Actions: {} }); 22 | }; 23 | 24 | /** 25 | * Collect action creators from few groups to one list 26 | * 27 | * @param {Array} Collected action creators. 28 | * 29 | * @param {Object} Group with action creators. 30 | * 31 | * @returns {Array} Collected action creators. 32 | * */ 33 | function combineGroups(result, group) { 34 | if (!isObject(group)) { 35 | throw new Error('Group of actions must be plain object!'); 36 | } 37 | return result.concat(parseObject(group)); 38 | } 39 | /** 40 | * Prepare action creators to format. 41 | * 42 | * @param {Object} Group with action creators. 43 | * 44 | * @return {Array} List with parsed action creators. 45 | * */ 46 | function parseObject(group) { 47 | return Object.keys(group).map(function (key) { 48 | return { name: key, fn: group[key] }; 49 | }); 50 | } 51 | 52 | /** 53 | * Split constants and action creators. 54 | * 55 | * @param {Object} Object with lists of constants and action creators. 56 | * 57 | * @return {Object} Object with lists of constants and action creators. 58 | * */ 59 | function formatActions(_ref, action) { 60 | var Constants = _ref.Constants; 61 | var Actions = _ref.Actions; 62 | 63 | if (Constants.hasOwnProperty(action.name)) { 64 | throw new Error('Action ' + action.name + ' already exist!'); 65 | } 66 | return { 67 | Constants: Object.assign(Constants, _defineProperty({}, action.name, action.name)), 68 | Actions: Object.assign(Actions, _defineProperty({}, action.name, function () { 69 | for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 70 | args[_key2] = arguments[_key2]; 71 | } 72 | 73 | return action.fn.apply(action, [action.name].concat(args)); 74 | })) 75 | }; 76 | } 77 | 78 | /** 79 | * Helper function to check is object is plain. 80 | * 81 | * @param {Object} Target to check. 82 | * 83 | * @returns {Boolean} Result of check. 84 | * */ 85 | function isObject(target) { 86 | return Object.prototype.toString.call(target) === '[object Object]'; 87 | } 88 | module.exports = exports['default']; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-easy-actions", 3 | "version": "0.4.0", 4 | "description": "Sugar library for easy creating Flux or Redux actions.", 5 | "main": "lib/core.js", 6 | "scripts": { 7 | "clean": "rimraf lib coverage", 8 | "build:lib": "babel src --out-dir lib", 9 | "version": "npm run clean && npm run build:lib && git add . && git commit -m \"build update\"", 10 | "test": "mocha --compilers js:babel/register" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/grigory-leonenko/redux-easy-actions.git" 15 | }, 16 | "keywords": [ 17 | "redux", 18 | "actions", 19 | "react", 20 | "flux" 21 | ], 22 | "author": "Grigory Leonenko", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/grigory-leonenko/redux-easy-actions/issues" 26 | }, 27 | "homepage": "https://github.com/grigory-leonenko/redux-easy-actions", 28 | "devDependencies": { 29 | "assert": "^1.3.0", 30 | "babel": "^5.8.21", 31 | "babel-core": "^5.8.22", 32 | "mocha": "^2.2.5", 33 | "rimraf": "^2.4.2" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/core.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Library for creating string keys for actions creators without manually writing string constants 3 | * 4 | * @param {...Object} Single or multiple action creators objects as arguments. 5 | * 6 | * @returns {Object} Constants for mathing actions and Actions creaters. 7 | * */ 8 | export default function (...groups){ 9 | return groups.reduce(combineGroups, []).reduce(formatActions, {Constants: {}, Actions: {}}); 10 | } 11 | 12 | /** 13 | * Collect action creators from few groups to one list 14 | * 15 | * @param {Array} Collected action creators. 16 | * 17 | * @param {Object} Group with action creators. 18 | * 19 | * @returns {Array} Collected action creators. 20 | * */ 21 | function combineGroups(result, group){ 22 | if(!isObject(group)){ 23 | throw new Error(`Group of actions must be plain object!`) 24 | } 25 | return result.concat(parseObject(group)) 26 | } 27 | /** 28 | * Prepare action creators to format. 29 | * 30 | * @param {Object} Group with action creators. 31 | * 32 | * @return {Array} List with parsed action creators. 33 | * */ 34 | function parseObject(group){ 35 | return Object.keys(group).map(key => {return {name: key, fn: group[key]}}) 36 | } 37 | 38 | /** 39 | * Split constants and action creators. 40 | * 41 | * @param {Object} Object with lists of constants and action creators. 42 | * 43 | * @return {Object} Object with lists of constants and action creators. 44 | * */ 45 | function formatActions({Constants, Actions}, action){ 46 | if(Constants.hasOwnProperty(action.name)){ 47 | throw new Error(`Action ${action.name} already exist!`) 48 | } 49 | return { 50 | Constants: Object.assign(Constants, {[action.name]: action.name}), 51 | Actions: Object.assign(Actions, {[action.name]: (...args) => {return action.fn(action.name, ...args)}}) 52 | } 53 | } 54 | 55 | /** 56 | * Helper function to check is object is plain. 57 | * 58 | * @param {Object} Target to check. 59 | * 60 | * @returns {Boolean} Result of check. 61 | * */ 62 | function isObject(target){ 63 | return Object.prototype.toString.call(target) === '[object Object]'; 64 | } 65 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | import EasyActions from '../src/core.js'; 2 | import assert from 'assert'; 3 | 4 | describe('redux-easy-actions full class decorator tests', () => { 5 | const ItemActions = EasyActions({ 6 | ADD_ITEM(type, text){ 7 | return {type, text} 8 | } 9 | }) 10 | const {Actions, Constants} = ItemActions; 11 | 12 | it('constants and actions created', () => { 13 | assert(Actions && Constants); 14 | }); 15 | 16 | it('right action type from action', () => { 17 | assert(Actions.ADD_ITEM().type === 'ADD_ITEM') 18 | }) 19 | 20 | it('right payload from action', () => { 21 | assert(Actions.ADD_ITEM('Foo').text === 'Foo') 22 | }) 23 | 24 | it('constant with action name exists', () => { 25 | assert(Constants.ADD_ITEM === 'ADD_ITEM') 26 | }) 27 | }) 28 | --------------------------------------------------------------------------------