When writing a big react redux application or any SPA using redux, there is a need to split code with webpack and load reducers asynchronously.
The way to load reducer asynchronously is utilizing redux store replaceReducer
. There is typical sample of inject reducer like injectReducer
function in https://github.com/davezuko/react-redux-starter-kit/blob/master/src/store/reducers.js. It only can replace the top level reducer in asyncReducers
. For example.
asyncReducers = {
products: combineReducers({
list: productListReducer,
one: productReducer
}),
orders: ordersReducer
}
The injectReducer
function only can replace products
or orders
which is top level in asyncReducers.
But there might be many chance that only asynchronously inject reducer with top one not enough since the split javascript file is still big.
Here introduce a way to inject reducer in arbitrary nested level.
The injectReducer
function api change a little bit, the key
is in the form of products
or products.list
and reducer
is pure reducer not one by combineReducers
. Then we can use key which is .
separated to build nested object of asyncReducers which is kind of tree structure and leaf value is the pure reducer. With this information, it is easy to use combineReducers
to rebuild the root reducer which is used by redux store replaceReducer
function.
const replaceAsyncReducers = (rootReducers, keys, reducer) => {
let key = keys.shift()
if (keys.length === 0) {
rootReducers[key] = reducer
return
}
if (rootReducers[key] === undefined) rootReducers[key] = {}
let nextRootReducers = rootReducers[key]
return replaceAsyncReducers(nextRootReducers, keys, reducer)
}
const combineAsyncReducers = (asyncReducers) => {
if (typeof asyncReducers !== 'object') return asyncReducers
let combineReducerObject = {}
for (let prop in asyncReducers) {
if (!asyncReducers.hasOwnProperty(prop)) continue
let value = asyncReducers[prop]
if (typeof value === 'object') {
combineReducerObject[prop] = combineAsyncReducers(value)
} else if (typeof value === 'function') {
combineReducerObject[prop] = value
}
}
return combineReducers(combineReducerObject)
}
export const makeRootReducer = (asyncReducers) => {
let newAsyncReducers = {}
console.log(asyncReducers)
for (let key in asyncReducers) {
if (!asyncReducers.hasOwnProperty(key)) continue
newAsyncReducers[key] = combineAsyncReducers(asyncReducers[key])
}
return combineReducers({
location: locationReducer,
...asyncReducers
})
}
export const injectReducer = (store, { key, reducer }) => {
let keys = key.split('.')
replaceAsyncReducers(store.asyncReducers, keys, reducer)
// store.asyncReducers[key] = reducer
store.replaceReducer(makeRootReducer(store.apolloClient, store.asyncReducers))
}
With this change, we can inject reducer anywhere with injectReducer(store, { key: 'products.list', reducer: productListReducer })
.