Skip to content

Instantly share code, notes, and snippets.

@saurabhnemade
Created August 13, 2019 04:40
Show Gist options
  • Save saurabhnemade/66c1f2d565c05133644aa4e8d3c6e82f to your computer and use it in GitHub Desktop.
Save saurabhnemade/66c1f2d565c05133644aa4e8d3c6e82f to your computer and use it in GitHub Desktop.
Dynamic Root Reducer - Final
import { createStore as _createStore, combineReducers } from 'redux';
import get from 'lodash/get';
import set from 'lodash/set';
const dynamicActionGenerator = () => {
return '@@TEST-REDUCER-VALIDITY/' + Math.random().toString(36).substring(7).split('').join('\\');
};
const isValidReducer = (reducer, throwError = false) => {
if (typeof reducer !== 'function') {
if (throwError) {
throw new Error('You did not passed a valid reducer. A reducer must be a function with two parameters state and action');
} else {
return false;
}
}
const initialState = reducer(undefined, {type: dynamicActionGenerator()});
if (typeof initialState === 'undefined') {
if (throwError) {
throw new Error('Reducer must return state!!!.');
} else {
return false;
}
}
return true;
}
const copy = (source, deep) => {
var o, prop, type;
if (typeof source != 'object' || source === null) {
// What do to with functions, throw an error?
o = source;
return o;
}
o = new source.constructor();
for (prop in source) {
if (source.hasOwnProperty(prop)) {
type = typeof source[prop];
if (deep && type == 'object' && source[prop] !== null) {
o[prop] = copy(source[prop]);
} else {
o[prop] = source[prop];
}
}
}
return o;
}
/**
* Wrapper for reducer at given path
* @param {path} key
* @param {function} reducer
*/
const pathMapReducer = (key, reducer) => {
return (state, action) => {
const newSubState = {...reducer(get(state, key), action)};
if (typeof newSubState === 'undefined') {
throw new Error(`The '${key}' reducer must not return undefined.`);
}
/** Make sure not to mutate the state here. Tried using Object.assign, but diff didn't computed anything which I suspect is a mutation */
let newState = copy(state, true);
set(newState, key, newSubState);
return newState;
};
};
/**
* Creates enchanced version of store with asyncReducers properties
* This is to keep track of initialReducers and dynamic reducers which will be pushed to asyncReducers
* @param {*} reducer
* @param {*} initialState
* @param {*} enhancers
*/
const createStore = (initialReducer, initialState = {}, enhancers) => {
let asyncReducers = {};
const dynamicRootReducer = (rootReducer) => (state, action) => {
let hasChanged = false;
let nextState = Object.assign({}, state);
let reducer;
let previousStateForKey;
let nextStateForKey;
Object.keys(asyncReducers).forEach((key) => {
nextState = pathMapReducer(key, asyncReducers[key])(state, action);
});
Object.keys(rootReducer).forEach(key => {
reducer = rootReducer[key];
previousStateForKey = state[key];
nextStateForKey = reducer(previousStateForKey, action);
nextState[key] = nextStateForKey;
hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
});
return nextState;
}
const attachReducer = (storeKeyPath, dynamicReducer) => {
if (isValidReducer(dynamicReducer)) {
asyncReducers = { ...asyncReducers, [storeKeyPath]: dynamicReducer };
store.replaceReducer(dynamicRootReducer(initialReducer));
} else {
throw new Error(`The reducer at "${storeKeyPath}" is not a valid reducer`);
}
};
let store = _createStore(dynamicRootReducer(initialReducer), initialState, enhancers);
store.attachReducer = attachReducer;
return store;
};
export { createStore }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment