Skip to content

Commit

Permalink
Merge pull request #11 from iceddev/cacheMoney
Browse files Browse the repository at this point in the history
cache check option
  • Loading branch information
monteslu committed Sep 28, 2018
2 parents 7a47a1b + 3012a7e commit 583777e
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 34 deletions.
24 changes: 23 additions & 1 deletion README.md
Expand Up @@ -193,6 +193,28 @@ export default duck.reducerCombined;

```

**That's it!** That's your entire state handler for a couple of functions that can by synchronous or promise-returing! Your react or whatever view can just look in redux for `props.someState.exampleA` or `props.someState.exampleAError`.
That's your entire state handler for a couple of functions that can by synchronous or promise-returing! Your react or whatever view can just look in redux for `props.someState.exampleA` or `props.someState.exampleAError`.


## cache

Sometimes you only want to do an action once. For example only fetch data from an API if you haven't already. `makeDuck` and `promiseHandler` can take an option to return actions that will check state and only call your action if needed.

```javascript

const duck = makeDuck({
exmapleA : somePromiseAPI
}, {namespace: 'foo', cache: true});

export const { exampleA, exampleACached } = duck;
export default duck.reducerCombined;

```

In this example if you call `exampleACached` the cooldux middleware will check the state for `foo.exampleA` and if it sees a [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy) value return that, otherwise call the `exampleA` function. **Your namespace must be the same property this duck belongs to.**



## That's it for now!

Each of the previous examples build on eachother, you certainly don't need to use all of the cooldux functions. It might be that just the early examples feel helpful or it may be that you're on board to use it all and just let the automagic take you.
81 changes: 65 additions & 16 deletions index.js
Expand Up @@ -105,6 +105,24 @@ function promiseHandler(type, options) {
return state;
}
};
if (options.cache) {
if (!options.namespace) {
throw new Error('Must specify a namespace option when using cached actions');
}
creators[type + 'ActionCached'] = function(actionFunc, actionArgs) {
var promise = Promise.resolve(type + 'ActionCached');
promise._cooldux = {
namespace: options.namespace,
name: name,
options: options,
cache: true,
actionFunc: actionFunc,
actionArgs: actionArgs,
type: type
};
return promise;
};
}
return creators;
}

Expand All @@ -127,8 +145,28 @@ function combinedHandler(types, options) {
return handlers;
}

function actionResolver(promise, _cooldux, dispatch) {
return promise.then(function(payload) {
dispatch({
type: _cooldux.name + '_End',
payload: payload
});
return payload;
}).catch(function(err) {
dispatch({
type: _cooldux.name + '_Error',
payload: err
});
if (_cooldux.options.throwErrors) {
throw err;
}
return null;
});
}

var promiseMiddleware = function(ref) {
var dispatch = ref.dispatch;
var getState = ref.getState;
return function(next) {
return function(action) {
if (action.then && action._cooldux) {
Expand All @@ -141,26 +179,29 @@ var promiseMiddleware = function(ref) {
});
return payload;
});
} else if (_cooldux.cache) {
var state = getState();
if (state[_cooldux.namespace]) {
var cachedValue = state[_cooldux.namespace][_cooldux.type];
if (cachedValue) {
dispatch({
type: _cooldux.name + '_End',
payload: cachedValue
});
return Promise.resolve(cachedValue);
}
dispatch({
type: _cooldux.name + '_Start'
});
var promise = Promise.resolve(_cooldux.actionFunc.apply(null, _cooldux.actionArgs));
return actionResolver(promise, _cooldux, dispatch);
}
return actionResolver(Promise.reject(_cooldux.namespace + ' namespace not in state'), _cooldux, dispatch);
}
dispatch({
type: _cooldux.name + '_Start'
});
return action.then(function(payload) {
dispatch({
type: _cooldux.name + '_End',
payload: payload
});
return payload;
}).catch(function(err) {
dispatch({
type: _cooldux.name + '_Error',
payload: err
});
if (_cooldux.options.throwErrors) {
throw err;
}
return null;
});
return actionResolver(action, _cooldux, dispatch);
}
next(action);
return action;
Expand All @@ -169,6 +210,9 @@ var promiseMiddleware = function(ref) {
};

function makeDuck(actions, options) {
if (options === void 0) {
options = {};
}
var actionProps = Object.keys(actions).filter(function(key) {
return typeof actions[key] !== 'object';
});
Expand All @@ -178,6 +222,11 @@ function makeDuck(actions, options) {
duck[key] = function() {
return duck[key + 'Action'](actions[key].apply(null, arguments));
};
if (options.cache) {
duck[key + 'Cached'] = function() {
return duck[key + 'ActionCached'](actions[key], arguments);
};
}
return;
}
if (typeof actions[key] === 'undefined') {
Expand Down
61 changes: 48 additions & 13 deletions index.mjs
Expand Up @@ -108,6 +108,16 @@ export function promiseHandler(type, options = {}) {
}
}
};
if(options.cache) {
if(!options.namespace) {
throw new Error('Must specify a namespace option when using cached actions');
}
creators[type + 'ActionCached'] = (actionFunc, actionArgs) => {
const promise = Promise.resolve(type + 'ActionCached');
promise._cooldux = { namespace: options.namespace, name, options, cache: true, actionFunc, actionArgs, type };
return promise;
}
}
return creators;
}

Expand Down Expand Up @@ -139,12 +149,27 @@ export function combinedHandler(types, options) {
return handlers;
}


function actionResolver(promise, _cooldux, dispatch) {
return promise.then(payload => {
dispatch({type: _cooldux.name + '_End', payload});
return payload;
})
.catch(err => {
dispatch({type: _cooldux.name + '_Error', payload: err});
if(_cooldux.options.throwErrors) {
throw err;
}
return null;
});
}

/**
* A middleware for redux that auto-dispatches cooldux actions from a cooldux promiseHandler.
*
* @param {Function} dispatch
*/
export const promiseMiddleware = ({ dispatch }) => {
export const promiseMiddleware = ({ dispatch, getState }) => {
return next => {
return action => {
if(action.then && action._cooldux) {
Expand All @@ -155,18 +180,23 @@ export const promiseMiddleware = ({ dispatch }) => {
return payload;
});
}
dispatch({type: _cooldux.name + '_Start'});
return action.then(payload => {
dispatch({type: _cooldux.name + '_End', payload});
return payload;
})
.catch(err => {
dispatch({type: _cooldux.name + '_Error', payload: err});
if(_cooldux.options.throwErrors) {
throw err;
else if(_cooldux.cache) {
const state = getState();
if(state[_cooldux.namespace]) {
const cachedValue = state[_cooldux.namespace][_cooldux.type];
if(cachedValue) {
dispatch({type: _cooldux.name + '_End', payload: cachedValue});
return Promise.resolve(cachedValue);
}
dispatch({type: _cooldux.name + '_Start'});
const promise = Promise.resolve(_cooldux.actionFunc.apply(null, _cooldux.actionArgs))
return actionResolver(promise, _cooldux, dispatch);
}
return null;
});

return actionResolver(Promise.reject(_cooldux.namespace + ' namespace not in state'), _cooldux, dispatch);
}
dispatch({type: _cooldux.name + '_Start'});
return actionResolver(action, _cooldux, dispatch);
}
next(action);
return action;
Expand All @@ -179,14 +209,19 @@ export const promiseMiddleware = ({ dispatch }) => {
*
* @param {Object} actions An object of functions
*/
export function makeDuck(actions, options) {
export function makeDuck(actions, options = {}) {
const actionProps = Object.keys(actions).filter(key => typeof actions[key] !== 'object');
const duck = combinedHandler(actionProps, options);
actionProps.forEach(key => {
if(typeof actions[key] === 'function') {
duck[key] = function() {
return duck[key + 'Action'](actions[key].apply(null, arguments));
}
if(options.cache) {
duck[key + 'Cached'] = function() {
return duck[key + 'ActionCached'](actions[key], arguments);
}
}
return;
}
if(typeof actions[key] === 'undefined') {
Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "cooldux",
"version": "0.13.0",
"version": "0.14.0",
"description": "Redux ducks pattern helpers",
"main": "index.js",
"scripts": {
Expand Down
72 changes: 69 additions & 3 deletions test/index.js
Expand Up @@ -11,10 +11,10 @@ var should = chai.should();
var cooldux = require('../index.js');


function createMiddleware () {
function createMiddleware (state = {}) {
const store = {
getState: chai.spy(function() {
return {};
return state;
}),
dispatch: chai.spy(function() {})
};
Expand Down Expand Up @@ -293,6 +293,70 @@ describe('cooldux', function() {

});

it('creates a duck with a cache check with cache in the store', (done) => {
const duck = cooldux.makeDuck({
a : (num1, num2) => num1 + num2
}, {namespace: 'foo', cache: true});

duck.a.should.be.a('function');
duck.aCached.should.be.a('function');

const { invoke, store, next } = createMiddleware({foo: {a: 'this isnt even a number!'}});
const action = duck.aCached(1, 2);
invoke(action)
.then(res => {
next.should.not.have.been.called.with(action);
res.should.equal('this isnt even a number!');
done();
});
});

it('creates a duck with a cache check without cache in the store', (done) => {
const duck = cooldux.makeDuck({
a : (num1, num2) => num1 + num2
}, {namespace: 'foo', cache: true});

duck.a.should.be.a('function');
duck.aCached.should.be.a('function');

const { invoke, store, next } = createMiddleware({foo: {}});
const action = duck.aCached(1, 2);
invoke(action)
.then(res => {
next.should.not.have.been.called.with(action);
res.should.equal(3);
done();
});
});

it('makeDuck with caching should error if namespace not supplied', (done) => {
try {
cooldux.makeDuck({
a : (num1, num2) => num1 + num2
}, {cache: true});
} catch (error) {
error.should.be.an('error');
done();
}
});

it('makeDuck with caching should error if supplied namespace is not same as the state property', (done) => {
const duck = cooldux.makeDuck({
a : (num1, num2) => num1 + num2
}, {namespace: 'foo', cache: true, throwErrors: true});

duck.a.should.be.a('function');
duck.aCached.should.be.a('function');

const { invoke, store, next } = createMiddleware({bar: {a: 3}});
const action = duck.aCached(1, 2);
invoke(action)
.catch(error => {
next.should.not.have.been.called.with(action);
done();
});
});

it('creates a duck with a default function', (done) => {
const duck = cooldux.makeDuck({
a : undefined
Expand All @@ -312,7 +376,7 @@ describe('cooldux', function() {

});

it('duck actions should error if not a function or undefined', (done) => {
it('makeDuck should error if not a function or undefined', (done) => {
try {
cooldux.makeDuck({
a : 'foo'
Expand All @@ -324,4 +388,6 @@ describe('cooldux', function() {

});



});

0 comments on commit 583777e

Please sign in to comment.