简体   繁体   中英

Am I mutating the redux state in reducer?

I am trying to change one item in of an array in my state using a reducer.

State looks like:

state: {
  items: [
  {
    id: 1,
    name: 'Superman',
    wearsCape: true
  },
  {
    id: 2,
    name: 'Batman',
    wearsCape: true
  },
  {
    id: 3,
    name: 'Iron Man',
    wearsCape: false
  }
];
}

I am trying to filter through all the items in the state to search for first occurance of a superhero that has name equal sHero.name . Then I am trying to change a property of found superhero. The reducer code looks like:

function findCapedCrusader(state, { sHero }) {
  var result = state.items.filter(s => {
    return s.name === sHero.name;
  });

  result[0].wearsCape = false;

  return { ...state };

}

Am I mutating the state by doing the above??

NOTE: apologies for stupid example. I am new to react and redux.

The filter() method is returning a new array instance, so you are not mutating your state. For more information, see the MDN documentation :

Return value

A new array with each element being the result of the callback function.

Yes you are mutating your state in this example. While filter does produce a new array, it is really only a shallow copy of the original. The elements of your state.items array are objects, so when you modify one in your new results array (which contains pointers to the original contents of state.items ) you modify the original object. See this answer for a bit more on deep vs shallow copies: What is the difference between a shallow copy and a deep copy with JavaScript arrays?

To fix this you can either:

1. Use the spread operator on result and then overwrite the value of wearsCape like this:

result = state.items.filter(s => s.name === sHero.name).map(s => {
    return {...s, wearsCape: false}
});

This method works with serializable and non-serializable data and is the recommended way to update nested state values.

Or

2. Make a deep copy of state.items before filtering:

const results = JSON.parse(JSON.stringify(state.items)).filter(s => {
    return s.name === sHero.name;
});

This method is a bit strange but works well for serializable data in cases where the value you want to modify is deeply nested and using the spread operator ( ... ) many times would become cumbersome.

You are. Even though the filter function will produce a new array, the state is in the objects you return in that new array. Based on that, you are changing the state, which does not comply which what a pure function is.

Here I leave you with a good post that will help you to understand better what a deep copy is and how to accomplish it.

https://medium.com/@tkssharma/objects-in-javascript-object-assign-deep-copy-64106c9aefab

As far as I know, filter method returns a new array but the returned elements still reference the old ones. So, if someone mutates a property to the newly created array they also mutate the original one.

 const state = { items: [ { id: 1, name: 'Superman', wearsCape: true }, { id: 2, name: 'Batman', wearsCape: true }, { id: 3, name: 'Iron Man', wearsCape: false } ] }; const newItem = state.items.filter( s => s.name === "Superman"); newItem[0].wearsCape = false; console.log( "newitem", newItem, "\\noriginal", state.items[0] ); 

If you want to change one object's property in the array and update your state:

 const state = { items: [ { id: 1, name: 'Superman', wearsCape: true }, { id: 2, name: 'Batman', wearsCape: true }, { id: 3, name: 'Iron Man', wearsCape: false } ] }; const newItems = state.items.map( s => { if( s.name === "Superman") { return { ...s, wearsCape: false }; } return s; }); console.log( "new items", newItems, "original", state.items ); 

As you can see, the original items is not mutated. For your situation it will be something like that:

function findCapedCrusader(state, { sHero }) {
  var newItems = state.items.map(s => {
    if (s.name === sHero.name ) {
        return { ...s, wearsCape = false }
    }
    return s;
  });
  return { ...state, items: newItems };
}

Again, this function will change one object's property and does not change anything else. I'm not quite sure this is what you want since you are trying a filter in your question. If you want to update your state with one single object, I can suggest another solution.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM