Home > OS >  removeChild() works as intended but gives an error?
removeChild() works as intended but gives an error?

Time:07-06

I made 'book' items using javascript in a div with the 'content' class and added an delete button on each of them dynamically so I can delete them. I'm trying to figure what what is wrong but I don't understand what I'm missing.

It seems to work as intended but in console it gives me an error that goes as following:

index.html:228 Uncaught TypeError: Failed to execute 'removeChild' on 'Node': 
parameter 1 is not of type 'Node'.
at HTMLButtonElement.<anonymous> (index.html:228:25)

but the item is removed successfully. What am I missing???

    //1. store the book objects in a simply array.
    const books = [];
    //2. add a function that takes the user's input. this function adds the object
    // in the array
    //3. write a function that loops through the array
    //and displays each book on the page in a card.
    // //4. add a 'new book' button so user can add a new book.
    const newBook = document.getElementById('new-book');
    const form = document.querySelector('form');
    const body = document.querySelector('body');
    const formButton = document.getElementById('form-button');
    const content = document.querySelector('.content');

    newBook.addEventListener('click', e => {
        toggleForm();
    })

    function toggleForm() {
        if (form.classList.value === 'form-hidden') {
            form.className = 'form-visible';
        }
        else {
            form.className = 'form-hidden';
        }
    }

    formButton.addEventListener('click', e => {
        const obj = {};
        const inputs = Array.from(document.querySelectorAll('input'));
        inputs.forEach(input => {
            obj[input.name] = input.value;
        })
        books.push(obj);
        inputs.forEach(input => {
            input.value = '';
        })
        toggleForm();
        createBooks();
        const deleteBtns = Array.from(document.querySelectorAll('#delete-btn'));
        deleteBtns.forEach(btn => {
            btn.addEventListener('click', e => {
                const btnDataKey = parseInt(btn.getAttribute('data-key'));
                content.removeChild(document.getElementById(btnDataKey));
            })
        })
    })

    function createBooks() {
        const bookDiv = document.createElement('div');
        bookDiv.className = 'book';
        const bookObj = books.length-1;
        for (const key in books[bookObj]) {
            const bookGrid = document.createElement('div');
            bookGrid.className = 'book-grid';
            const h3 = document.createElement('h1');
            h3.textContent = `${key}:`;
            const p = document.createElement('h2');
            p.textContent = books[bookObj][key];
            h3.className = 'long';
            p.className = 'long';
            bookGrid.append(h3, p);
            bookDiv.appendChild(bookGrid);
            const btn = document.createElement('button');
            btn.style.top = '0.3rem';
            btn.style.right = '.5rem';  
            btn.style.position= 'absolute';
            btn.textContent = 'Delete';
            btn.className = 'button-1';
            btn.style.fontSize = '1rem';
            btn.setAttribute('id', 'delete-btn');
            btn.setAttribute('data-key', `${bookObj}`);
            // const number = btn.getAttribute('data-key');
            // console.log(number);
            bookDiv.appendChild(btn);
        }
        bookDiv.setAttribute('id', `${bookObj}`);
        console.log (`this is the id of bookDiv ${bookDiv.id}`);
        bookDiv.style.position='relative';
        content.appendChild(bookDiv);
    }
* {
        padding: 0px;
        margin: 0px;
        font-family: 'ComiliBook', Arial, Helvetica, sans-serif;
        font-weight: normal;
        font-style: normal;
    }

    body {
        min-height: 100vh;
        display: grid;
        grid-template-rows: 1fr 6fr 1fr;
        overflow: auto;
    }

    .nav,
    .main,
    .footer {
        display: flex;
        justify-content: center;
        align-items: center;
        background-color: black;
        color: white;
        font-size: 2.5rem;
    }

    .main {
        background-color: white;
        color: black;
        font-size: 1rem;
        padding: 3.5rem;
    }

    .main>.content {
        width: 100%;
        height: 100%;
        border: solid 1px black;
        border-radius: 10px;
        position: relative;
        display: grid;
        margin: -2rem;
        padding: 2rem;
        gap: 1rem;
        grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
        background-color: RGB(240, 240, 240);
    }

    .form-container {
        padding: 1rem;
        position: absolute;
        top: .1rem;
        left: .1rem;
        z-index: 1;
    }

    .button-1,
    .button-2 {
        padding: .2rem .5rem;
        font-size: 1.5rem;
        background-color: white;
        color: black;
        border: 1px solid black;
        border-radius: 10px;
    }

    .button-2 {
        font-size: 1rem;
    }

    .button-1:hover,
    .button-2:hover {
        background-color: black;
        color: white;
    }

    .button-1:active,
    .button-2:active {
        background-color: black;
        color: white;
        transform: translate(0px, .2rem);
    }

    form {
        border: solid 1px black;
        position: absolute;
        padding: 1rem;
        display: grid;
        gap: .5rem;
        border-radius: 10px;
        background-color: black;
        color: white;
        font-size: 1.3rem;
    }

    .form-hidden {
        visibility: hidden;
    }

    .form-visible {
        visibility: visible;
    }

    .book {
        border: 1px solid black;
        padding: 1rem;
        display: grid;
        grid-template-rows: repeat(auto, 1fr);
        border-radius: 10px;
        align-items: center;
        background-color: gray;
        gap: 1rem;
    }

    .book-grid {
        display: grid;
        grid-template-columns: 1fr 2fr;
        border-radius: 10px;
        padding: 0.5rem;
        background-color: white;
    }

    h1, h2 {
        font-size: 1rem;
        text-overflow: ellipsis;
        font-family: Arial, Helvetica, sans-serif;
    }

    h2 {
        text-align: start;
    }

    .long {
        text-overflow: ellipsis;
        white-space: nowrap;
        overflow: hidden;
    }

    .delete-btn {
        top: 1rem;
        left: 1rem;
    }
