this question is probably really easy but I kind of got stuck with Firestore. It's my first project with firestore + js and I'm trying to receive and display data from my database. I have a feeling that I am doing something really stupid because it seems like I am looping through my data and overriding it that's why I can see only one article element even though there should be 2 fetched from the database.
document.addEventListener('DOMContentLoaded', event => {
const app = firebase.app();
const db = firebase.firestore();
const myProducts = db.collection('products');
myProducts.onSnapshot(products => {
const productsContainer = document.querySelector('#products__container');
products.forEach(doc => {
data = doc.data();
console.log(data);
let productMarkup = `<article id="${doc.id}">
<h4>${data.name}</h4>
<p>${data.price}</p>
</article>`;
productsContainer.innerHTML = productMarkup;
console.log(productMarkup);
});
});
});
After Alex suggestion, I decided to take the different approach to create DOM elements
const db = firebase.firestore();
const myProducts = db.collection('products');
const productsContainer = document.querySelector('#products__container');
function renderProduct(doc) {
const docFrag = document.createDocumentFragment();
let article = document.createElement('article');
let productName = document.createElement('h4');
let productPrice = document.createElement('p');
article.setAttribute('id', doc.id);
productName.textContent = doc.data().name;
productPrice.textContent = doc.data().price;
docFrag.appendChild(productName);
docFrag.appendChild(productPrice);
article.appendChild(docFrag);
productsContainer.appendChild(article);
}
myProducts.onSnapshot(products => {
products.forEach(doc => {
data = doc.data();
console.log(data);
renderProduct(doc);
});
});
});```
Related
I am trying to make barbershop web app where costumer can see list of free appointments and when they reserve free appointment I want to delete that field from firebase.
I have a collection which represents one barber.
This is how it looks in firebase.
As you see radno_vrijeme is object or map in firebase which contains 6 arrays, and in each array there is list of free working hours.
In my function I am able to do everthing except last line where I need to update firebase collection.
const finishReservation = async () => {
try {
const freeTimeRef = collection(db, `${barber}`);
const q = query(freeTimeRef);
const querySnap = await getDoc(q);
querySnap.forEach(async (doc) => {
const radnoVrijeme = doc.data().radno_vrijeme;
// Find the index of the hour you want to delete
const index = radnoVrijeme["Mon"].indexOf(hour);
// Remove the hour from the array
radnoVrijeme["Mon"].splice(index, 1);
// Update the document in the collection
console.log(radnoVrijeme);
const radnoVrijemeMap = new Map(Object.entries(radnoVrijeme));
await freeTimeRef.update({ radno_vrijeme: radnoVrijemeMap });
});
} catch (error) {
console.log(error);
}
};
I tried to pass it as JSON stringified object, but it didn't work. I always get this error :
"FirebaseError: Expected type 'ya', but it was: a custom Ia object"
When you are trying to fetch multiple documents using a collection reference or query, then you must use getDocs():
const finishReservation = async () => {
try {
const freeTimeRef = collection(db, `${barber}`);
const q = query(freeTimeRef);
const querySnap = await getDocs(q);
const updates = [];
querySnap.forEach((d) => {
const radnoVrijeme = d.data().radno_vrijeme;
const index = radnoVrijeme["Mon"].indexOf(hour);
radnoVrijeme["Mon"].splice(index, 1);
const radnoVrijemeMap = new Map(Object.entries(radnoVrijeme));
updates.push(updateDoc(d.ref, { radno_vrijeme: radnoVrijemeMap }))
});
await Promise.all(updates);
console.log("Documents updated")
} catch (error) {
console.log(error);
}
};
getDoc() is used to fetch a single document using a document reference.
i know that the problem is that let todoList is an empty array, but i dont know how to solve it.
the id tags in my created html is so e can create a delete button later
heres my code:
const textArea = document.querySelector("textarea");
const button = document.querySelector("button");
const listContainer = document.querySelector(".list-container");
let id = 0;
let todoList = [];
button.onclick = function () {
const listItem = {
title: textArea.value,
};
todoList.push(listItem);
addToStorage(todoList);
const dataFromStorage = getFromStorage();
createHtml(dataFromStorage);
};
function addToStorage(items) {
const stringify = JSON.stringify(items);
localStorage.setItem("list", stringify);
}
function getFromStorage() {
const data = localStorage.getItem("list");
const unstrigified = JSON.parse(data);
return unstrigified;
}
const createHtml = (data) => {
id++;
listContainer.innerHTML = "";
data.forEach((item) => {
listContainer.innerHTML += `<div class="list-item" data-id=${id}><p>${item.title} </p><button class="remove" data-id=${id}>Delete</button></div>`;
});
};
The problem here is you just forgot to load the data from localStorage when the page loaded like this
window.onLoad = () => {
const dataFromStorage = getFromStorage();
if(dataFromStorage){
createHtml(dataFromStorage);
} else {
createHtml([]);
}
}
The problem in the code is as follows
Initially the todolist will be an empty array. so when you do the below
todoList.push(listItem);
// adding to local storage which will override the existing todos when page is refreshed
addToStorage(todoList);
// So when the below line is executed only the latest todo will be returned
const dataFromStorage = getFromStorage();
createHtml(dataFromStorage);
Fix:
Initialise the todos from localstorage instead of an empty array
let todoList = [];
// change it as below
let todoList = getFromStorage();
Now Modify the getFromStorage() as below
// If the data is present in the localStorage then return it, else return empty array
function getFromStorage() {
const data = localStorage.getItem("list");
if (!data) return [];
const unstrigified = JSON.parse(data);
return unstrigified;
}
Now when the page is loaded, we need to display the todos. Add the below lines of code
window.onload = function () {
createHtml(todoList);
};
That's it. This will fix the issue.
Few minor improvements can be made as well.
todoList.push(listItem);
addToStorage(todoList);
const dataFromStorage = getFromStorage(); // this line is not necessary, remove it
createHtml(dataFromStorage); // change this to createHtml(todoList)
Codepen
Thanks.
I have a simple fetch function that gets data (messages from db) and putting it into an array to display it with simple vanilla JS. The thing is I am calling this function every 2 seconds in order to check for new messages. But when I do that I duplicate my messages and it keeps adding instead of replacing. I am struggling to understand what I should do to change, not add.
(a little dummy question, sorry)
const list = document.getElementById('message-list');
const getData = () => {
fetch('/messages')
.then((resp) => resp.json())
.then(function(data) {
console.log(data)
for (let i = 0; i < data.length; i++) {
const listItem = document.createElement('li');
listItem.innerText = data[i].message;
const delButton = document.createElement('button');
delButton.innerHTML = 'Delete';
delButton.addEventListener('click', ()=>{
const message_id = data[i].message_id;
deleteItem(message_id);
})
listItem.appendChild(delButton);
list.appendChild(listItem)
}
})
}
setInterval(getData,2000)
Make a Set of the message_ids processed so far, and on further calls, ignore messages matching that message_id:
const seenIds = new Set();
const getData = () => {
fetch('/messages')
.then((resp) => resp.json())
.then(function(data) {
data
.filter(({ message_id }) => !seenIds.has(seenIds))
.forEach(({ message, message_id }) => {
seenIds.add(message_id);
const listItem = document.createElement('li');
listItem.innerText = message;
const delButton = document.createElement('button');
delButton.textContent = 'Delete';
delButton.addEventListener('click', () => {
deleteItem(message_id);
});
listItem.appendChild(delButton);
list.appendChild(listItem)
});
});
};
That said, it would probably be better to change your backend so that it can filter the items for you, rather than sending objects over the net that proceed to get ignored.
I'm trying to improve a firestore get function, I have something like:
return admin.firestore().collection("submissions").get().then(
async (x) => {
var toRet: any = [];
for (var i = 0; i < 10; i++) {
try {
var hasMedia = x.docs[i].data()['mediaRef'];
if (hasMedia != null) {
var docData = (await x.docs[i].data()) as MediaSubmission;
let submission: MediaSubmission = new MediaSubmission();
submission.author = x.docs[i].data()['author'];
submission.description = x.docs[i].data()['description'];
var mediaRef = await admin.firestore().doc(docData.mediaRef).get();
submission.media = mediaRef.data() as MediaData;
toRet.push(submission);
}
}
catch (e) {
console.log("ERROR GETTIGN MEDIA: " + e);
}
}
return res.status(200).send(toRet);
});
The first get is fine but the performance is worst on the line:
var mediaRef = await admin.firestore().doc(docData.mediaRef).get();
I think this is because the call is not batched.
Would it be possible to do a batch get on an array of mediaRefs to improve performance?
Essentially I have a collection of documents which have foreign references stored by a string pointing to the path in a separate collection and getting those references has been proven to be slow.
What about this? I did some refactoring to use more await/async code, hopefully my comments are helpful.
The main idea is to use Promise.all and await all the mediaRefs retrieval
async function test(req, res) {
// get all docs
const { docs } = await admin
.firestore()
.collection('submissions')
.get();
// get data property only of docs with mediaRef
const datas = await Promise.all(
docs.map(doc => doc.data()).filter(data => data.mediaRef),
);
// get all media in one batch - this is the important change
const mediaRefs = await Promise.all(
datas.map(({ mediaRef }) =>
admin
.firestore()
.doc(mediaRef)
.get(),
),
);
// create return object
const toRet = datas.map((data: MediaSubmission, i) => {
const submission = new MediaSubmission();
submission.author = data.author;
submission.description = data.description;
submission.media = mediaRefs[i].data() as MediaData;
return submission;
});
return res.status(200).send(toRet);
}
i have a data structure as below
I created an array called new array with the IDs such as [19777873, 53399293]
var dbRef = firebase.database().ref().child('Agents').child(newarray[i]);
dbRef.on('value', snapshot => {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
database = firebase.database();
console.log("Testing if the array values are here \n" + newarray);
// var dbRef = firebase.database().ref().child('Agents').child(newarray[i]);
dbRef.on('value', newAgents, errData);
}
})
}
New Agent function
function newAgents(data) {
var container = document.getElementById("team");
container.innerHTML = '';
data.forEach(function(AgentSnap) { // loop over all jobs
var key = AgentSnap.AgentID;
console.log(AgentSnap.key);
var Agents = AgentSnap.val();
var AgentCard = `
<div class= "profilepics" id="${key}">
<figure ><img src=${Agents.profilepicurl}><figcaption>${Agents.Fname}</figcaption></figure>
</div>
`;
container.innerHTML += AgentCard;
})
}
the problem I'm having now is that images(from {Agents.profilepicurl}) are being displayed and name (from {Agents.Fname}) are not being displayed. instead of name it shows "undefined" and no error is show in console. What am I doing wrong and how can I fix this?
There are some strange things happening in the first few lines of your code. You are setting the "on" listener twice (ignoring the resulting snapshot for the first listener), and you're awaiting authentication and setting a database object without using it. I am not absolutely certain about what you're trying to achieve, but would this work? It's a simplified version of what I think you're trying to do with the current code:
console.log("Testing if the array values are here \n" + newarray);
firebase.auth().onAuthStateChanged((user) => {
if (user) {
document.getElementById("team").innerHTML = '';
database = firebase.database();
for (agentId of newarray) {
var dbRef = database.ref().child('Agents').child(newarray[i]);
dbRef.on('value', newAgents);
}
}
})
function newAgents(AgentSnap) {
var container = document.getElementById("team");
var key = AgentSnap.AgentID;
console.log(AgentSnap.key);
var Agent = AgentSnap.val();
var AgentCard = `
<div class= "profilepics" id="${key}">
<figure ><img src=${Agent.profilepicurl}><figcaption>${Agent.Fname}</figcaption></figure>
</div>
`;
container.innerHTML += AgentCard;
}