5

I need to update multiple properties on my application state with single reducer. Later I'm using combineReducers redux helper that combines all the reducers. Problem is, all the other reducers operate one level down from the root of the state (that is, on a property), except this one that I need to pass the whole state to.

How do I use the combineReducers function to pass the root state?

const App = combineReducers({
  pages: combineReducers({
    browse: combineReducers({
      numOfItems: loadMoreItems,
      filter: setFilter
    })
  }),
  <rootState>: openPage,
  favoriteItems: addItemToFavorites,
  inBasketItems: addItemToBasket
})

What do I place in <rootState> ?

Aurimas
  • 2,577
  • 5
  • 26
  • 37
  • Possible duplicate of [Read Store's Initial State in Redux Reducer](http://stackoverflow.com/questions/33749759/read-stores-initial-state-in-redux-reducer) – Ilanus Jan 21 '17 at 15:58
  • It did help to understand some things better, but can't find the answer there. – Aurimas Jan 21 '17 at 16:33

3 Answers3

6

The Redux docs cover this topic in the section Structuring Reducers - Beyond combineReducers. Basically, you need to use more than just combineReducers to accomplish what you want. I also give an example of writing some custom reducer structuring logic in my recent blog post Practical Redux, Part 7: Form Change Handling, Data Editing, and Feature Reducers.

Nero Vanbiervliet
  • 863
  • 2
  • 9
  • 16
markerikson
  • 63,178
  • 10
  • 141
  • 157
2

After some refactoring I found that best is not to use combineReducers in this situation at all. Instead I structured my root reducer like so:

const App = (state = initialState, action) => {
  state = Object.assign({}, state)

  switch (action.type) {
    case OPEN_PAGE:
      state = openPage(state, action)
    case LOAD_MORE_ITEMS:
      state.pages.browse.numOfItems = loadMoreItems(state.pages.browse.numOfItems, action)
    case SET_FILTER:
      state.pages.browse.filter = setFilter(state.pages.browse.filter, action)
    case ADD_ITEM_TO_FAVORITES:
      state.favoriteItems = addItemToFavorites(state.favoriteItems, action)
    case ADD_ITEM_TO_BASKET:
      state.inBasketItems = addItemToBasket(state.inBasketItems, action)
  }

  return state
}

Maybe it would make sense to use combineReducers if any of reducers that work on particular part of state were more complicated, but it worked like this for me. I still kept some boilerplate in sub-reducers for readability purpose, like so:

const loadMoreItems = (state = initialState.pages.browse.numOfItems, action) => {
  switch (action.type) {
    case LOAD_MORE_ITEMS:
      return state = action.number
    default:
      return state
  }
}

So basically I keep the state initialization and action name since it makes it easier to understand what this reducer is all about.

Aurimas
  • 2,577
  • 5
  • 26
  • 37
  • 1
    Be careful with the logic there. The idea of calling other functions is right, but you're mutating the state with the results. You need to make sure that _every_ level of nesting gets copied. See http://redux.js.org/docs/recipes/reducers/ImmutableUpdatePatterns.html for more info. – markerikson Jan 22 '17 at 19:18
  • @markerikson , do you mean that the reducers could be called asyncroniously? Because the reducer is handling only one single action at a time, meaning the state mutates only that much. – Aurimas Jan 29 '17 at 23:29
  • Uh... no, that's not at all what I'm talking about. What I mean is that when you want to update `state.pages.browse.filter`, you actually need to make copies of `browse`, `pages`, and `state`. Otherwise, you've directly mutated the existing `browse` object. Please read the linked page for an explanation, particularly this subsection: http://redux.js.org/docs/recipes/reducers/ImmutableUpdatePatterns.html#correct-approach-copying-all-levels-of-nested-data . – markerikson Jan 30 '17 at 22:24
  • If you look at the beginning of the code - I'm actually using `state = Object.assign({}, state)` to copy the whole state and then I mutate the copy. Does that solve the problem? – Aurimas Jan 31 '17 at 04:07
  • 2
    No, because that's still only copying the _first_ level. You need to copy _all_ the levels of nesting. Again, the page I've linked explains this idea, and gives an example of how to properly update a nested field. – markerikson Jan 31 '17 at 19:36
1

If I understood you well, you could define another reducer called rootState and simple pass it to combine reducer as any other reducer.

On the other hand, if you want to update several reducers with the same action, you just need to add a case in each reducer the action should change the state.

Nicolas
  • 1,193
  • 1
  • 10
  • 25
  • Thanks, but what would the rootReducer look like in this case? .. the point is that Redux will look for the specified *keys* in the initial state passed to store and will complain if he can't find "favoriteItems" or "inBasketItems" for example. – Aurimas Jan 21 '17 at 16:33
  • All keys in combine reducer have to have a redux reducer as value, as well as each reducer should have a default state when first initialized. Si in this case, "favoriteItems" will have the "addItemToFavorites" reducer as value, and this will have a default state value, in other to never return undefined. – Nicolas Jan 21 '17 at 17:44
  • I'm not sure you understand my question. Do you know what the _keys_ mean in `combineReducer`? – Aurimas Jan 21 '17 at 22:46