1

I have some files that builds a cart in a dropdown for my shop website.

One file adds the selected item to an array which will be my cart. The other file is the CartDropdown component itself. My cart only show the items when I close and open it (remounting), but I want it to remount every time I add a new item.

Adding item function:

const ProductContainer = ({ productInfo }) => {
  const { cartProducts, setCartProducts } = useContext(CartContext);
  const cartArray = cartProducts;

  const addProduct = () => {
    productInfo.quantity = 1;
    if (cartArray.includes(productInfo)) {
      const index = cartArray.findIndex((object) => {
        return object === productInfo;
      });
      cartProducts[index].quantity++;
      setCartProducts(cartArray);
    } else {
      cartArray.push(productInfo);
      setCartProducts(cartArray);
    }
    // setCartProducts(cartArray)
    console.log(cartProducts);
    // console.log(cartArray)
  };
};

dropdown component

const CartDropdown = () => {
  const { setCartProducts, cartProducts } = useContext(CartContext);
  const { setProducts, currentProducts } = useContext(ProductsContext);
  // useEffect(() => {}, [cartProducts])
  const cleanCart = () => {
    const cleanProducts = currentProducts;
    console.log(cleanProducts);
    for (let i in cleanProducts) {
      if (cleanProducts[i].hasOwnProperty("quantity")) {
        cleanProducts[i].quantity = 0;
      }
    }
    setProducts(cleanProducts);
    setCartProducts([]);
  };
  return (
    <div className="cart-dropdown-container">
      <div className="cart-items">
        {cartProducts.map((product) => (
          <div key={product.id}>
            <img src={product.imageUrl}></img>
          </div>
        ))}
      </div>
      <button onClick={cleanCart}>CLEAN CART</button>
      <Button children={"FINALIZE PURCHASE"} />
    </div>
  );
};

How can I force the dropdown to remount every time cartProducts changes?

CART CONTEXT:

    export const CartContext = createContext({
    isCartOpen: false,
    setIsCartOpen: () => { },
    cartProducts: [],
    setCartProducts: () => { }
})

export const CartProvider = ({ children }) => {
    const [isCartOpen, setIsCartOpen] = useState(false)
    const [cartProducts, setCartProducts] = useState([])
    const value = { isCartOpen, setIsCartOpen, cartProducts, setCartProducts };
    return (
        <CartContext.Provider value={value}>{children}</CartContext.Provider>
    )
}

product context

    export const ProductsContext = createContext({
    currentProducts: null,
    setProducts: () => {}
})

export const ProductsProvider = ({children}) => {
    const [currentProducts, setProducts] = useState(shop_data)
    const value = {currentProducts, setProducts} 

    return(
        <ProductsContext.Provider value={value}>{children}</ProductsContext.Provider>
    )
}
juanOwl
  • 25
  • 5

2 Answers2

0

You can change the key prop of the component every time you want to remount. Every time cartProduct changes, update the value of key. You can do that using a useEffect with cartProduct as a dependency.

<CartDropdown key={1} />

to

<CartDropdown key={2} />

Edit for more clarification:

const [keyCount, setKeyCount] = useState(0);

useEffect(() => {
    setKeyCount(keyCount+1);
}, [cartProducts]);

<CartDropdown {...otherProps} key={keyCount} />
Neon
  • 490
  • 2
  • 9
  • let forceRemount = 1 useEffect(() => { forceRemount += 1 },[cartProducts]) – juanOwl Oct 14 '22 at 13:17
  • that would probably work well, but didnt. In fact, if i do usestate(console.log('A'), [cartProducts]) its not triggering everytime i add my cart product – juanOwl Oct 14 '22 at 13:30
  • That could be because cartProducts reference is not changing every time, hence useEffect might not get triggered. I suggest to use a more appropriate check (cartProducts.quantity or cartProducts.length or something else) based on what works for you, but this approach should work. You just need to figure out based on what you want to re-mount your component. – Neon Oct 14 '22 at 13:35
0

The first issue I see is that you are not using the callback to set the state inside the context but you are doing cartProducts[index].quantity++ and react docs specify

Do Not Modify State Directly

Also after cartProducts[index].quantity++, you call setCartProducts(cartArray); not with cartProducts which you actually updated (this is also the reason why "if I do usestate(console.log('A'), [cartProducts]) its not triggering everytime i add my cart product". But anyway there is an issue even if you would use cartArray for both:

You shouldn't directly do const cartArray = cartProducts since by doing so cartArray will be a reference to cartProducts (not a copy of it) which also shouldn't be modified (because it would mean that you are modifying state directly).

So first 2 things I recommend you to improve would be:

  1. Initialize cartArray as a cartProducts deep copy (if your cartProducts is an array of objects, spread syntax won't do it). So I would reccomand you to check this question answers for creating a deep copy.
  2. After you make sure that cartArray is a deep copy of cartProducts, doublecheck you use cartArray to create a local newValue then set the state of the context with the same value (so basically:
      cartArray[index].quantity++;
      setCartProducts(cartArray);

)

  1. The deep copy part also apply for const cleanProducts = currentProducts; (you should also create a deep copy here for cleanProducts, instead of saving the object ref).

If you are not using deep copies, your code might still work in some cases, but you might encounter weird behaviors in some other instances (and thoose are really hard to debug). Therefore is a bad practice in general not using deep copies.

Berci
  • 2,876
  • 1
  • 18
  • 28