Source: app/util/reducers/combineReducersNamed.js

import combineReducers from 'redux/lib/combineReducers';

/**
 * Wrapper function to combinedReducer that will add a name to the reducer. The name will be
 * passed on to child named reducers, such that each reducer will automatically have a name
 * that matches the nesting of the reducer. For example, a reducer nested as:
 * ```
 * { view: { pages: { homePage: { myReducer } } } }
 * ```
 * will have the name `view.pages.homePage.myReducer`.
 *
 * This name is used by the `targetedReducer()` utility. It makes sure that the passed
 * reducer will only respond to actions with a `target` property in the action`meta` that matches
 * the reducer name.
 *
 * @function combineReducersNamed
 * @param reducerMap A map containing child reducers. This uses the same syntax as the redux
 * `combineReducers` parameter. See
 * {@link http://redux.js.org/docs/api/combineReducers.html|combineReducers}
 * @returns {function} A named reducer. This reducer should only be included in another named
 * reducer.
 */
export default reducerMap => {
  const namedReducer = name => {
    if (typeof name !== 'string') {
      // if the first argument is not a string, this reducer is probably
      // nested under a regular reducer
      throw new Error(
        'A reducer created with combineReducersNamed() should only be included in another named reducer.',
      );
    }

    const combineReducersMap = {};
    const postFixedName = name ? `${name}.` : name;

    Object.keys(reducerMap).forEach(
      reducerName =>
        (combineReducersMap[reducerName] = reducerMap[reducerName].isNamedReducer
          ? reducerMap[reducerName](`${postFixedName}${reducerName}`)
          : reducerMap[reducerName]),
    );

    return combineReducers(combineReducersMap);
  };

  namedReducer.isNamedReducer = true;
  return namedReducer;
};