Reusing Reducer Logic - Tuong-Nguyen/JavaScript-Structure GitHub Wiki
Scenarios
- Reducer logic doing the same kinds of work for different types of data.
- Multiple instances of a certain type of data being handled in the store
Example
Multiple counters in the application named A, B and C
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
const rootReducer = combineReducers({
counterA : counter,
counterB : counter,
counterC : counter
});
Problems
Dispatching an action will cause all three counter values to be incremented, not just one of them
Solutions
Higher-order reducer is a function that takes a reducer function as an argument and returns a new reducer function
Specific higher-order reducer
Reducer to generate new action constants with a given prefix or suffix
function createCounterWithNamedType(counterName = '') {
return function counter(state = 0, action) {
switch (action.type) {
case `INCREMENT_${counterName}`:
return state + 1;
case `DECREMENT_${counterName}`:
return state - 1;
default:
return state;
}
}
}
Reducer to attach additional info inside the action object
function createCounterWithNameData(counterName = '') {
return function counter(state = 0, action) {
const {name} = action;
if(name !== counterName) return state;
switch (action.type) {
case `INCREMENT`:
return state + 1;
case `DECREMENT`:
return state - 1;
default:
return state;
}
}
}
Generic higher-order reducer
Accepts both reducer function and identifier
function createNamedWrapperReducer(reducerFunction, reducerName) {
return (state, action) => {
const {name} = action;
const isInitializationCall = state === undefined;
if(name !== reducerName && !isInitializationCall) return state;
return reducerFunction(state, action);
}
}
const rootReducer = combineReducers({
counterA : createNamedWrapperReducer(counter, 'A'),
counterB : createNamedWrapperReducer(counter, 'B'),
counterC : createNamedWrapperReducer(counter, 'C'),
});
With filtering
function createFilteredReducer(reducerFunction, reducerPredicate) {
return (state, action) => {
const isInitializationCall = state === undefined;
const shouldRunWrappedReducer = reducerPredicate(action) || isInitializationCall;
return shouldRunWrappedReducer ? reducerFunction(state, action) : state;
}
}
const rootReducer = combineReducers({
// check for suffixed strings
counterA : createFilteredReducer(counter, action => action.type.endsWith('_A')),
// check for extra data in the action
counterB : createFilteredReducer(counter, action => action.name === 'B'),
// respond to all 'INCREMENT' actions, but never 'DECREMENT'
counterC : createFilteredReducer(counter, action => action.type === 'INCREMENT')
};