Implementing Firebase, giving a “is not a function” error whatever I do

Tags: , ,



I am trying to improve a question that got a few downvotes yesterday evening. I am still running into the same problem.

I am trying to work with Firebase realtime database. However it doesn’t matter how I try it, if I try to implement a function I get an error “this is not a function” and it doesn’t react to my clicks.

It’s a toggle button that I’m trying to get working.

The exact error is this:

myscript.js:138 Uncaught TypeError: myLibrary[i].toggleRead is not a function at HTMLButtonElement. (myscript.js:138)

I am going to include the HTML file, even the CSS file(as it isn’t that much), and the javascript one. I thought if things get big it’s bad but for completeness and making it easier to reproduce the error I am doing it this way. I am trying to learn from my mistakes in previous questions so please give me some feedback if this question isn’t good enough either. I’m relatively new to stackoverflow.

Here is the HTML file:

<!DOCTYPE html>
<html>
<head>
    <title>Page Title</title>
    <meta charset="UTF-8">
    <link href="styles/style.css" rel="stylesheet" type="text/css">
</head>
<body>
    <h1>Library</h1>
    <div id="container">
        <button id="addButton">+</button>
    </div>

    <!-- The core Firebase JS SDK is always required and must be listed first -->
    <script src="https://www.gstatic.com/firebasejs/8.1.2/firebase-app.js"></script>

    <!-- TODO: Add SDKs for Firebase products that you want to use
        https://firebase.google.com/docs/web/setup#available-libraries -->
    <script src="https://www.gstatic.com/firebasejs/8.1.2/firebase-database.js"></script>

    <script>
    // Your web app's Firebase configuration
    var firebaseConfig = {
     *removed*
    };
    // Initialize Firebase
    firebase.initializeApp(firebaseConfig);
    </script>

    <script src="javascript/myscript.js"></script>
</body>

Here the CSS:

body {
    background-color: tan;
}
h1 {
    font-size: 40px;
    margin: 20px 0px 20px 20px;
}
#container {
    display: grid;
    grid-template-columns: 200px 200px 200px 200px;
    grid-template-rows: 300px 300px 300px 300px 300px 300px 300px;
}
#addButton {
    background-color: purple;
    margin: 20px;
    font-size: 40px;
    color: white;

    border-color: black;
    border-width: 2px;
    border-style: solid;
    border-radius: 20px;

And Javascript:

let myLibrary = [];

// var database = firebase.database();

databaseOn();

function Book(title, author, pages, read) {
    this.title = title;
    this.author = author;
    this.pages = pages;
    this.read = read;
}

// Book.prototype.info = function() {
//     return `${this.title} by ${this.author}, ${this.pages} pages, read ${this.read}`
// }

Book.prototype.toggleRead = function() {
    this.read ? this.read = false : this.read = true;
}

function addBookToLibrary(boek) {
    myLibrary.push(boek)
}

const btn = document.querySelector('#addButton');
btn.addEventListener('click', () => {
    addNewBook();
});

function addNewBook() {
    addToArray();
    buttons();
    databaseStuff();
}

function addToArray() {
    let title = prompt("Please enter the title:", "");
    let author = prompt("Please enter the author:", "");
    let pages = parseInt(prompt("Please enter the number of pages", ""));
    let readStr = prompt("Have you read it yet? yes or no:");
    let read = false;
    while ((readStr !== "yes") || (readStr !== "no")) {
        if (readStr == "yes") {
            read = true;
            break
        } else if (readStr == "no") {
            read = false;
            break
        } else {
            readStr = prompt("Have you read it yet? yes or no:")
        }
    }

    // moet "new" voor Book als je classe weer gebruikt
    const boekAdd = new Book(title, author, pages, read);
    addBookToLibrary(boekAdd);
}

function databaseStuff() {
    firebase.database().ref('library').set(myLibrary);
}

function databaseOn() {
    let ref = firebase.database().ref('library');
    ref.on('value', (snapshot) => {
        console.log(snapshot.val());
        myLibrary = snapshot.val();
        buttons();
    })
}

