UNPKG

redux-conditional

Version:

Make sharing reducers easy by conditionally applying actions to shared Redux reducers.

247 lines (173 loc) 8.65 kB
Redux Conditional ============= Conditionally apply actions to [Redux](https://github.com/reactjs/redux) reducers to make sharing reducers easy. ```js npm install --save redux-conditional ``` ## Motivation & Usage Redux would apply one action to all reducers under one store. It means different parts of one store using same reducers will always be kept same by default. It makes sharing reducers difficult (One solution is to use high order function to generate reducers. It will get some repeat code and become awkard when you want to share more reducers. And it won't support hierarchal data well). Sometimes, we want different parts of one store to share same reducers and to be able to maintain their own data. redux-conditional is created to help conditionally apply same reducers to different parts of one store. It can even support to combine multiple conditions in hierarchal store. ## Get started We want a list of counters and each counter can increment and decrement independently. Let's define normal reducer and action creators. ```js // just one normal reducer function counter(state = 0, action) { switch (action.type) { case 'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } } // just normal action creators function increment() { return { type: 'INCREMENT' }; } function decrement() { return { type: 'DECREMENT' }; } ``` Now we can make use of *redux-conditional* to create a list of counters sharing the same reducer. Check *counter.js* at [examples](https://github.com/ypxing/redux-conditional/tree/master/examples) ```js import { conditionalReducerByKey, simpleConditional } from 'redux-conditional'; // we can use other keys than 0, 1, 2 const targets = [0, 1, 2]; const targetReducer = conditionalReducerByKey(targets)(counter); const actionCreator = simpleConditional().actionCreator; // new action creators to take "key" into account. const conditionalIncrement = actionCreator(increment); const conditionalDecrement = actionCreator(decrement); // let's operate on different counters let state = {}; // let increment the counter with key 1 state = targetReducer(state, conditionalIncrement(1)()); // state will become { '0': 0, '1': 1, '2': 0 } // let decrement the counter with key 2 state = targetReducer(state, conditionalDecrement(2)()); // state will become { '0': 0, '1': 1, '2': -1 } ``` ## How it works 1. One property *conditionalKey* will be added to each action; The value will be data specific to one counter. 2. A new reducer will be created based on the original reducer. It will check *conditionalKey* in one action to decide deliver/discard it. ## Combine different conditions in store hierarchy Just like *combineReducers* in Redux, *redux-conditional* supports combination of different conditions in store hierarchy. Check *hierarchy.j* at [examples](https://github.com/ypxing/redux-conditional/tree/master/examples) ```js const level1 = 'myFirst'; const level2 = 'mySecond'; const conditional = simpleConditional({ switches: [level1, level2] }); const conditionalIncrement = conditional.actionCreator(increment); const conditionalDecrement = conditional.actionCreator(decrement); const BTargetReducer = conditionalReducerByKey(['B1', 'B2'], conditional.mySecond)(counter); const ATargetReducer = conditionalReducerByKey(['A1', 'A2'], conditional.myFirst)(BTargetReducer); const store = createStore(ATargetReducer); store.dispatch({ type: 'UNKNOWN' }); store.dispatch((conditionalIncrement({ myFirst: 'A1', mySecond: 'B2' })())); // store.getState() will be { A1: { B1: 0, B2: 1 }, A2: { B1: 0, B2: 0 } } store.dispatch((conditionalDecrement({ myFirst: 'A2', mySecond: 'B1' })())); // store.getState() will be { A1: { B1: 0, B2: 1 }, A2: { B1: -1, B2: 0 } } ``` ## Customization There are several ways you can customize it. ##### Attach *conditionalKey* to one path of one action instead of to it directly. Check *simpleConditionalKeyWithPaths.js* at [examples](https://github.com/ypxing/redux-conditional/tree/master/examples) ```js // we would like the conditional key to be at action.path1.path2 const paths = ['path1', 'path2']; const conditional = simpleConditional({ paths }); const targetReducer = conditionalReducerByKey(targets, conditional.default)(counter); ``` ##### Define different *condition* to decide deliver/discard one action. Check *customizedConditionMaker.js* at [examples](https://github.com/ypxing/redux-conditional/tree/master/examples) ```js import { conditionalReducerByKey, simpleConditional } from 'redux-conditional'; const targets = [0, 1, 2]; const conditional = simpleConditional(); // new action creators to take "key" into account. const conditionalIncrements = conditional.actionCreator(increment); // need to return true or false to decide delivering/discarding actions. // data will be 0, 1 or 2 in this case. it can be any data if we make targets one plain object. // For example, we don't want counter 1 to be greater than 2. const conditionMaker = data => (state, action) => { // if you use conditional.actionCreator (a witer), you need this reader. const key = conditional.default.conditionalKeyReader(action); return key === data && (key !== 1 || state === undefined || state < 2); }; const targetReducer = conditionalReducerByKey(targets, { conditionMaker })(counter); // let's operate on different counters let state = {}; // let increment the counter with key 1 state = targetReducer(state, conditionalIncrements(1)()); // state will become { '0': 0, '1': 1, '2': 0 } state = targetReducer(state, conditionalIncrements(1)()); // state will become { '0': 0, '1': 2, '2': 0 } state = targetReducer(state, conditionalIncrements(1)()); // state will become { '0': 0, '1': 2, '2': 0 } // let increment the counter with key 2 state = targetReducer(state, conditionalIncrements(2)()); // state will become { '0': 0, '1': 2, '2': 1 } state = targetReducer(state, conditionalIncrements(2)()); // state will become { '0': 0, '1': 2, '2': 2 } state = targetReducer(state, conditionalIncrements(2)()); // state will become { '0': 0, '1': 2, '2': 3 } ``` ##### Put counters in different levels of our store. Check *differentLevel.js* at [examples](https://github.com/ypxing/redux-conditional/tree/master/examples) ```js import { conditionalReducer, simpleConditional } from 'redux-conditional'; import { combineReducers, createStore } from 'redux'; const conditional = simpleConditional(); // combine reducers as we do in redux const counterBReducer = combineReducers({ counterB: conditionalReducer(conditional.default.conditionMaker('B'), counter), }); const rootReducers = combineReducers({ counterA: conditionalReducer(conditional.default.conditionMaker('A'), counter), another: counterBReducer, }); // new action creators to take "key" into account. const incrementByKey = conditional.actionCreator(increment); const decrementByKey = conditional.actionCreator(decrement); const store = createStore(rootReducers); // init our store store.dispatch({type: 'UNKNOWN'}); store.dispatch(incrementByKey('A')()); store.dispatch(incrementByKey('A')()); store.dispatch(decrementByKey('B')()); // state will become { counterA: 2, another: { counterB: -1 } } ``` ##### Use your own "conditions" and "key" Check *notSimpleCondition.js* at [examples](https://github.com/ypxing/redux-conditional/tree/master/examples) ```js const targets = { A: 'anythingForA', B: 'anythingForB' }; // data would be anythingForA or anythingForB const conditionMaker = (data) => (state, action) => action.myAnything === data; const targetReducer = conditionalReducerByKey(targets, { conditionMaker })(counter); // new action creators to take "key" into account. const incrementByKey = (key) => { const currentAction = increment(); currentAction.myAnything = targets[key]; return currentAction; }; const decrementByKey = (key) => { const currentAction = decrement(); currentAction.myAnything = targets[key]; return currentAction; }; // let's operate on different counters let state = {}; // let increment the counter with key 1 state = targetReducer(state, incrementByKey('A')); // state will become { A: 1, B: 0 } // let decrement the counter with key 2 state = targetReducer(state, decrementByKey('B')); // state will become { A: 1, B: -1 } ``` **Please note** that *simpleConditional* would apply its *writer* to target action and *reader* to read data back. If any redux middleware you use discards it, it's not going to work. In this case, you have to use *writer* to action manually before delivering it to reducer. ## License MIT