<!DOCTYPE html>
<html lang="en">

<head>
    <link rel="stylesheet" media="screen" href="https://fontlibrary.org//face/comili" type="text/css" />
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div >My Home Library</div>
    <div >
        <div >
            <div >
                <button id="new-book" >New Book</button>
                <form action="#" method="post" class='form-hidden'>
                    <div>
                        <label for="author">Author:</label>
                        <input type="text" name="Author" id="author">
                    </div>
                    <div>
                        <label for="title">Title:</label>
                        <input type="text" name="Title" id="title">
                    </div>
                    <div>
                        <label for="pages">Number of pages:</label>
                        <input type="number" name="Pages" id="pages">
                    </div>
                    <div>
                        <label for="status">Have I read the book?: </label>
                        <input type="text" name="Status" id="status">
                    </div>
                    <button  type="button" id="form-button">Add a new book</button>
                </form>
            </div>
        </div>
    </div>
    <div >footer</div>
</body>
</html>

CodePudding user response:

The problem is that with every click on the "Add a new book" button, your code adds click listeners to all delete buttons, also those that already have them.

So once you have 2 or more books, the first delete buttons will have more than one click handler doing the same job. Only the first one that runs will do the job successfully. The others, trying to delete the book element again, will fail with an error.

So... don't add click handlers like that: remove that part of the code that loops over the buttons and adds click handlers. Instead add one listener on the content-element, and check if the click came from any of the delete buttons. If so, perform the deletion.

There is much to comment on your code, which I do not address here. For instance, HTML should never have multiple elements with the same ID. That is invalid. But I didn't touch any off that, so that you can see exactly what I changed to solve the problem you raised:

//1. store the book objects in a simply array.
    const books = [];
    //2. add a function that takes the user's input. this function adds the object
    // in the array
    //3. write a function that loops through the array
    //and displays each book on the page in a card.
    // //4. add a 'new book' button so user can add a new book.
    const newBook = document.getElementById('new-book');
    const form = document.querySelector('form');
    const body = document.querySelector('body');
    const formButton = document.getElementById('form-button');
    const content = document.querySelector('.content');

    newBook.addEventListener('click', e => {
        toggleForm();
    })

    function toggleForm() {
        if (form.classList.value === 'form-hidden') {
            form.className = 'form-visible';
        }
        else {
            form.className = 'form-hidden';
        }
    }
    
    content.addEventListener('click', e => {
        if (!e.target.textContent.includes("Delete")) return;
        const btnDataKey = e.target.getAttribute('data-key');
        content.removeChild(document.getElementById(btnDataKey));
    });

    formButton.addEventListener('click', e => {
        const obj = {};
        const inputs = Array.from(document.querySelectorAll('input'));
        inputs.forEach(input => {
            obj[input.name] = input.value;
        })
        books.push(obj);
        inputs.forEach(input => {
            input.value = '';
        })
        toggleForm();
        createBooks();
    })

    function createBooks() {
        const bookDiv = document.createElement('div');
        bookDiv.className = 'book';
        const bookObj = books.length-1;
        for (const key in books[bookObj]) {
            const bookGrid = document.createElement('div');
            bookGrid.className = 'book-grid';
            const h3 = document.createElement('h1');
            h3.textContent = `${key}:`;
            const p = document.createElement('h2');
            p.textContent = books[bookObj][key];
            h3.className = 'long';
            p.className = 'long';
            bookGrid.append(h3, p);
            bookDiv.appendChild(bookGrid);
            const btn = document.createElement('button');
            btn.style.top = '0.3rem';
            btn.style.right = '.5rem';  
            btn.style.position= 'absolute';
            btn.textContent = 'Delete';
            btn.className = 'button-1';
            btn.style.fontSize = '1rem';
            btn.setAttribute('id', 'delete-btn');
            btn.setAttribute('data-key', `${bookObj}`);
            // const number = btn.getAttribute('data-key');
            // console.log(number);
            bookDiv.appendChild(btn);
        }
        bookDiv.setAttribute('id', `${bookObj}`);
        console.log (`this is the id of bookDiv ${bookDiv.id}`);
        bookDiv.style.position='relative';
        content.appendChild(bookDiv);
    }
