3

I thought assign was supposed to make a new object, that's why I did this in my reducer:

    case types.ADD_ATTRIBUTE:
      var newState = Object.assign({}, state)
      newState.attributes[action.industry].push(action.attribute)
      return Object.assign({}, state, newState);

    case types.REMOVE_ATTRIBUTE:
      var newState = Object.assign({}, state)
      var removeIndex = newState.attributes[action.industry].indexOf(action.attribute)
      newState.attributes[action.industry].splice(removeIndex, 1)
      return Object.assign({}, state, newState);

However, when I do this, the component will not trigger an update (componentWillReceiveProps). It does receive the new props, but the react-redux internal shouldComponentUpdate does not detect changes.

What am I doing wrong here?

BlockChange
  • 345
  • 1
  • 2
  • 10

4 Answers4

4

In case if you want to re-render object containing attributes[action.industry], you need to recreate this array same as you do with state.

case types.ADD_ATTRIBUTE:
  return {
    ...state,
    attributes: {
      ...state.attributes,
      [action.industry]: [...state.attributes[action.industry], action.attribute]
    }
  }

case types.REMOVE_ATTRIBUTE:
  const removeIndex = newState.attributes[action.industry].indexOf(action.attribute)
  return {
    ...state,
    attributes: {
      ...state.attributes,
      [action.industry]: [
          ...state.attributes[action.industry].slice(0, removeIndex), 
          ...state.attributes[action.industry].slice(removeIndex + 1)
        ]
      }
   }
elmeister
  • 184
  • 1
  • 3
  • 6
0

Here's how you could handle the types.ADD_ATTRIBUTE case:

With Object.assign:

const newActionIndustry = state.attributes[action.industry].concat(action.attribute)

const newAttributes = Object.assign({}, state.attributes, {
  [action.industry]: newActionIndustry
})

const newState =  Object.assign({}, state, {
  attributes: newAttributes
})

Handle the types.REMOVE_ATTRIBUTE case by yourself using this code.

Igorsvee
  • 4,101
  • 1
  • 25
  • 21
0

I eventually settled on this: (with some ES6 magic)

  case types.ADD_ATTRIBUTE:
      let newAttrState = state.attributes[action.industry].slice()
      newAttrState.push(action.attribute)
      return Object.assign({}, state, { attributes: { [action.industry]: newAttrState }} );

  case types.REMOVE_ATTRIBUTE:
      var newAttrState = state.attributes[action.userIndustry].slice()
      let removeIndex = newAttrState.indexOf(action.attribute)
      newAttrState.splice(removeIndex, 1)
      return Object.assign({}, state, { attributes: { [action.userIndustry]: newAttrState }} );

*Update: Which I now realize, is overriding the whole attributes object, with only one dynamically keyed array, while I need to sustain the other arrays stored in that object...

BlockChange
  • 345
  • 1
  • 2
  • 10
0

React-redux's shouldComponentUpdate() does a shallow comparison of the state in order to decide to render or not. This shallow comparison checks only one level depth of the object, it means that if you don't change the reference of the state itself or any of its first-level properties, it won't trigger an update of the component.

Your array is deeply nested in state.attributes[action.industry], and your action is not modifying state nor attributes, so react-redux won't update your component. In order to solve your problem, you need to change attributes[action.industry], creating a new array (e.g. using Array.concat() instead of Array.push() or using the spread operator like in attributes[action.industry] = [...attributes[action.industry], action.attribute ]

Alternatively, if you are using a stateful component you can create your own version of shouldComponentUpdate() that takes into account the attributes property in order to decide to render or not.

Manolo Santos
  • 1,915
  • 1
  • 14
  • 25