Advait
Advait

Reputation: 13

Check for duplicates in a shopping cart and updating the quantity (ReactJS)

I have a shopping cart that is a nested list. I want to check if a certain item is already in the list before adding it again. If it is already in the list I just want to update the quantity. What am I doing wrong here, and how can I implement localstorage here (hypothetically)

Here is the function:

   function addToCart(e) {
    var updatedCart = [];
    if (cart.includes(e.target.innerText)) {
    console.log("Duplicate found");
    for (var i = 0; i < cart.length; i++) {
        if (cart[i][0] == e.target.innerText) {
        cart[i][1]++;
        updatedCart.push(cart[i]);
        i++;
        } else {
        i++;
        }
    }
    } else {
    setCart((cart) => [...cart, [e.target.innerText, 1]]);
    }
}

Upvotes: 1

Views: 1460

Answers (3)

kreysix
kreysix

Reputation: 359

Or basically, just use a Map. Helps in performance optimisations as well. Your cart structure would be like this:

{
  productId1:quantity1,
  productId2:quantity2,
  ...
  ..
}

A small snippet :

      let map = new Map(state.cart);
      let qty = (map.get(id) || 0) + quantity; //if item is not present will just add the item, else update its quantity
      if (qty !== 0) {
        map.set(id, qty);
      } else {
        map.delete(id); //else delete the item from cart
      }

Upvotes: 0

Yevhen Horbunkov
Yevhen Horbunkov

Reputation: 15530

Based on the line:

setCart((cart) => [...cart, [e.target.innerText, 1]]);

I may assume, that your cart contains array of arrays where inner arrays have product name and quantity as its first and second items, repsectively.

If I am correct with that assumption, the condition:

if (cart.includes(e.target.innerText))

Is never met, hence corresponding block never executed.

One more thing I may suggest is to use textContent rather then innerText (for performance considerations).

So, your addToCart() function may look something, like:

const addToCart = ({target:{textContent:itemName}}) => {
    const cartShallowCopy = [...cart],
            matchingItem = cartShallowCopy.find(([cartItemName]) =>
                cartItemName == itemName)
    matchingItem
        ? matchingItem[1]++
        : cartShallowCopy.push([itemName, 1])
    setCart(cartShallowCopy)
}

following is a proof-of-a-concept live-demo:

const { useState } = React,
      { render } = ReactDOM,
      rootNode = document.getElementById('root')
      
const App = () => {
  const [cart, setCart] = useState([]),
        addToCart = ({target:{textContent:itemName}}) => {
           const cartShallowCopy = [...cart],
                 matchingItem = cartShallowCopy.find(([cartItemName]) =>
                     cartItemName == itemName)
           matchingItem
             ? matchingItem[1]++
             : cartShallowCopy.push([itemName, 1])
           setCart(cartShallowCopy)
        }
  return (
    <div>
      <h4>These are products:</h4>
      <ul>
        {
          ['apple', 'banana', 'pear', 'peach'].map(name => (
            <li key={name}>
              <span 
                onClick={addToCart}
                className="productItem"
              >
                {name}
              </span>
            </li>
          ))
        }
      </ul>
      <h4>This is cart:</h4>
      <ul>
        { !!cart.length && cart.map(([name, qty]) => (
            <li key={name}>{name}, {qty}</li>
          ))
        }
      </ul>
    </div>
  )
}

render (
  <App />,
  rootNode
)
.productItem:hover {
  cursor: pointer;
  background-color: purple;
  color: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>

Upvotes: 1

Alpe89
Alpe89

Reputation: 299

As a suggestion, it is better (time-complexity wise) to store something like your cart in a javascript object or a map. You could store your item names (it would be better to have some sort of unique id) as the keys, and the item amount as the value; so basically something like that:

const cart = {
  itemA: 1,
  itemB: 2,
  itemC: 4
}

That way you could just update the value by copying the old state and overwrite only the value of the searched key if it exists

const cartCopy = {...cart};
if (!cartCopy[itemName]) {
  cartCopy[itemName] = 1;
} else {
  cartCopy[itemName] += 1;
}
setState(cartCopy);

You should improve that code by yourself and using this snippet just as some quick tip, as to use localStorage it would be not that hard. Especially with this object way of doing that; just remind that localStorage is slow to access, so I would suggest to update your component's state first, and when is updated just add into the localStorage your object with some kind of label. You should use localStorage only to have some kind of persistency of your cart through the sessions, so basically you'll load the state from localStorage only when you mount the app (if the cart is always visible) and update the localStorage when there is some update to the local state of your cart.

Upvotes: 2

Related Questions