title: Combine redux reducers without adding nesting
body: <p>I have a scenario where I have 2 reducers that are the result of a <code>combineReducers</code>.  I want to combine them together, but keep their keys at the same level on nesting.</p>
  <p>For example, given the following reducers</p>
  <pre><code>const reducerA = combineReducers({ reducerA1, reducerA2 })
  const reducerB = combineReducers{{ reducerB1, reducerB2 })
  </code></pre>
  <p>I want to end up with a structure like:</p>
  <pre><code>{
      reducerA1: ...,
      reducerA2: ...,
      reducerB1: ...,
      reducerB2: ...
  }
  </code></pre>
  <p>If I use <code>combineReducers</code> again on <code>reducerA</code> and <code>reducerB</code> like so:</p>
  <pre><code>const reducer = combineReducers({ reducerA, reducersB })
  </code></pre>
  <p>I end up with a structure like:</p>
  <pre><code>{
      reducerA: {
          reducerA1: ...,
          reducerA2: ...
      },
      reducerB: {
          reducerB1: ...,
          reducerB2: ...
      }
  }
  </code></pre>
  <p>I can't combine <code>reducerA1</code>, <code>reducerA2</code>, <code>reducerB1</code> and <code>reducerB2</code> in a single <code>combineReducers</code> call as <code>reducerA</code> and <code>reducerB</code> are being provided to me already combined from different npm packages.</p>
  <p>I have tried using the <a href="https://github.com/acdlite/reduce-reducers" rel="noreferrer">reduce-reducers</a> library to combine them togethers and reduce the state together, an idea I got from looking at the <a href="https://redux.js.org/recipes/structuring-reducers/beyond-combinereducers#sharing-data-between-slice-reducers" rel="noreferrer">redux docs</a>, like so:</p>
  <pre><code>const reducer = reduceReducers(reducerA, reducerB)
  </code></pre>
  <p>Unfortunately this did not work as the resulting reducer from <code>combineReducers</code> producers a warning if unknown keys are found and ignores them when returning its state, so the resulting structure only contains that of <code>reducerB</code>:</p>
  <pre><code>{
      reducerB1: ...,
      reducerB2: ...
  }
  </code></pre>
  <p>I don't really want to implement my own <code>combineReducers</code> that does not enforce the structure so strictly if I don't have to, so I'm hoping someone knows of another way, either built-in to redux or from a library that can help me with this. Any ideas?</p>
  <hr />
  <h3>Edit:</h3>
  <p>There was an answer provided (it appears to have been deleted now) that suggested using <a href="https://github.com/SanCoder-Q/flat-combine-reducers/blob/master/src/flat-combine-reducers.js" rel="noreferrer">flat-combine-reducers</a> library:</p>
  <pre><code>const reducer = flatCombineReducers(reducerA, reducerB)
  </code></pre>
  <p>This was one step closer than reduce-reducers in that it managed to keep the keep the state from both <code>reducerA</code> and <code>reducerB</code>, but the warning messages are still being produced, which makes me wonder if the vanishing state I observed before was not <code>combineReducers</code> throwing it away, but rather something else going on with the reduce-reducers implementation.</p>
  <p>The warning messages are:</p>
  <blockquote>
  <p>Unexpected keys &quot;reducerB1&quot;, &quot;reducerB2&quot; found in previous state received by the reducer. Expected to find one of the known reducer keys instead: &quot;reducerA1&quot;, &quot;reducerA2&quot;. Unexpected keys will be ignored.</p>
  <p>Unexpected keys &quot;reducerA1&quot;, &quot;reducerA2&quot; found in previous state received by the reducer. Expected to find one of the known reducer keys instead: &quot;reducerB1&quot;, &quot;reducerB2&quot;. Unexpected keys will be ignored.</p>
  </blockquote>
  <p>If I do a production build, the warning disappear (such is the way for many react/redux warnings), but I'd rather them not appear at all.</p>
  <p>I've also done some more searching for other libraries and found <a href="https://github.com/ryo33/redux-concatenate-reducers" rel="noreferrer">redux-concatenate-reducers</a>:</p>
  <pre><code>const reducer = concatenateReducers([reducerA, reducerB])
  </code></pre>
  <p>This has the same result as flat-combine-reducers so the search continues.</p>
  <hr />
  <h3>Edit 2:</h3>
  <p>A few people have made some suggestions now but none have worked so far, so here is a test to help:</p>
  <pre><code>import { combineReducers, createStore } from 'redux'
  
  describe('Sample Tests', () =&gt; {
  
      const reducerA1 = (state = 0) =&gt; state
      const reducerA2 = (state = { test: &quot;value1&quot;}) =&gt; state
      const reducerB1 = (state = [ &quot;value&quot; ]) =&gt; state
      const reducerB2 = (state = { test: &quot;value2&quot;}) =&gt; state
      
      const reducerA = combineReducers({ reducerA1, reducerA2 })
      const reducerB = combineReducers({ reducerB1, reducerB2 })
  
      const mergeReducers = (...reducers) =&gt; (state, action) =&gt; {
          return /* your attempt goes here */
      }
  
      it('should merge reducers', () =&gt; {
          const reducer = mergeReducers(reducerA, reducerB)
  
          const store = createStore(reducer)
  
          const state = store.getState()
  
          const expectedState = {
              reducerA1: 0,
              reducerA2: {
                  test: &quot;value1&quot;
              },
              reducerB1: [ &quot;value&quot; ],
              reducerB2: {
                  test: &quot;value2&quot;
              }
          }
  
          expect(state).to.deep.equal(expectedState)
      })
  })
  </code></pre>
  <p>The goal is to get this test to pass <strong>AND</strong> not produce any warnings in the console.</p>
  <hr />
  <h3>Edit 3:</h3>
  <p>Added more tests to cover more cases, including handling an action after the initial creation and if the store is created with initial state.</p>
  <pre><code>import { combineReducers, createStore } from 'redux'
  
  describe('Sample Tests', () =&gt; {
  
      const reducerA1 = (state = 0) =&gt; state
      const reducerA2 = (state = { test: &quot;valueA&quot; }) =&gt; state
      const reducerB1 = (state = [ &quot;value&quot; ]) =&gt; state
      const reducerB2 = (state = {}, action) =&gt; action.type == 'ADD_STATE' ? { ...state, test: (state.test || &quot;value&quot;) + &quot;B&quot; } : state
      
      const reducerA = combineReducers({ reducerA1, reducerA2 })
      const reducerB = combineReducers({ reducerB1, reducerB2 })
  
      // from Javaguru's answer
      const mergeReducers = (reducer1, reducer2) =&gt; (state, action) =&gt; ({
          ...state,
          ...reducer1(state, action),
          ...reducer2(state, action)
      })
  
      it('should merge combined reducers', () =&gt; {
          const reducer = mergeReducers(reducerA, reducerB)
  
          const store = createStore(reducer)
  
          const state = store.getState()
  
          const expectedState = {
              reducerA1: 0,
              reducerA2: {
                  test: &quot;valueA&quot;
              },
              reducerB1: [ &quot;value&quot; ],
              reducerB2: {}
          }
  
          expect(state).to.deep.equal(expectedState)
      })
  
      it('should merge basic reducers', () =&gt; {
          const reducer = mergeReducers(reducerA2, reducerB2)
  
          const store = createStore(reducer)
  
          const state = store.getState()
  
          const expectedState = {
              test: &quot;valueA&quot;
          }
  
          expect(state).to.deep.equal(expectedState)
      })
  
      it('should merge combined reducers and handle actions', () =&gt; {
          const reducer = mergeReducers(reducerA, reducerB)
  
          const store = createStore(reducer)
  
          store.dispatch({ type: &quot;ADD_STATE&quot; })
  
          const state = store.getState()
  
          const expectedState = {
              reducerA1: 0,
              reducerA2: {
                  test: &quot;valueA&quot;
              },
              reducerB1: [ &quot;value&quot; ],
              reducerB2: {
                  test: &quot;valueB&quot;
              }
          }
  
          expect(state).to.deep.equal(expectedState)
      })
  
      it('should merge basic reducers and handle actions', () =&gt; {
          const reducer = mergeReducers(reducerA2, reducerB2)
  
          const store = createStore(reducer)
  
          store.dispatch({ type: &quot;ADD_STATE&quot; })
  
          const state = store.getState()
  
          const expectedState = {
              test: &quot;valueAB&quot;
          }
  
          expect(state).to.deep.equal(expectedState)
      })
  
      it('should merge combined reducers with initial state', () =&gt; {
          const reducer = mergeReducers(reducerA, reducerB)
  
          const store = createStore(reducer, { reducerA1: 1, reducerB1: [ &quot;other&quot; ] })
  
          const state = store.getState()
  
          const expectedState = {
              reducerA1: 1,
              reducerA2: {
                  test: &quot;valueA&quot;
              },
              reducerB1: [ &quot;other&quot; ],
              reducerB2: {}
          }
  
          expect(state).to.deep.equal(expectedState)
      })
  
      it('should merge basic reducers with initial state', () =&gt; {
          const reducer = mergeReducers(reducerA2, reducerB2)
  
          const store = createStore(reducer, { test: &quot;valueC&quot; })
  
          const state = store.getState()
  
          const expectedState = {
              test: &quot;valueC&quot;
          }
  
          expect(state).to.deep.equal(expectedState)
      })
  
      it('should merge combined reducers with initial state and handle actions', () =&gt; {
          const reducer = mergeReducers(reducerA, reducerB)
  
          const store = createStore(reducer, { reducerA1: 1, reducerB1: [ &quot;other&quot; ] })
  
          store.dispatch({ type: &quot;ADD_STATE&quot; })
  
          const state = store.getState()
  
          const expectedState = {
              reducerA1: 1,
              reducerA2: {
                  test: &quot;valueA&quot;
              },
              reducerB1: [ &quot;other&quot; ],
              reducerB2: {
                  test: &quot;valueB&quot;
              }
          }
  
          expect(state).to.deep.equal(expectedState)
      })
  
      it('should merge basic reducers with initial state and handle actions', () =&gt; {
          const reducer = mergeReducers(reducerA2, reducerB2)
  
          const store = createStore(reducer, { test: &quot;valueC&quot; })
  
          store.dispatch({ type: &quot;ADD_STATE&quot; })
  
          const state = store.getState()
  
          const expectedState = {
              test: &quot;valueCB&quot;
          }
  
          expect(state).to.deep.equal(expectedState)
      })
  })
  </code></pre>
  <p>The above <code>mergeReducers</code> implementation passes all the tests, but still producers warnings to the console.</p>
  <pre><code>  Sample Tests
      ✓ should merge combined reducers
      ✓ should merge basic reducers
  Unexpected keys &quot;reducerB1&quot;, &quot;reducerB2&quot; found in previous state received by the reducer. Expected to find one of the known reducer keys instead: &quot;reducerA1&quot;, &quot;reducerA2&quot;. Unexpected keys will be ignored.
  Unexpected keys &quot;reducerA1&quot;, &quot;reducerA2&quot; found in previous state received by the reducer. Expected to find one of the known reducer keys instead: &quot;reducerB1&quot;, &quot;reducerB2&quot;. Unexpected keys will be ignored.
      ✓ should merge combined reducers and handle actions
      ✓ should merge basic reducers and handle actions
      ✓ should merge combined reducers with initial state
      ✓ should merge basic reducers with initial state
      ✓ should merge combined reducers with initial state and handle actions
      ✓ should merge basic reducers with initial state and handle actions
  </code></pre>
  <p>It is important to note that the warnings being printed are for the test case immediately after and that <code>combineReducers</code> reducers will only print each unique warning once, so because I'm reusing the reducer between tests, the warnings are only shown for the first test case to produce it (I could combine the reducers in each test to prevent this, but as the criteria I'm looking for it to not produce them at all, I'm happy with this for now).</p>
  <p>If you are attempting this, I don't mind if <code>mergeReducers</code> accepts 2 reducers (like above), an array of reducers or an object of reducers (like <code>combineReducers</code>).  Actually, I don't mind how it is achieved as long as it doesn't require any changes to the creation of <code>reducerA</code>, <code>reducerB</code>, <code>reducerA1</code>, <code>reducerA1</code>, <code>reducerB1</code> or <code>reducerB2</code>.</p>
  <hr />
  <h3>Edit 4:</h3>
  <p>My current solution is modified from Jason Geomaat's answer.</p>
  <p>The idea is to filter the state being provided to the reducer using the keys of previous calls by using the following wrapper:</p>
  <pre><code>export const filteredReducer = (reducer) =&gt; {
      let knownKeys = Object.keys(reducer(undefined, { type: '@@FILTER/INIT' }))
  
      return (state, action) =&gt; {
          let filteredState = state
  
          if (knownKeys.length &amp;&amp; state !== undefined) {
              filteredState = knownKeys.reduce((current, key) =&gt; {
                  current[key] = state[key];
                  return current
              }, {})
          }
  
          let newState = reducer(filteredState, action)
  
          let nextState = state
  
          if (newState !== filteredState) {
              knownKeys = Object.keys(newState)
              nextState = {
                  ...state,
                  ...newState
              }
          }
  
          return nextState;
      };
  }
  </code></pre>
  <p>I merge the result of the filtered reducers using the redux-concatenate-reducers library (could have used flat-combine-reducers but the merge implementation of the former seems a bit more robust).  The <code>mergeReducers</code> function looks like:</p>
  <pre><code>const mergeReducers = (...reducers) =&gt; concatenateReducers(reducers.map((reducer) =&gt; filterReducer(reducer))
  </code></pre>
  <p>This is called like so:</p>
  <pre><code>const store = createStore(mergeReducers(reducerA, reducerB)
  </code></pre>
  <p>This passes all of the tests and doesn't produce any warnings from reducers created with <code>combineReducers</code>.</p>
  <p>The only bit I'm not sure about is where the <code>knownKeys</code> array is being seeded by calling the reducer with an <code>INIT</code> action.  It works, but it feels a little dirty.  If I don't do this, the only warning that is produced is if the store is created with an initial state (the extra keys are not filtered out when resolving the initial state of the reducer.</p>