Redux: Question: Mutation of an inner object in a state in a reducer

Created on 15 Apr 2016  ·  3Comments  ·  Source: reduxjs/redux

Is this a valid redux reducer?

function reducer(state={ byId: {}, ids: [] }, action) {
  switch (action.type) {
    case 'ADD':
      state = { ...state }
      state.byId[action.id] = action.value
      state.ids.push(action.id)
      return state

    default:
      return state
  }
}

The entire state is shallow cloned first, so a new object will be returned, but the inner objects stay the same and mutated.

Or do I have to do something like:

function reducer(state={ byId: {}, ids: [] }, action) {
  switch (action.type) {
    case 'ADD':
      return {
        ...state,
        byId: {
          ...state.byId,
          [action.id]: action.value
        },
        ids: [ ...state.ids, action.id ]
      }

    default:
      return state
  }
}

The side effects I see with the first approach are in DevTools LogMonitor:

  • expanding a sub node (e.g. byId) in a previous state will show the same value as the latest one even when it was something else previously
  • time travel isn't working

Is it just a bug in the LogMonitor or the second approach is the right one? If I add state = _.cloneDeep(state) before the mutations it actually works fine, proving that the LogMonitor reuses the same objects, hence the same values.

The rest of the app works fine and @connected views update correctly though.

Most helpful comment

The latter example is what you need to do. Obviously, that can get somewhat verbose if you're nesting things. There's several utility libraries that try to abstract that for you, and I have some of them listed over at in the Immutable Data page of my Redux libraries list.

Also reducer composition helps with this.

function byId(state = {}, action) {
  switch (action.type) {
    case 'ADD':
      return {
        ...state,
        [action.id]: action.value
      }
    default:
      return state
  }
}

function ids(state = [], action) {
  switch (action.type) {
    case 'ADD':
      return [ ...state, action.id ]
    default:
      return state
  }
}

const reducer = combineReducers({
  byId,
  ids
})

All 3 comments

Nope, that first example is absolutely mutating the state. The initial shallow copy creates a new object, but the new object points to the same byId and ids references as the original. So, when you do byId[action.id] = action.value and ids.push(action.id), you're mutating the original items.

The latter example is what you need to do. Obviously, that can get somewhat verbose if you're nesting things. There's several utility libraries that try to abstract that for you, and I have some of them listed over at in the Immutable Data page of my Redux libraries list.

The Redux FAQ also has a question that covers correctly updating state immutably: http://redux.js.org/docs/FAQ.html#react-not-rerendering .

The latter example is what you need to do. Obviously, that can get somewhat verbose if you're nesting things. There's several utility libraries that try to abstract that for you, and I have some of them listed over at in the Immutable Data page of my Redux libraries list.

Also reducer composition helps with this.

function byId(state = {}, action) {
  switch (action.type) {
    case 'ADD':
      return {
        ...state,
        [action.id]: action.value
      }
    default:
      return state
  }
}

function ids(state = [], action) {
  switch (action.type) {
    case 'ADD':
      return [ ...state, action.id ]
    default:
      return state
  }
}

const reducer = combineReducers({
  byId,
  ids
})

@gaearon @markerikson Thanks. I ended up writing a high-order-reducer that manages dynamic indexed lists (by ID)

Code: https://gist.github.com/elado/95484b754f31fcd6846c7e75de4aafe4

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mickeyreiss-visor picture mickeyreiss-visor  ·  3Comments

cloudfroster picture cloudfroster  ·  3Comments

jimbolla picture jimbolla  ·  3Comments

benoneal picture benoneal  ·  3Comments

amorphius picture amorphius  ·  3Comments