function buttons() {
    const containerDiv = document.querySelector('#container')
    const buttonAdd = document.querySelector('#addButton')

    // const database = document.createElement('button');
    // const br = document.createElement('br');
    while ((containerDiv.firstChild) && (containerDiv.firstChild != buttonAdd)) {
        containerDiv.removeChild(containerDiv.firstChild);
    }
    // const lengLib = myLibrary.length;
    for (let i = 0; i < myLibrary.length; i++) {
        const div = document.createElement('div');
        const pBookTitle = document.createElement('p');
        const pBookAuthor = document.createElement('p');
        const pBookPages = document.createElement('p');
        const pBookRead = document.createElement('p');
        const btn = document.createElement('button');
        const toggle = document.createElement('button');

        div.setAttribute('id', `divNmb${i}`);
        div.setAttribute('style', 'margin: 20px; background-color: purple; text-align: center; border-color: black; border-width: 2px; border-style: solid; border-radius: 20px;')
        // div.textContent = `${myLibrary[i - 1].title}`;
        pBookTitle.setAttribute('id', `p1Nmb${i}`);
        pBookTitle.setAttribute('style', 'color: white;');
        pBookTitle.textContent = `${myLibrary[i].title}`;
        pBookAuthor.setAttribute('id', `p2Nmb${i}`);
        pBookAuthor.setAttribute('style', 'color: white;');
        pBookAuthor.textContent = `${myLibrary[i].author}`;
        pBookPages.setAttribute('id', `p3Nmb${i}`);
        pBookPages.setAttribute('style', 'color: white;');
        pBookPages.textContent = `${myLibrary[i].pages}`;
        pBookRead.setAttribute('id', `p4Nmb${i}`);
        pBookRead.setAttribute('style', 'color: white;');
        pBookRead.textContent = naamFunc(`${myLibrary[i].read}`);
        btn.setAttribute('id', `delNmb${i}`);
        btn.textContent = "Delete";
        toggle.setAttribute('id', `toggleNmb${i}`);
        toggle.textContent = "Toggle read";
        // database.setAttribute('id', `databaseNmb${lengLib}`)
        // database.textContent = "database";
        // br.setAttribute('id', `brNmb${lengLib}`);
    
        div.appendChild(pBookTitle);
        div.appendChild(pBookAuthor);
        div.appendChild(pBookPages);
        div.appendChild(pBookRead);
        div.appendChild(btn);
        div.appendChild(toggle);
        // div.appendChild(database);
        containerDiv.insertBefore(div, buttonAdd);
    
        btn.addEventListener('click', () => {
            myLibrary.splice(i, 1);
    
            div.removeChild(pBookTitle);
            div.removeChild(pBookAuthor);
            div.removeChild(pBookPages);
            div.removeChild(pBookRead);
            div.removeChild(btn);
            div.removeChild(toggle);
            // div.removeChild(database);
            containerDiv.removeChild(div);
        })

        toggle.addEventListener('click', () => {
            myLibrary[i].toggleRead();
            pBookRead.textContent = naamFunc(`${myLibrary[i].read}`);
        })

        function naamFunc(stringRead) {
            if (stringRead == "true") {
                return "Have Read";
            } else if (stringRead == "false") {
                return "Not Read";
            } else {
                console.log(stringRead);
            }
        }
    }
}

I’ve narrowed it down to the databaseOn() function. If I comment that out with the call to that function at the top it toggles just fine. And connecting to the database with “set”, through the addButton event (databaseStuff()), works as well.

Thanks.

Answer

I have tried your code in my browser I see while fetching from the server you are not casting your array of object to an array of books.

I have modified some of your code.

use of destructure object instead of multiple parameters

function Book({title, author, pages, read}){}

Cast server object to Book

 if(Array.isArray(snapshot.val())) {
            myLibrary = snapshot.val().map(data=>new Book(data))
  }
 else{
            myLibrary = [new Book(snapshot.val())]
     }

also way of adding book

 const boekAdd = new Book({title, author, pages, read});

Whole code in a single HTML file.

<!DOCTYPE html>
<html>

<head>
    <title>Page Title</title>
    <meta charset="UTF-8">
    <style>
        body {
            background-color: tan;
        }

        h1 {
            font-size: 40px;
            margin: 20px 0px 20px 20px;
        }

        #container {
            display: grid;
            grid-template-columns: 200px 200px 200px 200px;
            grid-template-rows: 300px 300px 300px 300px 300px 300px 300px;
        }

        #addButton {
            background-color: purple;
            margin: 20px;
            font-size: 40px;
            color: white;

            border-color: black;
            border-width: 2px;
            border-style: solid;
            border-radius: 20px;
        }
    </style>
</head>

<body>
    <h1>Library</h1>
    <div id="container">
        <button id="addButton">+</button>
    </div>

    <!-- The core Firebase JS SDK is always required and must be listed first -->
    <script src="https://www.gstatic.com/firebasejs/8.1.2/firebase-app.js"></script>

    <!-- TODO: Add SDKs for Firebase products that you want to use
        https://firebase.google.com/docs/web/setup#available-libraries -->
    <script src="https://www.gstatic.com/firebasejs/8.1.2/firebase-database.js"></script>

    <script>
        // Your web app's Firebase configuration
        var firebaseConfig = {
         *removed*
        };
        // Initialize Firebase
        firebase.initializeApp(firebaseConfig);
    </script>

   <script>
       let myLibrary = [];

// var database = firebase.database();

databaseOn();

function Book({title, author, pages, read}) {
    this.title = title;
    this.author = author;
    this.pages = pages;
    this.read = read;
}

// Book.prototype.info = function() {
//     return `${this.title} by ${this.author}, ${this.pages} pages, read ${this.read}`
// }

