Created
July 20, 2016 14:59
-
-
Save goldhand/344abe2597efea46dd3c559a97ac8a7a to your computer and use it in GitHub Desktop.
Redux midddleware for accruing interest before dispatching an action
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {Map} from 'immutable'; | |
/** | |
* Accrue redux middleware will only return actions that have accured enough interest | |
* | |
* change in accrued interest is calculated by interest += (next - prev) | |
* Math.abs(interest) >= resistance for action to recieve accrued flag | |
* | |
* To opt into this functionality add {meta: {resistance: <value>, accrue: <field>}} to the dispatched action. | |
* Where <value> is the amount that should be accrued before next dispatch and | |
* <field> is the field in the dispatched action that contains the value of <value> | |
* | |
* @example - Throttle actions until innerWidth delta 50 | |
* function updateWindowWidth() { | |
* return { | |
* type: types.RESIZE_WIDTH, | |
* windowWidth: innerWidth, | |
* meta: { | |
* resistance: 50, | |
* accrue: 'windowWidth', | |
* }, | |
* }; | |
* } | |
* @attribute {number} accrued - positive or negative change since last accrued action | |
* @returns {action} - next(action) | |
*/ | |
export default () => next => { | |
// store accrued information in [action.type] buckets | |
let refs = Map(); | |
return action => { | |
const {meta, type} = action; | |
// not opting into this middleware | |
if (!meta || !meta.resistance) { | |
return next(action); | |
} | |
const | |
{resistance, accrue} = meta, | |
nextValue = action[accrue], | |
prevValue = refs.getIn([type, 'prev'], 0), | |
change = nextValue - prevValue; | |
// if no change don't return anything | |
if (!change) return; | |
// accrue interest | |
refs = refs.updateIn([type, 'interest'], 0, v => v + change); | |
refs = refs.setIn([type, 'prev'], nextValue); | |
// interest is full | |
if (Math.abs(refs.getIn([type, 'interest'])) >= resistance) { | |
refs = refs.setIn([type, 'interest'], 0); | |
return next(action); | |
} | |
return; | |
}; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import test from 'ava'; | |
import accrue from 'middleware/accrue'; | |
import {createStore, applyMiddleware} from 'redux'; | |
function counterAction(w) { | |
return { | |
type: 'COUNT', | |
counter: w, | |
meta: { | |
resistance: 50, | |
accrue: 'counter', | |
}, | |
}; | |
} | |
function reducer(state = {counter: 0}, action) { | |
switch (action.type) { | |
case 'COUNT': | |
return { | |
...state, | |
counter: action.counter, | |
}; | |
default: | |
return state; | |
} | |
} | |
test('accrue doesnt dispatch until full', t => { | |
t.plan(3); | |
let store = createStore(reducer, applyMiddleware(accrue)); | |
store.dispatch(counterAction(10)); | |
t.is(store.getState().counter, 0, 'only 10 of 50'); | |
store.dispatch(counterAction(20)); | |
t.is(store.getState().counter, 0, 'only 20 of 50'); | |
store.dispatch(counterAction(60)); | |
t.is(store.getState().counter, 60, 'state should update'); | |
}); | |
test('accrue will empty after fill', t => { | |
t.plan(3); | |
let store = createStore(reducer, applyMiddleware(accrue)); | |
store.dispatch(counterAction(75)); | |
t.is(store.getState().counter, 75, 'should have filled once'); | |
store.dispatch(counterAction(110)); | |
t.is(store.getState().counter, 75, 'shouldnt change state'); | |
store.dispatch(counterAction(140)); | |
t.is(store.getState().counter, 140, 'should fill again and update state'); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment