Update: A reader asked me to publish this as an npm package. Done and done! https://npmjs.com/package/redux-ize. Enjoy!
In redux apps, we have actions, we have action creators, but at Meeshkan, we use Redux Action Creator Creators. Sound like overkill? Maybe. But let me show how we use it and then you be the judge!
So let’s say that we have an action creator that creates an action and, thanks to react-redux
, automatically dispatches it through our middleware when we invoke it. The middleware can add all sorts of good stuff (timestamps, device info…), log events, run asynchronous tasks and a whole lot more. Let’s zoom in on our action creator:
export default clapForPost(nClaps) => ({type: "CLAP_FOR_POST",payload: nClaps});
Let’s zoom in on our analytics middleware:
import analytics from 'my-analytics-tool'
export default store => next => action => {if (action.meta && action.meta.analyticsData) {analytics(action.type, action.meta.analyticsData)}next(action);}
Hm… in our analytics data, we’d really like to report what page the claps are coming from. So we go back to our action.js
file and write a new function:
export default clapForPostWithAnalytics(nClaps, analytics) => ({type: "CLAP_FOR_POST",payload: nClaps,meta: { analytics },});
Do you smell the code smell yet? What if we also want to introduce navigation data based on where we are in the app? If we have five possible things to add, hello 2⁵ different functions.
What would be nice is if we could add a bit of analytics data to one action, a bit of navigation data to another… So let’s do it!
const ize = n => r => (...argsOuter) => f => (...argsInner) => {const primo = f(...argsInner.slice(n));return {...primo,meta: {...(primo.meta ? primo.meta : {}),...r(...argsInner.slice(0, n), ...argsOuter),},}}
export const analyticsIze = ize(0)(a => ({analytics: a || {},}));
export const promiseIze = ize(2)((resolve, reject) => ({form: {resolve,reject,}}));
export default (...args) => args.reduce((a,b) => b(a));
And then in our file with the action creator…
import Ize, { promiseIze, analyticsIze } from './ize';import { clapForPost } from './actions/self-aggrandizing';
class MyComponent {render() {const { clapForPost } = this.props;const clapper = () =>new Promise((res, rej) => clapForPost(res, rej, 3));return (<button onClick={clapper}>Hello world</button>);}}
connect(null,{clapForPost: Ize(clapForPost,promiseIze(),analytics({placeInApp: 'MyComponent'})),})(MyComponent);
And the action that will be sent is:
{type: "CLAP_FOR_POST",nClaps: 3,meta: {analytics: {placeInApp: 'MyComponent',},form: {resolve: [Function resolve],reject: [Function reject],},},}
Voilà our action creator creator! Interestingly, this strategy grew from our adaptation of a much-used hack in the redux-form
world. Action creator creators are a generalization of this strategy.
At Meeshkan, the rule of thumb has become:
Happy hacking!