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:
byId
) in a previous state will show the same value as the latest one even when it was something else previouslyIs 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 @connect
ed views update correctly though.
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
Most helpful comment
Also reducer composition helps with this.