Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Changing state in Redux

Tags:

reactjs

redux

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?

like image 983
Tommz Avatar asked Dec 24 '15 00:12

Tommz


People also ask

What is the only way to trigger a state change in Redux?

dispatch(action)​ Dispatches an action. This is the only way to trigger a state change. The store's reducing function will be called with the current getState() result and the given action synchronously.

How does Redux manage state?

Redux is a JavaScript library that depicts how an application's state should be managed and accessed. It supports state management via a centralized store. Though there are many libraries like Flux that support state management, data flow in Redux is clear and easier to integrate.

How do we return a new state in reducers of Redux?

Reducers are the only way to change states in Redux. It is the only place where you can write logic and calculations. Reducer function will accept the previous state of app and action being dispatched, calculate the next state and returns the new object.

How do you update a component in Redux state change?

The way you have it written, the state won't update unless you explicitly update it using setState() (most likely in the componentWillReceiveProps() method). When you use mapStateToProps() with the Redux connect() HOC, you are mapping your Redux state to your component through its props, so in your case this. props.


1 Answers

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)}
  });
like image 197
DDA Avatar answered Oct 10 '22 11:10

DDA