Google Firebase — Firestore — обычный javascript — db.collection.update() изначально дублирует элемент списка перед обновлением браузера, почему?

# #javascript #firebase #google-cloud-firestore

Вопрос:

Я пытаюсь изучить JavaScript, и он используется в некоторых базовых веб-технологиях. Я следую фантастической серии NetNinja для создания базового приложения Google Firebase — Firestore. Большая часть кода ниже взята из класса, однако я пытаюсь добавить функцию для обновления логического поля после щелчка по элементу li (см. строку 26 — app.js). Обновление переключает значение логического значения.

Я испытываю проблему, когда элемент li дублируется и появляется дважды в списке, когда я обновляю браузер, дублированный элемент li исчезает. Значение поля обновляется в firestore правильно, и я могу видеть изменения в firestore — я просто не уверен в своей реализации функции db.collection().doc().update ().

Примечание — в настоящее время я только открываю index.html файл локально, в Chrome (но я не думаю, что это вызывает эту проблему). Я пытался выполнить обновление непосредственно в консоли, но, похоже, у меня такое же поведение, что заставляет меня думать, что это моя реализация функции обновления.

Любая помощь будет очень признательна, наряду с улучшениями и объяснениями. Заранее большое спасибо!

index.html

 <html>
    <head>
        <link rel="stylesheet" href="styles.css">
        <script src="https://www.gstatic.com/firebasejs/8.7.0/firebase-app.js"></script>
        <script src="https://www.gstatic.com/firebasejs/8.7.0/firebase-firestore.js"></script>
    </head>
    <body>

        <h1>Bob Things</h1>

        <div class="content">
            <form id="add-item-form">
                <input type="text" name="item" placeholder="Bob item">
                <input type="text" name="replenish" placeholder="true">
                <button>Add Item</button>
            </form>
            <ul id="item-list"></ul>
        </div>

        <script>
        // Initialise Firebase
        var firebaseConfig = {
            apiKey: "AIzaSyDccrM8EErMCUUc4XUdkEytPDwUuJryxrA",
            authDomain: "firestore-test-5b316.firebaseapp.com",
            projectId: "firestore-test-5b316",
            storageBucket: "firestore-test-5b316.appspot.com",
            messagingSenderId: "588262547847",
            appId: "1:588262547847:web:3bf82908cce90ec8e3f322"
        };
        // Initialize Firebase
        firebase.initializeApp(firebaseConfig);
        const db = firebase.firestore();
        db.settings({ timestampsInSnapshots:true ,merge:true});
        </script>

        <script src="app.js"></script>
    </body>
</html>
 

app.js

 const itemList = document.querySelector('#item-list');
const form = document.querySelector('#add-item-form');
const collectionVal = "bob_things"

// Create element and render info
function renderItems(doc){
    
    // Set variables and setup li elements
    let li = document.createElement('li');
    let item = document.createElement('span');
    let replenish = document.createElement('span');
    let cross = document.createElement('div');   

    li.setAttribute('data-id',doc.id);
    item.textContent = doc.data().item;
    replenish.textContent = doc.data().replenish;
    cross.textContent = 'x';

    // Add to main li
    li.appendChild(item);
    li.appendChild(replenish);
    li.appendChild(cross);
    itemList.appendChild(li);

    // Onclick should change
    li.addEventListener('click',(e)=>{
        console.log('here');
        let id = e.target.parentElement.getAttribute('data-id');
        let replenishNewVal = !doc.data().replenish;
        db.collection(collectionVal).doc(id).update({
            replenish: replenishNewVal
        })
        .then(() =>{
            console.log('Updated ' doc.data().item);
        })
        .catch((error)=> {
            console.error('Error updating',error);
        })
    })

    // Deleting a list item
    cross.addEventListener('click',(e)=>{
        e.stopPropagation();
        let id = e.target.parentElement.getAttribute('data-id');
        db.collection(collectionVal).doc(id).delete();
    })
}

// Saving Data
form.addEventListener('submit',(e) => {
    e.preventDefault();
    db.collection(collectionVal).add({
        item: form.item.value,
        replenish: form.replenish.value
    })
    form.item.value = '';
    form.replenish.value = '';
})

// Realtime listener
db.collection(collectionVal).onSnapshot(snapshot => {
    console.log('ran the listener');
    let changes = snapshot.docChanges();
    changes.forEach(change => {
        if (change.type == 'added' || change.type == 'modified'){
            renderItems(change.doc);
        } else if (change.type == 'removed'){
            let li = itemList.querySelector('[data-id=' change.doc.id ']');
            itemList.removeChild(li);
        }
    })
})
 

Комментарии:

1. Когда вы говорите, что «открываете index.html файл локально», вы имеете в виду, что используете локальный веб-сервер? Или вы имеете в виду, что просто открываете файл по file:// протоколу? Они очень разные.

Ответ №1:

Проблема в том, что здесь:

 db.collection(collectionVal).onSnapshot(snapshot => {
    console.log('ran the listener');
    let changes = snapshot.docChanges();
    changes.forEach(change => {
        if (change.type == 'added' || change.type == 'modified'){ // 👈 For new or updated item
            renderItems(change.doc); // 👈 add a new LI item to the HTML
        } else if (change.type == 'removed'){
            let li = itemList.querySelector('[data-id=' change.doc.id ']');
            itemList.removeChild(li);
        }
    })
})
 

Поэтому для каждого нового или обновленного элемента вы добавляете новый LI в HTML. Но когда документ обновляется в базе данных, вы должны фактически обновить соответствующий LI в HTML вместо создания нового.

Самое простое исправление, основанное на вашем существующем коде,-это удалить LI, а затем повторно добавить его:

 if (change.type == 'removed' || change.type == 'modified'){
    let li = itemList.querySelector('[data-id=' change.doc.id ']');
    itemList.removeChild(li);
}
if (change.type == 'added' || change.type == 'modified'){
    renderItems(change.doc);
}
 

Так что теперь modified обрабатывается дважды: один раз, чтобы удалить существующий LI, и один раз, чтобы добавить новый.


Хотя все вышесказанное работает, лучшим решением было бы найти существующий файл LI в HTML, а затем обновить его данными из измененного документа. Я рекомендую попробовать это самостоятельно, так как это даст вам более идиоматичное решение.

Комментарии:

1. Большое спасибо Фрэнку — это помогло! Большое спасибо за четко объясненный ответ.