#Existing Cart Item's attribute (product variant) changes when trying to add product to cart

22 messages · Page 1 of 1 (latest)

obsidian mountain
#

Greetings y'all,
So I'm working on this nextjs project (but the problem is react.js based really) and I came across a problem, that when I add an item (with a certain attribute/variant) to the cart, the same product that exists in the cart, changes its attribute to the one I'm selecting. which is not the intended behavior. And it's confusing the heck out of me.

the files that concern this problem (as far as I can gather) are :
MiniCart.jsx (the file responsible to show the cart when clicking on the shopping bag icon)
AppContext.jsx (context file, where all of the logic of the cart reducer lies)
ProductIntro.jsx (the page section where you can select the variant and click "add to cart")

code repo: https://github.com/mhdalihoria/sinbad-v2
The endpoint that has a product with a variant: /products/8847
(Note: the page is in arabic mostly, but I'll translate the parts where the problem is at)

GitHub

Contribute to mhdalihoria/sinbad-v2 development by creating an account on GitHub.

#

We are to be concerned with the second line that says:
الالوان: ازرق
which means: colors: blue

#

this one means colors: green

#

this one means colors: pink

#

I really hope I can get some help on this matter, as I have been pulling my hair about it for quite a long time so far, Thanks in advance

steep matrix
#

@obsidian mountain I believe the problem was the following:

  const selectAttr = (name, value) => {
    const existingItem = selectAttributes?.find((item) => item.name === name);
    setSelectAttributes((prevSelectAttributes) => {
      if (existingItem) {
        existingItem.value = value;
        return [existingItem];
      } else {
        return [...prevSelectAttributes, { name, value }];
      }
    });
  };

I am not sure why you checked for existing item here, but this caused the weird behaviour.
Simple remove the existing item check and set the state:

  const selectAttr = (name, value) => setSelectAttributes([{ name, value }]);
#

TLDR:
I believe the reason why you had this behaviour is the following line:
existingItem.value = value;
You are keep referring to the same object by it's reference and changes the value of it.
Each state value will have exactly the same attribute stored, you are mutating the reference of that object since you never create a new object you are keep finding the existing one in the state then you mutate its value.

In you code you have a list of attributes which is a list of objects.
Now, when we select a variant we populate the state which you do correctly by making a new list + a new object with the name + value.
(Now I still don't understand why this is necessary but I leave you to it)

Now the issue caused the following line:
existingItem.value = value;
It is because we have saved the selected variant in the state, then we try to find it.
We have successfully find it so we get back the reference of that object.
We mutate the value on this object and store it.
That object's reference will be saved and used inside our redux store.
Now since you have never created a new object attribute, you will keep reusing the same attribute object's reference in your application.
Each item will share the same attribute object.
Now since you do an on spot mutation using this object's reference, that will affect every item in your store hence all of them will have the same attribute value after the mutation.

#

I hope it helped. Good luck! 🙂

obsidian mountain
#

Thank you very much, you have done a splendid job in explaining why this have worked

#

To clarify something:
I did this

return [...prevSelectAttributes, { name, value }]

because I was told that a product can have different attributes, so each object is representing an attribute and its changes.

So I tried looking for the attribute that was getting changed, by looking it up using the "existingItem" stuff, and changing it's value

obsidian mountain
#

I looked up on how that can be done, and found this:

const selectAttr = (name, value) => {
    setSelectAttributes(prevSelectAttributes=> {
      const attributesArr = [...prevSelectAttributes]
      const indexOfAttr = attributesArr.findIndex(attr => attr.name === name)
      attributesArr[indexOfAttr] = {
        ...attributesArr[indexOfAttr],
        value: value
      }
      return attributesArr
    })
  };

You think this can do the trick in the perfect and appropriate manner?

steep matrix
#

I think there is no problem with the way it works.
If I add a blue and red shirt from the same type they will count as 2 items as they are different

#

Even on the invoice you will see 2 different items with 2 different references.

#

so I don't think there is anything wrong the way you store the values.

obsidian mountain
#

If I add a blue and red shirt from the same type they will count as 2 items as they are different
Right, but my concern was, a blue shirt with the size of XL for example, that's why I wanted an approach that doesn't entirely overwrite the state upon change

obsidian mountain
steep matrix
#

You have an action which is very "generic". I truly believe it does a bit too much, you can tell this from looking at the reducer.
I would break this down into different actions like:

dispatch({ type: "INCREASE_QUANTITY", payload: item.id })
dispatch({ type: "DECREASE_QUANTITY", payload: item.id })
dispatch({ type: "REMOVE_ITEM", payload: item.id })
dispatch({ type: "ADD_ITEM", payload: newItem })
dispatch({ 
  type: "SET_SIZE", 
  payload: { id: item.id, size: "xl" },
 })
// etc...
obsidian mountain
#

I see what you did there, splitting the dispatch down to multiple pieces can be very handy, especially as the projects grows bigger and more complex.

Thanks for your help, friend 💜

steep matrix
obsidian mountain
#

I appreciate that, it's definitely been overwhelming at the start, as it's my first soon-to-be production-ready project. But I'm trying to adapt. Thanks again for the help