How to sync UI with changes in virtual cart?

I am learning some front end design patterns, and I keep reading about how in a shopping cart example, you want to have a virtual shopping cart, and then have the UI listen for changes to it, and then reflect it on the page. But I have not found good examples of exactly how to “listen for changes and reflect it” particularly where I’m essentially syncing changes in multiple places.

Below I took a stab at a most rudimentary system. The code WORKS (fiddle here), however, it seems wildly brittle (especially since reality could be far more complex than this with a lot more things to have to match to find the right cart row. The changePriceInUI and removeFromUI functions, for example, rely on matchers, because I’ve learned that item.row can only reference 1 jQuery object in either Cart page or Total page. That’s why the code that’s commented out, if used, would only affect the UI on cart-body in the Total page. But the code that’s commented out is what makes most sense to me in terms of most accurately working with items in a cart and syncing changes.

For example, I can imagine item being a Class where each time the price is changed, there’s a setter function that updates the UI (but again, item.row is only 1 row). But I guess not? Would love to hear how shopping carts are built to sync up changes. Other learning resources welcome!

cart = []
cart_body = $(".cart-body")

function addItem(name, price) {
  item = {}
  item.name = name
  item.price = price  
  item.row = $("<tr class='item-row' data-name='"+name+"' data-price='"+price+"'><td class='name'>" + name + "</td><td class='price'>" + price + "</td></tr>")
  cart.push(item)
  addToUI(item.row)
}

function addToUI(item_row) {
  cart_body.append(item_row)
}

function removeItem(name,price) {
  var length = cart.length
    for (var i = 0; i < length; i++ ) {
    if (cart[i].name == name && cart[i].price == price) { 
      item_whose_row_to_remove = cart[i]
      cart.splice(i,1)
      break
    }
  }
  removeFromUI(item_whose_row_to_remove)
}

function removeFromUI(item) {
  // item.row.remove()
  cart_body.find(".item-row[data-name='"+item.name+"'][data-price='"+item.price+"']").remove()
}

function changeItemPrice(name,new_price) {
  var length = cart.length
    for (var i = 0; i < length; i++ ) {
    if (cart[i].name == name) { 
      old_price = cart[i].price
      cart[i].price = new_price
      item_whose_price_to_update = cart[i]
      break
    }
  }
  changePriceInUI(item_whose_price_to_update, old_price)
}

function changePriceInUI(item, old_price) {
  // item.row.find(".price").empty().append(item.price)
  cart_body.find(".item-row[data-name='"+item.name+"'][data-price='"+old_price+"']").find(".price").empty().append(item.price)
}


addItem("Ball", 1)
addItem("Stick",2)
removeItem("Ball",1)
changeItemPrice("Stick",3)