redux-conditional
v0.7.5
Published
Make sharing reducers easy by conditionally applying actions to shared Redux reducers.
Downloads
4
Maintainers
Readme
Redux Conditional
Conditionally apply actions to Redux reducers to make sharing reducers easy.
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.
// 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
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
- One property conditionalKey will be added to each action; The value will be data specific to one counter.
- 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
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
// 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
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
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
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