Search code examples
javascriptalpine.js

Alpine.js function to update totals when adding/removing items incorrect


I have a page that uses an Alpine.js template to add and remove sets of form fields for entering people's details and that is also supposed to update a price based on how many people there are which is passed as a hidden input.

It works fine when you add people, and also when you remove them again. But if you then try adding after removing, it gets the total incorrect.

The code is

<div x-data="add()" x-init="addNew">
    <div>
      <label for="price">Price:</label>
      <input type="text" name="price" id="price" value="100">
    </div>
    <template x-for="(field, index) in fields" :key="index">
      <div>
        <p>Person: <span x-text="`${index + 1}`"></span></p>
        <div>
          <label :for="`first_name_${index + 1}`">First name:</label>
          <input
            x-model="field.first_name"
            type="text"
            :name="`first_name_${index + 1}`"
            :id="`first_name_${index + 1}`"
          >
        </div>

        <div>
          <label :for="`last_name_${index + 1}`">Last name:</label>
          <input
            x-model="field.last_name"
            type="text"
            :name="`last_name_${index + 1}`"
            :id="`last_name_${index + 1}`"
          >      
        </div>

        <button type="button" @click="remove(index)">Remove</button>
      </div>

    </template>
    <button type="button" @click="addNew">Add</button>
</div>

<script>
  let priceInput = document.getElementById('price'),
      count = 1;

  function calcTotal(count) {
    if(count==1) priceInput.value = 200;
    if(count==2) priceInput.value = 375;
    if(count>2)  priceInput.value = 550;
  }

  function add() {
    return {
      fields: [],
      addNew() {
        this.fields.push({
          first_name: '',
          last_name: '',
        });
        calcTotal(count);
        count++;
      },
      remove(index) {
        this.fields.splice(index, 1);
        count = index;
        calcTotal(count);
      }
    }
  }
</script>

and I've created a CodePen.

Prices start at 200 for 1 person, then 375 for 2 and then are capped at 550 for 3 or more. If you test with the CodePen, you can see that the totals go correctly if you add up to 3 people and then remove back down to 1.

But when you get back down to 1 and add another again, the total doesn't update until you've added a 3rd (i.e. 375 instead of 550).

What do I need to do to fix it?

And also, unrelated to this specific issue, when I was creating the CodePen and chose Alpine as a js dependency, it automatically added Alpine 3.1.4.0 but my project was created originally with 2.x.x. With 3.x in the CodePen, nothing in the <template> would render. What's different between 2 and 3 that causes that?


Solution

  • You're setting and updating your count variable all wrong. I've fixed the script for you:

    <script>
      let priceInput = document.getElementById('price'),
          count = 0;
    
      function calcTotal(total) {
        console.log(total);
        if(total==1) priceInput.value = 200;
        if(total==2) priceInput.value = 375;
        if(total>2)  priceInput.value = 550;
      }
    
      function add() {
        return {
          fields: [],
          addNew() {
            this.fields.push({
              first_name: '',
              last_name: '',
            });
            ++count;
            calcTotal(count);
          },
          remove(index) {
            this.fields.splice(index, 1);
            --count;
            calcTotal(count);
          }
        }
      }
    </script>
    

    As you can see, I've set the count to 0 by default, since you use x-init to add a new field, which increments the count to 1 (in your case to 2, even though there was only one field).

    I changed the variable name in the calcTotal function to prevent variable conflict.

    I changed addNew to ensure it first increments and then calculates, since you first add new fields. I changed remove to ensure it first decrements and then calculates, since you first remove the fields. I don't use the index to set the count since that makes no sense whatsoever (Image you have 5 people, you delete number 2, so now you only have 2 left??).

    TIP: You could even make it simpler by just using this.fields.length and removing the count variable altogether (calcTotal(this.fields.length)).