* {
        padding: 0px;
        margin: 0px;
        font-family: 'ComiliBook', Arial, Helvetica, sans-serif;
        font-weight: normal;
        font-style: normal;
    }

    body {
        min-height: 100vh;
        display: grid;
        grid-template-rows: 1fr 6fr 1fr;
        overflow: auto;
    }

    .nav,
    .main,
    .footer {
        display: flex;
        justify-content: center;
        align-items: center;
        background-color: black;
        color: white;
        font-size: 2.5rem;
    }

    .main {
        background-color: white;
        color: black;
        font-size: 1rem;
        padding: 3.5rem;
    }

    .main>.content {
        width: 100%;
        height: 100%;
        border: solid 1px black;
        border-radius: 10px;
        position: relative;
        display: grid;
        margin: -2rem;
        padding: 2rem;
        gap: 1rem;
        grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
        background-color: RGB(240, 240, 240);
    }

    .form-container {
        padding: 1rem;
        position: absolute;
        top: .1rem;
        left: .1rem;
        z-index: 1;
    }

    .button-1,
    .button-2 {
        padding: .2rem .5rem;
        font-size: 1.5rem;
        background-color: white;
        color: black;
        border: 1px solid black;
        border-radius: 10px;
    }

    .button-2 {
        font-size: 1rem;
    }

    .button-1:hover,
    .button-2:hover {
        background-color: black;
        color: white;
    }

    .button-1:active,
    .button-2:active {
        background-color: black;
        color: white;
        transform: translate(0px, .2rem);
    }

    form {
        border: solid 1px black;
        position: absolute;
        padding: 1rem;
        display: grid;
        gap: .5rem;
        border-radius: 10px;
        background-color: black;
        color: white;
        font-size: 1.3rem;
    }

    .form-hidden {
        visibility: hidden;
    }

    .form-visible {
        visibility: visible;
    }

    .book {
        border: 1px solid black;
        padding: 1rem;
        display: grid;
        grid-template-rows: repeat(auto, 1fr);
        border-radius: 10px;
        align-items: center;
        background-color: gray;
        gap: 1rem;
    }

    .book-grid {
        display: grid;
        grid-template-columns: 1fr 2fr;
        border-radius: 10px;
        padding: 0.5rem;
        background-color: white;
    }

    h1, h2 {
        font-size: 1rem;
        text-overflow: ellipsis;
        font-family: Arial, Helvetica, sans-serif;
    }

    h2 {
        text-align: start;
    }

    .long {
        text-overflow: ellipsis;
        white-space: nowrap;
        overflow: hidden;
    }

    .delete-btn {
        top: 1rem;
        left: 1rem;
    }
<!DOCTYPE html>
<html lang="en">

<head>
    <link rel="stylesheet" media="screen" href="https://fontlibrary.org//face/comili" type="text/css" />
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div >My Home Library</div>
    <div >
        <div >
            <div >
                <button id="new-book" >New Book</button>
                <form action="#" method="post" class='form-hidden'>
                    <div>
                        <label for="author">Author:</label>
                        <input type="text" name="Author" id="author">
                    </div>
                    <div>
                        <label for="title">Title:</label>
                        <input type="text" name="Title" id="title">
                    </div>
                    <div>
                        <label for="pages">Number of pages:</label>
                        <input type="number" name="Pages" id="pages">
                    </div>
                    <div>
                        <label for="status">Have I read the book?: </label>
                        <input type="text" name="Status" id="status">
                    </div>
                    <button  type="button" id="form-button">Add a new book</button>
                </form>
            </div>
        </div>
    </div>
    <div >footer</div>
</body>
</html>

CodePudding user response:

I was able to reproduce by adding two books, then deleting the first.

The problem is that you're adding an event listener again when the second form is submitted.

You need to assign a unique id to each delete button so that the listener is only added once, or better, don't use an ID at all.

The reason it works is:

  1. The first time the listener runs, the book is successfully removed.
  2. However, the second (or third, fourth) time the listener runs, it errors. This is becuase all of your Delete buttons have the same HTML ID and you use a foreach to add listeners.
  3. The reason you need at least two books to reproduce this issue is that the foreach will add the listener again to the first book, causing the bug (the second book will NOT have the bug).

Solution:

Move your event listener to the createBooks function, only adding it once.

            btn.setAttribute('data-key', `${bookObj}`);

            btn.addEventListener('click', e => {
                const btnDataKey = parseInt(btn.getAttribute('data-key'));
                console.log(document.getElementById(btnDataKey));
                content.removeChild(document.getElementById(btnDataKey));
            })

            // const number = btn.getAttribute('data-key');
            // console.log(number);

  • Related