Book.prototype.toggleRead = function() {
    this.read ? this.read = false : this.read = true;
}

function addBookToLibrary(boek) {
    myLibrary.push(boek)
}

const btn = document.querySelector('#addButton');
btn.addEventListener('click', () => {
    addNewBook();
});

function addNewBook() {
    addToArray();
    buttons();
    databaseStuff();
}

function addToArray() {
    let title = prompt("Please enter the title:", "");
    let author = prompt("Please enter the author:", "");
    let pages = parseInt(prompt("Please enter the number of pages", ""));
    let readStr = prompt("Have you read it yet? yes or no:");
    let read = false;
    while ((readStr !== "yes") || (readStr !== "no")) {
        if (readStr == "yes") {
            read = true;
            break
        } else if (readStr == "no") {
            read = false;
            break
        } else {
            readStr = prompt("Have you read it yet? yes or no:")
        }
    }

    // moet "new" voor Book als je classe weer gebruikt
    const boekAdd = new Book({title, author, pages, read});
    addBookToLibrary(boekAdd);
}

function databaseStuff() {
    firebase.database().ref('library').set(myLibrary);
}

function databaseOn() {
    let ref = firebase.database().ref('library');
    ref.on('value', (snapshot) => {
        console.log(snapshot.val());
        if(Array.isArray(snapshot.val())){
            myLibrary = snapshot.val().map(data=>new Book(data))
        }else{
            myLibrary = [new Book(snapshot.val())]
        }
        buttons();
    })
}

function buttons() {
    const containerDiv = document.querySelector('#container')
    const buttonAdd = document.querySelector('#addButton')

    // const database = document.createElement('button');
    // const br = document.createElement('br');
    while ((containerDiv.firstChild) && (containerDiv.firstChild != buttonAdd)) {
        containerDiv.removeChild(containerDiv.firstChild);
    }
    // const lengLib = myLibrary.length;
    for (let i = 0; i < myLibrary.length; i++) {
        const div = document.createElement('div');
        const pBookTitle = document.createElement('p');
        const pBookAuthor = document.createElement('p');
        const pBookPages = document.createElement('p');
        const pBookRead = document.createElement('p');
        const btn = document.createElement('button');
        const toggle = document.createElement('button');

        div.setAttribute('id', `divNmb${i}`);
        div.setAttribute('style', 'margin: 20px; background-color: purple; text-align: center; border-color: black; border-width: 2px; border-style: solid; border-radius: 20px;')
        // div.textContent = `${myLibrary[i - 1].title}`;
        pBookTitle.setAttribute('id', `p1Nmb${i}`);
        pBookTitle.setAttribute('style', 'color: white;');
        pBookTitle.textContent = `${myLibrary[i].title}`;
        pBookAuthor.setAttribute('id', `p2Nmb${i}`);
        pBookAuthor.setAttribute('style', 'color: white;');
        pBookAuthor.textContent = `${myLibrary[i].author}`;
        pBookPages.setAttribute('id', `p3Nmb${i}`);
        pBookPages.setAttribute('style', 'color: white;');
        pBookPages.textContent = `${myLibrary[i].pages}`;
        pBookRead.setAttribute('id', `p4Nmb${i}`);
        pBookRead.setAttribute('style', 'color: white;');
        pBookRead.textContent = naamFunc(`${myLibrary[i].read}`);
        btn.setAttribute('id', `delNmb${i}`);
        btn.textContent = "Delete";
        toggle.setAttribute('id', `toggleNmb${i}`);
        toggle.textContent = "Toggle read";
        // database.setAttribute('id', `databaseNmb${lengLib}`)
        // database.textContent = "database";
        // br.setAttribute('id', `brNmb${lengLib}`);
    
        div.appendChild(pBookTitle);
        div.appendChild(pBookAuthor);
        div.appendChild(pBookPages);
        div.appendChild(pBookRead);
        div.appendChild(btn);
        div.appendChild(toggle);
        // div.appendChild(database);
        containerDiv.insertBefore(div, buttonAdd);
    
        btn.addEventListener('click', () => {
            myLibrary.splice(i, 1);
    
            div.removeChild(pBookTitle);
            div.removeChild(pBookAuthor);
            div.removeChild(pBookPages);
            div.removeChild(pBookRead);
            div.removeChild(btn);
            div.removeChild(toggle);
            // div.removeChild(database);
            containerDiv.removeChild(div);
        })

        toggle.addEventListener('click', (ev) => {
            console.log(i)
            console.log(myLibrary);
            myLibrary[i].toggleRead();
            pBookRead.textContent = naamFunc(`${myLibrary[i].read}`);
        })

        function naamFunc(stringRead) {
            if (stringRead == "true") {
                return "Have Read";
            } else if (stringRead == "false") {
                return "Not Read";
            } else {
                console.log(stringRead);
            }
        }
    }
}
   </script>
</body>

</html>



Source: stackoverflow