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.

1. How to spot the problem

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

InjectReducers method here takes ~ 190ms

2. How to fix the problem

2.1. Overwrite 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.

2.2. Take care of configureStore usage

Since Store/Index/getStore is using it in the same file, need to also overwrite it so that the correct configureStore gets called.

3. Adjust places that are using injectReducer

3.1. 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;
}

3.2. 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();
}

3.3. Check other places where the project might be using injectReducer

This is project specific, so need to search for where else could store.injectReducer is used, and add combineReducers similar to before.

4. Verifying fix

Verify with the performance tab that the time of configureStore has been reduced

Here can see that  is called only once and whole  takes ~3x less time to process.

Here can see that combineReducers is called only once and whole configureStore takes ~3x less time to process.