State in redux/react app has a property with the name of the reducer

Karel Frajták picture Karel Frajták · Feb 27, 2016 · Viewed 7.3k times · Source

I am creating an app using Redux and React. I run into a problem where I cannot map state to component properties since the state has a property that matches the name of the reducer I used.

The root reducer is created with combineReducers method

const rootReducer = combineReducers({
  appReducer
});

The initial state is

const initialState = {
  sources: [], 
  left: {}, 
  right: {},
  diff: {} 
}

However in the component function mapStateToProps:

function mapStateToProps(state) {
  return {
    sources: state.sources
  }
}

The state.sources is undefined because the value of state parameter is

{
  appReducer: {
    sources: [], 
    left: {}, 
    right: {}, 
    diff: {}
  }
}

Is this a feature of redux? So when I use more reducers, all of them will add new property to state variable? Or is there something wrong on my side (I never noticed this behavior in redux tutorials).

Thanks

Answer

Dan Abramov picture Dan Abramov · Feb 27, 2016

If you only have a single reducer, you don’t need combineReducers(). Just use it directly:

const initialState = {
  sources: [],
  left: {},
  right: {}
}
function app(state = initialState, action) {
  switch (action.type) {
  case 'ADD_SOURCE':
    return Object.assign({}, state, {
      sources: [...state.sources, action.newSource]
    })
  case 'ADD_SOURCE_TO_LEFT':
    return Object.assign({}, state, {
      left: Object.assign({}, state.left, {
        [action.sourceId]: true
      })
    })
  case 'ADD_SOURCE_TO_RIGHT':
    return Object.assign({}, state, {
      right: Object.assign({}, state.right, {
        [action.sourceId]: true
      })
    })
  default:
    return state
  }
}

Now you can create a store with that reducer:

import { createStore } from 'redux'
const store = createStore(app)

And connect a component to it:

const mapStateToProps = (state) => ({
  sources: state.sources
})

However your reducer is hard to read because it update many different things at once. Now, this is the moment you want to split it into several independent reducers:

function sources(state = [], action) {
  switch (action.type) {
  case 'ADD_SOURCE':
    return [...state.sources, action.newSource]
  default:
    return state
  }
}

function left(state = {}, action) {
  switch (action.type) {
  case 'ADD_SOURCE_TO_LEFT':
    return Object.assign({}, state, {
      [action.sourceId]: true
    })
  default:
    return state
  }    
}

function right(state = {}, action) {
  switch (action.type) {
  case 'ADD_SOURCE_TO_RIGHT':
    return Object.assign({}, state, {
      [action.sourceId]: true
    })
  default:
    return state
  }    
}

function app(state = {}, action) {
  return {
    sources: sources(state.sources, action),
    left: left(state.left, action),
    right: right(state.right, action),
  }
}

This is easier to maintain and understand, and it also makes it easier to change and test reducers independently.

Finally, as the last step, we can use combineReducers() to generate the root app reducer instead of writing it by hand:

// function app(state = {}, action) {
//   return {
//     sources: sources(state.sources, action),
//     left: left(state.left, action),
//     right: right(state.right, action),
//   }
// }

import { combineReducers } from 'redux'
const app = combineReducers({
  sources,
  left,
  right
})

There is no large benefit to using combineReducers() instead of writing the root reducer by hand except that it’s slightly more efficient and will likely save you a few typos. Also, you can apply this pattern more than once in your app: it’s fine to combine unrelated reducers into a single reducer several times in a nested way.

All this refactoring would have no effect on the components.

I would suggest you to watch my free Egghead course on Redux which covers this pattern of reducer composition and shows how combineReducers() is implemented.