Skip to content

Commit 583777e

Browse files
authored
Merge pull request #11 from iceddev/cacheMoney
cache check option
2 parents 7a47a1b + 3012a7e commit 583777e

File tree

5 files changed

+206
-34
lines changed

5 files changed

+206
-34
lines changed

README.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,28 @@ export default duck.reducerCombined;
193193

194194
```
195195

196-
**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`.
196+
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`.
197+
198+
199+
## cache
200+
201+
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.
202+
203+
```javascript
204+
205+
const duck = makeDuck({
206+
exmapleA : somePromiseAPI
207+
}, {namespace: 'foo', cache: true});
208+
209+
export const { exampleA, exampleACached } = duck;
210+
export default duck.reducerCombined;
211+
212+
```
213+
214+
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.**
215+
216+
217+
218+
## That's it for now!
197219

198220
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.

index.js

Lines changed: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,24 @@ function promiseHandler(type, options) {
105105
return state;
106106
}
107107
};
108+
if (options.cache) {
109+
if (!options.namespace) {
110+
throw new Error('Must specify a namespace option when using cached actions');
111+
}
112+
creators[type + 'ActionCached'] = function(actionFunc, actionArgs) {
113+
var promise = Promise.resolve(type + 'ActionCached');
114+
promise._cooldux = {
115+
namespace: options.namespace,
116+
name: name,
117+
options: options,
118+
cache: true,
119+
actionFunc: actionFunc,
120+
actionArgs: actionArgs,
121+
type: type
122+
};
123+
return promise;
124+
};
125+
}
108126
return creators;
109127
}
110128

@@ -127,8 +145,28 @@ function combinedHandler(types, options) {
127145
return handlers;
128146
}
129147

148+
function actionResolver(promise, _cooldux, dispatch) {
149+
return promise.then(function(payload) {
150+
dispatch({
151+
type: _cooldux.name + '_End',
152+
payload: payload
153+
});
154+
return payload;
155+
}).catch(function(err) {
156+
dispatch({
157+
type: _cooldux.name + '_Error',
158+
payload: err
159+
});
160+
if (_cooldux.options.throwErrors) {
161+
throw err;
162+
}
163+
return null;
164+
});
165+
}
166+
130167
var promiseMiddleware = function(ref) {
131168
var dispatch = ref.dispatch;
169+
var getState = ref.getState;
132170
return function(next) {
133171
return function(action) {
134172
if (action.then && action._cooldux) {
@@ -141,26 +179,29 @@ var promiseMiddleware = function(ref) {
141179
});
142180
return payload;
143181
});
182+
} else if (_cooldux.cache) {
183+
var state = getState();
184+
if (state[_cooldux.namespace]) {
185+
var cachedValue = state[_cooldux.namespace][_cooldux.type];
186+
if (cachedValue) {
187+
dispatch({
188+
type: _cooldux.name + '_End',
189+
payload: cachedValue
190+
});
191+
return Promise.resolve(cachedValue);
192+
}
193+
dispatch({
194+
type: _cooldux.name + '_Start'
195+
});
196+
var promise = Promise.resolve(_cooldux.actionFunc.apply(null, _cooldux.actionArgs));
197+
return actionResolver(promise, _cooldux, dispatch);
198+
}
199+
return actionResolver(Promise.reject(_cooldux.namespace + ' namespace not in state'), _cooldux, dispatch);
144200
}
145201
dispatch({
146202
type: _cooldux.name + '_Start'
147203
});
148-
return action.then(function(payload) {
149-
dispatch({
150-
type: _cooldux.name + '_End',
151-
payload: payload
152-
});
153-
return payload;
154-
}).catch(function(err) {
155-
dispatch({
156-
type: _cooldux.name + '_Error',
157-
payload: err
158-
});
159-
if (_cooldux.options.throwErrors) {
160-
throw err;
161-
}
162-
return null;
163-
});
204+
return actionResolver(action, _cooldux, dispatch);
164205
}
165206
next(action);
166207
return action;
@@ -169,6 +210,9 @@ var promiseMiddleware = function(ref) {
169210
};
170211

171212
function makeDuck(actions, options) {
213+
if (options === void 0) {
214+
options = {};
215+
}
172216
var actionProps = Object.keys(actions).filter(function(key) {
173217
return typeof actions[key] !== 'object';
174218
});
@@ -178,6 +222,11 @@ function makeDuck(actions, options) {
178222
duck[key] = function() {
179223
return duck[key + 'Action'](actions[key].apply(null, arguments));
180224
};
225+
if (options.cache) {
226+
duck[key + 'Cached'] = function() {
227+
return duck[key + 'ActionCached'](actions[key], arguments);
228+
};
229+
}
181230
return;
182231
}
183232
if (typeof actions[key] === 'undefined') {

index.mjs

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,16 @@ export function promiseHandler(type, options = {}) {
108108
}
109109
}
110110
};
111+
if(options.cache) {
112+
if(!options.namespace) {
113+
throw new Error('Must specify a namespace option when using cached actions');
114+
}
115+
creators[type + 'ActionCached'] = (actionFunc, actionArgs) => {
116+
const promise = Promise.resolve(type + 'ActionCached');
117+
promise._cooldux = { namespace: options.namespace, name, options, cache: true, actionFunc, actionArgs, type };
118+
return promise;
119+
}
120+
}
111121
return creators;
112122
}
113123

@@ -139,12 +149,27 @@ export function combinedHandler(types, options) {
139149
return handlers;
140150
}
141151

152+
153+
function actionResolver(promise, _cooldux, dispatch) {
154+
return promise.then(payload => {
155+
dispatch({type: _cooldux.name + '_End', payload});
156+
return payload;
157+
})
158+
.catch(err => {
159+
dispatch({type: _cooldux.name + '_Error', payload: err});
160+
if(_cooldux.options.throwErrors) {
161+
throw err;
162+
}
163+
return null;
164+
});
165+
}
166+
142167
/**
143168
* A middleware for redux that auto-dispatches cooldux actions from a cooldux promiseHandler.
144169
*
145170
* @param {Function} dispatch
146171
*/
147-
export const promiseMiddleware = ({ dispatch }) => {
172+
export const promiseMiddleware = ({ dispatch, getState }) => {
148173
return next => {
149174
return action => {
150175
if(action.then && action._cooldux) {
@@ -155,18 +180,23 @@ export const promiseMiddleware = ({ dispatch }) => {
155180
return payload;
156181
});
157182
}
158-
dispatch({type: _cooldux.name + '_Start'});
159-
return action.then(payload => {
160-
dispatch({type: _cooldux.name + '_End', payload});
161-
return payload;
162-
})
163-
.catch(err => {
164-
dispatch({type: _cooldux.name + '_Error', payload: err});
165-
if(_cooldux.options.throwErrors) {
166-
throw err;
183+
else if(_cooldux.cache) {
184+
const state = getState();
185+
if(state[_cooldux.namespace]) {
186+
const cachedValue = state[_cooldux.namespace][_cooldux.type];
187+
if(cachedValue) {
188+
dispatch({type: _cooldux.name + '_End', payload: cachedValue});
189+
return Promise.resolve(cachedValue);
190+
}
191+
dispatch({type: _cooldux.name + '_Start'});
192+
const promise = Promise.resolve(_cooldux.actionFunc.apply(null, _cooldux.actionArgs))
193+
return actionResolver(promise, _cooldux, dispatch);
167194
}
168-
return null;
169-
});
195+
196+
return actionResolver(Promise.reject(_cooldux.namespace + ' namespace not in state'), _cooldux, dispatch);
197+
}
198+
dispatch({type: _cooldux.name + '_Start'});
199+
return actionResolver(action, _cooldux, dispatch);
170200
}
171201
next(action);
172202
return action;
@@ -179,14 +209,19 @@ export const promiseMiddleware = ({ dispatch }) => {
179209
*
180210
* @param {Object} actions An object of functions
181211
*/
182-
export function makeDuck(actions, options) {
212+
export function makeDuck(actions, options = {}) {
183213
const actionProps = Object.keys(actions).filter(key => typeof actions[key] !== 'object');
184214
const duck = combinedHandler(actionProps, options);
185215
actionProps.forEach(key => {
186216
if(typeof actions[key] === 'function') {
187217
duck[key] = function() {
188218
return duck[key + 'Action'](actions[key].apply(null, arguments));
189219
}
220+
if(options.cache) {
221+
duck[key + 'Cached'] = function() {
222+
return duck[key + 'ActionCached'](actions[key], arguments);
223+
}
224+
}
190225
return;
191226
}
192227
if(typeof actions[key] === 'undefined') {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cooldux",
3-
"version": "0.13.0",
3+
"version": "0.14.0",
44
"description": "Redux ducks pattern helpers",
55
"main": "index.js",
66
"scripts": {

test/index.js

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ var should = chai.should();
1111
var cooldux = require('../index.js');
1212

1313

14-
function createMiddleware () {
14+
function createMiddleware (state = {}) {
1515
const store = {
1616
getState: chai.spy(function() {
17-
return {};
17+
return state;
1818
}),
1919
dispatch: chai.spy(function() {})
2020
};
@@ -293,6 +293,70 @@ describe('cooldux', function() {
293293

294294
});
295295

296+
it('creates a duck with a cache check with cache in the store', (done) => {
297+
const duck = cooldux.makeDuck({
298+
a : (num1, num2) => num1 + num2
299+
}, {namespace: 'foo', cache: true});
300+
301+
duck.a.should.be.a('function');
302+
duck.aCached.should.be.a('function');
303+
304+
const { invoke, store, next } = createMiddleware({foo: {a: 'this isnt even a number!'}});
305+
const action = duck.aCached(1, 2);
306+
invoke(action)
307+
.then(res => {
308+
next.should.not.have.been.called.with(action);
309+
res.should.equal('this isnt even a number!');
310+
done();
311+
});
312+
});
313+
314+
it('creates a duck with a cache check without cache in the store', (done) => {
315+
const duck = cooldux.makeDuck({
316+
a : (num1, num2) => num1 + num2
317+
}, {namespace: 'foo', cache: true});
318+
319+
duck.a.should.be.a('function');
320+
duck.aCached.should.be.a('function');
321+
322+
const { invoke, store, next } = createMiddleware({foo: {}});
323+
const action = duck.aCached(1, 2);
324+
invoke(action)
325+
.then(res => {
326+
next.should.not.have.been.called.with(action);
327+
res.should.equal(3);
328+
done();
329+
});
330+
});
331+
332+
it('makeDuck with caching should error if namespace not supplied', (done) => {
333+
try {
334+
cooldux.makeDuck({
335+
a : (num1, num2) => num1 + num2
336+
}, {cache: true});
337+
} catch (error) {
338+
error.should.be.an('error');
339+
done();
340+
}
341+
});
342+
343+
it('makeDuck with caching should error if supplied namespace is not same as the state property', (done) => {
344+
const duck = cooldux.makeDuck({
345+
a : (num1, num2) => num1 + num2
346+
}, {namespace: 'foo', cache: true, throwErrors: true});
347+
348+
duck.a.should.be.a('function');
349+
duck.aCached.should.be.a('function');
350+
351+
const { invoke, store, next } = createMiddleware({bar: {a: 3}});
352+
const action = duck.aCached(1, 2);
353+
invoke(action)
354+
.catch(error => {
355+
next.should.not.have.been.called.with(action);
356+
done();
357+
});
358+
});
359+
296360
it('creates a duck with a default function', (done) => {
297361
const duck = cooldux.makeDuck({
298362
a : undefined
@@ -312,7 +376,7 @@ describe('cooldux', function() {
312376

313377
});
314378

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

325389
});
326390

391+
392+
327393
});

0 commit comments

Comments
 (0)