Changing state in Redux

- 1 answer

Ad

I am trying to add an element to an array in the state and change another array element's property. Say we have the following state structure:

{
  menuItems: [{
    href: '/',
    active: true
  }]
}

After dispatching the ADD_MENU_ITEM action I want to end up with this state:

{
  menuItems: [{
    href: '/new',
    active: true
  }, {
    href: '/',
    active: false,
  }]
}

I have tried managing this in Redux reducers in several fashions:

function reducer(state = {}, action) {
  switch (action.type) {
    case ADD_MENU_ITEM: {
      let menuItems = state.menuItems;
      let newMenuItem = action.newMenuItem; 

      // First try
      menuItems[0].active = false;
      menuItems.unshift(newMenuItem);
      state = Object.assign({}, state, { menuItems: menuItems });

      // Second try 
      menuItems[0].active = false;
      menuItems.unshift(newMenuItem);
      state = Object.assign({}, state, {menuItems: Object.assign([], menuItems)});

      // Third try 
      menuItems[0].active = false;
      state = (Object.assign({}, state, {
        menuItems: [
          Object.assign({}, newMenuItem), 
          ...menuItems
        ]
      }));

      // Fourth try
      menuItems[0].active = false;
      state = update(state, {
        menuItems: {$unshift: new Array(newMenuItem)}
      });

      console.log(state);
      return state;
    }
  }
}

In the fourth try, I am using React's Immutability Helpers but it never works. I logged the state to the console before returning the state and it logs correctly, but when logging inside the components which get re-rendered, the menuItems array does not add the first item, although the active member is set to false.

What could I be doing wrong?

Ad

Answer

Ad

The state in the reducer should be immutable and for this reason should not be modified. It is also recommended to flatten your object whenever possible.

In your scenario your initial state could be an array as such:

[{
    href: '/',
    active: true
  }]

In your reducer, try returning a brand new array as follows:

function reducer(state = {}, action) {
  switch (action.type) {
    case ADD_MENU_ITEM: {
      return [
        action.newMenuItem,
        ...state.map(item => Object.assign({}, item, { active: false }))
      ];
    }
  }
}

More information about reducers can be found here: Redux Reducers Documentation

Helpful excerpt from the documentation:

It’s very important that the reducer stays pure. Things you should never do inside a reducer:

  • Mutate its arguments;
  • Perform side effects like API calls and routing transitions;
  • Calling non-pure functions, e.g. Date.now() or Math.random().

MORE INFO ADDED

In your reducer, and for all four tries, you are modifying the existing state before returning it.

This results in react-redux, when checking if your state has changed, not to see any changes as both the previous and next states are pointing to the same object.

Here are the lines I am referring to:

First Try:

  // This line modifies the existing state.
  state = Object.assign({}, state, { menuItems: menuItems });

Second Try:

  // This line modifies the existing state.
  state = Object.assign({}, state, {menuItems: Object.assign([], menuItems)});

Third Try:

  // This line modifies the existing state.
  state = (Object.assign({}, state, {
    menuItems: [
      Object.assign({}, newMenuItem), 
      ...menuItems
    ]
  }));

Fourth Try:

  // This line modifies the existing state.
  state = update(state, {
    menuItems: {$unshift: new Array(newMenuItem)}
  });
Ad
source: stackoverflow.com
Ad