skip to Main Content

I’m making an expense tracker with HTML, CSS, and JavaScript and I ran into a problem where when I first load up the page I can add as many div items I want, but then after I delete one (or more) of them, the adding button doesn’t work anymore. The error I get in the console is:

main.js:8 Uncaught TypeError: Cannot read properties of null (reading ‘appendChild’) at HTMLButtonElement.duplicate (main.js:8:25).

I have a function for removing a div (in this case the div is the expense item in the list), called deleteItem(), and another one for adding a div, called duplicate(). I’m not sure what is happening, but if anyone can help, that would be great.
I’ve seen a few possible solutions on YouTube that use JQuery, but since I’m still learning JavaScript, I’m trying not to get into that just yet. However, if the solution requires JQuery or anything like that, let me know.

document.getElementById('new-item-btn').onclick = duplicate;

var original = document.getElementById('expense-item');

function duplicate() {
  var clone = original.cloneNode(true);
  clone.id = "expense-item";
  original.parentNode.appendChild(clone);
}

function deleteItem() {
  var removeItem = document.getElementById('expense-item');
  removeItem.remove();
}
<main id="main">
  <div class="container" id="container">
    <div class="expense-item" id="expense-item">
      <button class="delete-btn" onclick="deleteItem()">Delete</button>
      <div class="expense-inputs">
        <label for="expense-name"></label>
        <input type="text" placeholder="Expense Name" id="expense-name">
        <label for="expense-category"></label>
        <select name="expense-category" id="expense-category">
          <option value="">Category</option>
          <option value="groceries">Groceries</option>
          <option value="housing">Housing</option>
          <option value="utilities">Utilities</option>
        </select>
        <label for="expense-amount"></label>
        <input type="text" id="expense-amount" name="expense-amount" placeholder="Amount">
      </div>
    </div>
  </div>
  <div class="info">
    <div class="new-item">
      <button class="new-item-btn" id="new-item-btn" onclick="duplicate()">New Item</button>
    </div>
  </div>
</main>

2

Answers


  1. What you can do to make the delete method dynamic and work without id is:

    function deleteItem(obj) { // <- obj refers to the clicked object because of `this` in  onclick="deleteItem()"
      var removeItem = obj.parentElement; // <- use parentElement to target the "<div class="expense-item" id="expense-item">"
      removeItem.remove();
    }
    

    Then you just need to add this to onclick="deleteItem()"

    Note I would advice you to still change the id to something new on the create but this is the example of the delete method.

    demo

    document.getElementById('new-item-btn').onclick = duplicate;
    
    var original = document.getElementById('expense-item');
    
    function duplicate() {
      var clone = original.cloneNode(true);
      original.parentNode.appendChild(clone);
    }
    
    function deleteItem(obj) {
      var removeItem = obj.parentElement;
      removeItem.remove();
    }
    <main id="main">
      <div class="container" id="container">
        <div class="expense-item" id="expense-item">
          <button class="delete-btn" onclick="deleteItem(this)">Delete</button>
          <div class="expense-inputs">
            <label for="expense-name"></label>
            <input type="text" placeholder="Expense Name" id="expense-name">
            <label for="expense-category"></label>
            <select name="expense-category" id="expense-category">
              <option value="">Category</option>
              <option value="groceries">Groceries</option>
              <option value="housing">Housing</option>
              <option value="utilities">Utilities</option>
            </select>
            <label for="expense-amount"></label>
            <input type="text" id="expense-amount" name="expense-amount" placeholder="Amount">
          </div>
        </div>
      </div>
      <div class="info">
        <div class="new-item">
          <button class="new-item-btn" id="new-item-btn" onclick="duplicate()">New Item</button>
        </div>
      </div>
    </main>
    Login or Signup to reply.
  2. You can, in most instances, remove IDs when creating dynamic content and use other means of identifying DOM nodes – such as querySelector in combination with event.target. Inspecting an Event to determine the origin of the click is probably the most reliable method.

    The dynamically added content does not know in advance IDs or other attributes so by supplying the event as an argument to the deleteItem method you can identify the newly added content from button to container and remove it quite easily.

    document.querySelector('.new-item-btn').onclick = duplicate;
    
    
    
    function duplicate() {
      const original = document.querySelector('[data-id="expense-item"]');
      original.parentNode.appendChild( original.cloneNode(true) );
    }
    
    function deleteItem(e) {
      let item = e.target.closest('.expense-item');
      if( document.querySelectorAll('.expense-item').length > 1 ){
          item.parentNode.removeChild( item );
      }
    }
    <main>
      <div class="container">
      
        <div class="expense-item" data-id="expense-item">
          <button class="delete-btn" onclick="deleteItem(event)">Delete</button>
          <div class="expense-inputs">
            <label>
              <input type="text" placeholder="Expense Name" />
            </label>
            <label>
              <select name="expense-category" data-id="expense-category">
                <option value="">Category</option>
                <option value="groceries">Groceries</option>
                <option value="housing">Housing</option>
                <option value="utilities">Utilities</option>
              </select>
            </label>
            <label>
              <input type="text" name="expense-amount" placeholder="Amount" />
            </label>
          </div>
        </div>
        
      </div>
    
      <div class="info">
        <div class="new-item">
          <button class="new-item-btn" data-id="new-item-btn" onclick="duplicate()">New Item</button>
        </div>
      </div>
    </main>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search