Skip to content

Instantly share code, notes, and snippets.

@lpirola
Forked from Dr-Nikson/README.md
Created March 13, 2017 15:51
Show Gist options
  • Save lpirola/99424fd28dcd049d5ea2df64d00ccb4f to your computer and use it in GitHub Desktop.
Save lpirola/99424fd28dcd049d5ea2df64d00ccb4f to your computer and use it in GitHub Desktop.

Reduce boilerplate in Redux

  • Create actions similar to Flummox.
  • Generate action ids.
  • Supports actions with decorators, promises, and therefore ES7 async.
//
// 4 different ways to write actions: ES7 Async/Await, Promises, Async Function, Synchronous.
//
import {createActions, asyncAction} from './helpers.js'
export const CounterActions = createActions({
async incrementAsync() {
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, 1000)
})
let result = await promise
return result
},
// Use decorator to mark action that returns promise
@asyncAction()
incrementPromise() {
// Debug
const promise = new Promise( (resolve, reject) => {
setTimeout(() => {
resolve()
}, 1000)
})
return promise;
},
incrementFunctionWithState() {
return (dispatch, getState) => {
const {counter} = getState()
console.log(dispatch)
if (counter % 2 === 0) return
dispatch(CounterActions.increment())
}
},
incrementFunction()) {
return dispatch => {
setTimeout(() => {
dispatch(CounterActions.increment())
}, 1000)
}
},
increment() {
return {}
},
})
import React from 'react';
import {Provider} from 'redux/react';
import {RouteHandler} from 'react-router'
import {createRedux, createDispatcher, composeStores} from 'redux';
import thunkMiddleware from 'redux/lib/middleware/thunk';
import {compose} from 'redux';
import {promiseMiddleware} from './helpers.js';
import * as stores from './store.js';
const store = composeStores(stores);
const dispatcher = createDispatcher(
store,
getState => [promiseMiddleware(), thunkMiddleware(getState)]
);
const redux = createRedux(dispatcher);
////////////////////////////////////////////////////////////////////////////////
// We use the above code - which this is shorthand for this, but adds our promise middleware.
//const redux = createRedux(stores)
////////////////////////////////////////////////////////////////////////////////
export default class App extends React.Component {
render() {
return (
<Provider redux={redux}>
{() =>
<RouteHandler/>
}
</Provider>
);
}
}
// redux-helpers.js
import _ from 'lodash';
import uniqueId from 'uniqueid';
// Create actions that don't need constants :)
export const createActions = (actionObj) => {
const baseId = uniqueId();
return _.zipObject(_.map(actionObj, (actionCreator, key) => {
const actionId = `${baseId}-${key}`;
const asyncTypes = ['BEGIN', 'SUCCESS', 'FAILURE'].map( (state) => `${actionId}-${state}`);
const method = (...args) => {
const result = actionCreator(...args);
if (result instanceof Promise) {
// Promise (async)
return {
types: asyncTypes,
promise: result,
};
} else if (typeof result === 'function') {
// Function (async)
return (...args) => { // eslint-disable-line no-shadow
return {
type: actionId,
...(result(...args) || {})
};
};
} else { // eslint-disable-line no-else-return
// Object (sync)
return {
type: actionId,
...(result || {})
};
}
};
if (actionCreator._async === true) {
const [ begin, success, failure ] = asyncTypes;
method._id = {
begin,
success,
failure
};
} else {
method._id = actionId;
}
return [key, method];
}));
};
// Get action ids from actions created with `createActions`
export const getActionIds = (actionCreators) => {
return _.mapValues(actionCreators, (value, key) => { // eslint-disable-line no-unused-vars
return value._id;
});
};
// Replace switch statements in stores (taken from the Redux README)
export const createStore = (initialState, handlers) => {
return (state = initialState, action = {}) =>
handlers[action.type] ?
handlers[action.type](state, action) :
state;
};
export function promiseMiddleware() {
return (next) => (action) => {
const { promise, types, ...rest } = action;
if (!promise) {
return next(action);
}
const [REQUEST, SUCCESS, FAILURE] = types;
next({ ...rest, type: REQUEST });
return promise.then(
(result) => next({ ...rest, result, type: SUCCESS }),
(error) => next({ ...rest, error, type: FAILURE })
);
};
}
export function asyncAction() {
return (target, name, descriptor) => {
descriptor.value._async = true;
return descriptor;
};
}
import React from 'react'
import App from './app.js'
React.render(<App/>, document.getElementById('body'))
import {createStore, getActionIds} from './helpers.js'
import {default as Immutable, Map, List} from 'immutable'
import {CounterActions} from './actions.js'
const actions = getActionIds(CounterActions)
const initialState = 0
export const counter = createStore(initialState, {
// actions.incrementPromise.begin
// actions.incrementPromise.success
// actions.incrementPromise.failure
[actions.incrementPromise.success]: (state, actions) => {
return state + 5
},
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment