During app setup, static reducers are created and initialized. The way that happens is - first Store/Index/configureStore
is called to initialize the store’s injectReducer
method and then Store/Index/injectStaticReducers
is called to inject all static reducers in a loop.
The problem is that, when called in a loop, injectReducer
is combining and replacing all of the already set-up reducers with each new reducer. This is a very expensive operation and it can be improved by splitting the loop into two parts - first, set up all reducers and then, combine them all at once.
The issue usually is noticeable by a long App component’s mount time. Especially when the app is using a lot of static reducers. When looking at the network requests this may appear as a gap between render chunk download and FCP. This is especially apparent when there is a 4x CPU slowdown applied
By looking into the performance tab in the browser, it can be seen that the combineReducers
function is taking a lot of time when the App component is being mounted.
InjectReducers method here takes ~ 190ms
Store/Index/configureStore
/**
* Configure the store
* @namespace Store/Index/configureStore
* */
export function configureStore(store) {
// Add a dictionary to keep track of the registered async reducers
store.asyncReducers = {};
// Create an inject reducer function
// This function adds the async reducer, and creates a new combined reducer
store.injectReducer = (key, asyncReducer) => {
store.asyncReducers[key] = asyncReducer;
};
store.combineReducers = () => {
store.replaceReducer(combineReducers(store.asyncReducers));
};
// Return the modified store
return store;
}
In the code, snippet can be seen that configureStore
is now returning the store with two methods - injectReducer
and combineReducers
. The first one is used to add a new reducer to the store and the second one is used to combine all reducers into one.
configureStore
usageSince Store/Index/getStore
is using it in the same file, need to also overwrite it so that the correct configureStore
gets called.
injectReducer
Store/Index/injectStaticReducers
export default function injectStaticReducers(store) {
// eslint-disable-next-line no-param-reassign
store.asyncReducers = [];
// Inject all the static reducers into the store
Object.entries(getStaticReducers()).forEach(
([name, reducer]) => {
// eslint-disable-next-line no-param-reassign
store.asyncReducers[name] = reducer;
store.injectReducer(name, reducer);
}
);
store.combineReducers();
return store;
}
Util/DynamicReducers/Helper/injectToReducers
export default function injectToReducers(store, reducers) {
Object.keys(reducers).forEach((key) => {
if (!Reflect.has(store.asyncReducers, key)) {
// eslint-disable-next-line no-param-reassign
store.asyncReducers[key] = reducers[key];
store.injectReducer(key, reducers[key]);
}
});
store.combineReducers();
}
injectReducer
This is project specific, so need to search for where else could store.injectReducer
is used, and add combineReducers
similar to before.
Verify with the performance tab that the time of configureStore
has been reduced
Here can see that combineReducers
is called only once and whole configureStore
takes ~3x less time to process.