Combine redux reducers without adding nesting

OK, although the problem was already solved in the meantime, I just wanted to share what solution I came up:

    import { ActionTypes } from 'redux/lib/createStore'

    const mergeReducers = (...reducers) => {
        const filter = (state, keys) => (
          state !== undefined && keys.length ?

            keys.reduce((result, key) => {
              result[key] = state[key];
              return result;
            }, {}) :

            state
        );

        let mapping  = null;

        return (state, action) => {
            if (action && action.type == ActionTypes.INIT) {
                // Create the mapping information ..
                mapping = reducers.map(
                  reducer => Object.keys(reducer(undefined, action))
                );
            }
            return reducers.reduce((next, reducer, idx) => {
                const filteredState = filter(next, mapping[idx]);
                const resultingState = reducer(filteredState, action);

                return filteredState !== resultingState ?
                  {...next, ...resultingState} : 
                  next;

            }, state);
        };
    };

Previous Answer:

In order to chain an array of reducers, the following function can be used:

const combineFlat = (reducers) => (state, action) => reducers.reduce((newState, reducer) => reducer(newState, action), state));

In order to combine multiple reducers, simply use it as follows:

const combinedAB = combineFlat([reducerA, reducerB]);

Ok, decided to do it for fun, not too much code... This will wrap a reducer and only provide it with keys that it has returned itself.

// don't provide keys to reducers that don't supply them
const filterReducer = (reducer) => {
  let lastState = undefined;
  return (state, action) => {
    if (lastState === undefined || state == undefined) {
      lastState = reducer(state, action);
      return lastState;
    }
    var filteredState = {};
    Object.keys(lastState).forEach( (key) => {
      filteredState[key] = state[key];
    });
    var newState = reducer(filteredState, action);
    lastState = newState;
    return newState;
  };
}

In your tests:

const reducerA = filterReducer(combineReducers({ reducerA1, reducerA2 }))
const reducerB = filterReducer(combineReducers({ reducerB1, reducerB2 }))

NOTE: This does break with the idea that the reducer will always provide the same output given the same inputs. It would probably be better to accept the list of keys when creating the reducer:

const filterReducer2 = (reducer, keys) => {
  let lastState = undefined;
  return (state, action) => {
    if (lastState === undefined || state == undefined) {
      lastState = reducer(state, action);
      return lastState;
    }
    var filteredState = {};
    keys.forEach( (key) => {
      filteredState[key] = state[key];
    });
    return lastState = reducer(filteredState, action);
  };
}

const reducerA = filterReducer2(
  combineReducers({ reducerA1, reducerA2 }),
  ['reducerA1', 'reducerA2'])
const reducerB = filterReducer2(
  combineReducers({ reducerB1, reducerB2 }),
  ['reducerB1', 'reducerB2'])

Solution for those using Immutable

The solutions above don't handle immutable stores, which is what I needed when I stumbled upon this question. Here is a solution I came up with, hopefully it can help someone else out.

import { fromJS, Map } from 'immutable';
import { combineReducers } from 'redux-immutable';

const flatCombineReducers = reducers => {
  return (previousState, action) => {
    if (!previousState) {
      return reducers.reduce(
        (state = {}, reducer) =>
          fromJS({ ...fromJS(state).toJS(), ...reducer(previousState, action).toJS() }),
        {},
      );
    }
    const combinedReducers = combineReducers(reducers);
    const combinedPreviousState = fromJS(
      reducers.reduce(
        (accumulatedPreviousStateDictionary, reducer, reducerIndex) => ({
          ...accumulatedPreviousStateDictionary,
          [reducerIndex]: previousState,
        }),
        {},
      ),
    );
    const combinedState = combinedReducers(combinedPreviousState, action).toJS();
    const isStateEqualToPreviousState = state =>
      Object.values(combinedPreviousState.toJS()).filter(previousStateForComparison =>
        Map(fromJS(previousStateForComparison)).equals(Map(fromJS(state))),
      ).length > 0;
    const newState = Object.values(combinedState).reduce(
      (accumulatedState, state) =>
        isStateEqualToPreviousState(state)
          ? {
              ...state,
              ...accumulatedState,
            }
          : {
              ...accumulatedState,
              ...state,
            },
      {},
    );

    return fromJS(newState);
  };
};

const mergeReducers = (...reducers) => flatCombineReducers(reducers);

export default mergeReducers;

This is then called this way:

mergeReducers(reducerA, reducerB)

It produces no errors. I am basically returning the flattened output of the redux-immutable combineReducers function.

I have also released this as an npm package here: redux-immutable-merge-reducers.