javascript

reactjs

react-redux

redux

react-native

case 'ADD_TO_CART': {
    let item = action.payload;
    let newState = addToCart(state, item);
    return newState;
}
const increaseItem = (array = [], dish) => {
    array.filter((item) => {
        if (item.id === dish.id) {
            item.quantity++;
        }
    });
    return array;
}

case 'INCREASE_ITEM': {
    let item = action.payload;
    console.log('run in increase')
    let newState = increaseItem(state, item);
    return newState;
}

Here is the code. Problem is when the properties quantity increase, redux think the state has no change so mapStateToProps not run. Is there any solution for this please?

Solution 1

Array filter returns a new array, it does not update the original. Return directly or save to a variable to make changes

const increaseItem = (array = [], dish) => {
    return array.filter(//your code here);
    //return array;
}

// OR

const increaseItem = (array = [], dish) => {
    const newArray = array.filter(//your code here);
    return newArray;
}

However, this is not doing what you think it is. You should use map instead

const increaseItem = (array = [], dish) => {
    return array.map((item) => {
        if (item.id === dish.id) {
            item.quantity++;
        }
        return item; // Add this
    });
}

filter will only return values from the array if the callback function returns true. Your function isn't checking if it should filter, its trying to modify values (and is).

map will return the value of the callback for each index of the array. So your given callback should do what you expect if you return each item at the end as shown above.

The last piece of the problem is to ensure you don't mutate state. This is most likely the root your problem.

const increaseItem = (array = [], dish) => {
    return array.map((item) => {
      let item = {...item}; // Add this
        if (item.id === dish.id) {
            item.quantity++;
        }
        return item;
    });
}

With map and filter, you are creating a new state array. But, when doing item.quantity++;, you are mutating a nested object in both the original state and new state, as the nested objects are still using the same references. Creating a new object while mapping ensures that not only the main state array is new, but also any nested objects as well (this specific example only protects 1 deep).

Explanation

Its longer than the answer, but I wanted to make it clear.

The problem you're having is a very common one, and has to do with how JavaScript handles non-primitive data types. When you create an array or object and assign it to a variable, the variable doesn't actually contain the object, it contains a reference or a pointer to the object. The object itself is actually stored somewhere else in memory.

For clarity, lets just denote references by numbers surrounded by <>. Lets create an object:

let obj1 = {a: 'b'};

obj1 holds the reference to the new object we created, lets say the reference is <1>. Now lets make a copy of the object.

let obj1 = {a: 'b'};
let obj2 = obj1;

console.log(obj1);
console.log(obj2);

Since the variable holds a reference, what were actually assigning obj2 is the same reference of <1>.

obj1 
// reference: <1>
// value: {a: 'b'}

obj2
// reference: <1>
// value: {a: 'b'}

So the misconception comes in here because people assume that obj2 is now its own independent copy of the original. But as you see, they reference the same object in memory. The result is that doing something like obj2.a = 'c' now causes obj1.a to also equal 'c'.

Run the snippet below to see for yourself:

let obj1 = {a: 'b'};
let obj2 = obj1;

obj2.a = 'c';

console.log(obj1);
console.log(obj2);

How do we avoid making misleading copies?

The simplest way is to create a brand new object and just fill it with the old object's values by using spread syntax.

let obj1 = {a: 'b'};
let obj2 = {...obj1};

// obj1 
// reference: <1>
// value: {a: 'b'}

// obj2
// reference: <2>
// value: {a: 'b'}

obj2.a = 'c';

console.log(obj1);
console.log(obj2);

Now you can see that we have copied an object, but each reference their own objects in memory. This is almost always our desired behavior.

Things get extra confusing when we bring in nesting. But if you understand the basic idea it should make more sense.

let obj1 = {
  foo: 'bar',
  nested: {a: 'b'}
};

// obj1 
// reference: <1>
// value: {foo: 'bar', nested: <2>}

// nested
// reference: <2>
// value: {a: 'b'}

Nested objects get their own references too. So when we destructure to create a new object here's what we're doing.

let obj2 = {...obj1};

obj1 
// reference: <1>
// value: {foo: 'bar', nested: <2>}

nested
// reference: <2>
// value: {a: 'b'}

obj2
// reference: <3>
// value: {foo: 'bar', nested: <2>}

obj2 references a new place in memory, but the nested object still has the same reference as before!

So if we modify a nested property we have a similar behavior as before even though we made a new object at the top. This is called a "shallow copy". Try it out:

let obj1 = {
  foo: 'bar',
  nested: {a: 'b'}
};

let obj2 = {...obj1};

obj2.nested.a = 'c';

console.log(obj1);
console.log(obj2);

Solution: make new objects of all nested values as well.

let obj2 = {...obj1, nested: {...obj1.nested}};

Now we have successfully created an completely independent copy of a nested object.

obj1 
// reference: <1>
// value: {foo: 'bar', nested: <2>}

nested
// reference: <2>
// value: {a: 'b'}

obj2
// reference: <3>
// value: {foo: 'bar', nested: <4>}

nested
// reference: <4>
// value: {a: 'b'}

You may edit obj2 and its nested values with confidence that obj1 and it's nested values will remain unchanged.

let obj1 = {foo: 'bar', nested: {a: 'b'}};
let obj2 = {...obj1, nested: {...obj1.nested}};

obj2.nested.a = 'c';

console.log(obj1);
console.log(obj2);