The problem is that every time I click on an element with a state things appear twice. For example if i click on a button and the result of clicking would be to output something in the console, it would output 2 times. However in this case, whenever I click a function is executed twice.
The code:
const getfiles = async () => {
let a = await documentSpecifics;
for(let i = 0; i < a.length; i++) {
var wrt = document.querySelectorAll("#writeto");
var fd = document.querySelector('.filtered-docs');
var newResultEl = document.createElement('div');
var writeToEl = document.createElement('p');
newResultEl.classList.add("result");
writeToEl.id = "writeto";
newResultEl.appendChild(writeToEl);
fd.appendChild(newResultEl);
listOfNodes.push(writeToEl);
listOfContainers.push(newResultEl);
wrt[i].textContent = a[i].data.documentName;
}
}
The code here is supposed to create a new div element with a paragraph tag and getting data from firebase firestore, will write to the p tag the data. Now if there are for example 9 documents in firestore and i click a button then 9 more divs will be replicated. Now in total there are 18 divs and only 9 containing actual data while the rest are just blank. It continues to create 9 more divs every click.
I'm also aware of React.Strictmode doing this for some debugging but I made sure to take it out and still got the same results.
Firebase code:
//put data in firebase
createFileToDb = () => {
var docName = document.getElementById("title-custom").value; //get values
var specifiedWidth = document.getElementById("doc-width").value;
var specifiedHeight = document.getElementById("doc-height").value;
var colorType = document.getElementById("select-color").value;
parseInt(specifiedWidth); //transform strings to integers
parseInt(specifiedHeight);
firebase.firestore().collection("documents")
.doc(firebase.auth().currentUser.uid)
.collection("userDocs")
.add({
documentName: docName,
width: Number(specifiedWidth), //firebase-firestore method for converting the type of value in the firestore databse
height: Number(specifiedHeight),
docColorType: colorType,
creation: firebase.firestore.FieldValue.serverTimestamp() // it is possible that this is necessary in order to use "orderBy" when getting data
}).then(() => {
console.log("file in database");
}).catch(() => {
console.log("failed");
})
}
//get data
GetData = () => {
return firebase.firestore()
.collection("documents")
.doc(firebase.auth().currentUser.uid)
.collection("userDocs")
.orderBy("creation", "asc")
.get()
.then((doc) => {
let custom = doc.docs.map((document) => {
var data = document.data();
var id = document.id;
return { id, data }
})
return custom;
}).catch((err) => {console.error(err)});
}
waitForData = async () => {
let result = await this.GetData();
return result;
}
//in render
let documentSpecifics = this.waitForData().then((response) => response)
.then((u) => {
if(u.length > 0) {
for(let i = 0; i < u.length; i++) {
try {
//
} catch(error) {
console.log(error);
}
}
}
return u;
});
Edit: firebase auth is functioning fine so i dont think it has anything to do with the problem
Edit: This is all in a class component
Edit: Clicking a button calls the function createFileToDb
I think that i found the answer to my problem.
Basically, since this is a class component I took things out of the render and put some console.log statements to see what was happening. what i noticed is that it logs twice in render but not outside of it. So i took the functions out.
Here is the code that seems to fix my issue:
contain = () => {
const documentSpecifics = this.waitForData().then((response) => {
var wrt = document.getElementsByClassName('writeto');
for(let i = 0; i < response.length; i++) {
this.setNewFile();
wrt[i].textContent = response[i].data.documentName;
}
return response;
})
this.setState({
docs: documentSpecifics,
docDisplayType: !this.state.docDisplayType
})
}
As for creating elements i put them in a function so i coud reuse it:
setNewFile = () => {
const wrt = document.querySelector(".writeto");
const fd = document.querySelector("#filtered-docs");
var newResultEl = document.createElement('div');
newResultEl.classList.add("result");
var wrtEl = document.createElement('p');
wrtEl.classList.add("writeto");
fd.appendChild(newResultEl);
newResultEl.appendChild(wrtEl);
}
The firebase and firestore code remains the same.
the functions are called through elements in the return using onClick.
I can't seem to figure out where I am going wrong, I have tried placing the code in an async function and awaiting for the loop to finish but it somehow keeps committing the writebatch before it's done.
Please could someone shed some light on my code, I don't know where I might not be understanding the process.
What I managed to research was that async function run in the background and allows other code to run, so I need to wait for the async operation first if I need a job to be done after.
Please let me know what I'm missing, appreciate the help, here's my code:
getReady();
async function stage1() {
const batch = db.batch();
await response.data.matches.forEach(async match => {
var batchRef = db.collection('matches').doc(`${match.id}`);
var utcDate = `${match.utcDate}`; // ISO-8601 formatted date returned from server
var localDate = moment.utc(utcDate).toDate();
var unixTime = ((localDate.getTime()) / 1000);
const now = new Date();
const secondsSinceEpoch = Math.round(now.getTime() / 1000)
var howLong = timeDifference(unixTime, secondsSinceEpoch);
var checkMatches = db.collection('matches');
matchesSnapshot = await checkMatches.where('matchId', '==',
match.id).get();
if (matchesSnapshot.empty) {
batch.set(batchRef, {
competitionName: `${match.competition.name}`,
competitionId: `${match.competition.id}`,
matchStatus: `${match.status}`,
matchDate: `${match.utcDate}`,
matchDay: `${match.matchday}`,
unixDate: unixTime,
matchId: `${match.id}`,
lastUpdated: `${match.lastUpdated}`,
homeTeamScore: match.score.fullTime.homeTeam,
awayTeamScore: match.score.fullTime.awayTeam,
homeWinOdds: `${match.odds.homeWin}`,
drawOdds: `${match.odds.draw}`,
awayWinOdds: `${match.odds.awayWin}`,
matchResults: `${match.score.winner}`,
matchduration: `${match.score.duration}`,
fullTimeHomeTeam: `${match.score.fullTime.homeTeam}`,
fullTimeAwayTeam: `${match.score.fullTime.awayTeam}`,
halfTimeHomeTeam: `${match.score.halfTime.homeTeam}`,
halfTimeAwayTeam: `${match.score.halfTime.awayTeam}`,
extraTimeHomeTeam: `${match.score.extraTime.homeTeam}`,
extraTimeAwayTeam: `${match.score.extraTime.awayTeam}`,
penaltiesHomeTeam: `${match.score.penalties.homeTeam}`,
penaltiesAwayTeam: `${match.score.penalties.awayTeam}`,
homeTeamId: `${match.homeTeam.id}`,
awayTeamId: `${match.awayTeam.id}`,
homeTeamName: `${match.homeTeam.name}`,
awayTeamName: `${match.awayTeam.name}`,
category: 'Football'
});
} else if (!matchesSnapshot.empty) {
var checkingMatches = db.collection('matches').doc(`${match}`);
var doc = await checkingMatches.get();
var oldTime = doc.data().lastUpdated;
var utcDate2 = `${match.lastUpdated}`; // ISO-8601 formatted date returned from server
var utcDate3 = oldTime; //
var localDate2 = moment.utc(utcDate2).toDate();
var localDate3 = moment.utc(utcDate3).toDate();
var unixTime2 = ((localDate2.getTime()) / 1000);
var unixTime3 = ((localDate3.getTime()) / 1000);
if (unixTime2 > unixTime3) {
const reference = db.collection('matches').doc(`${match.id}`);
batch.update(reference, {
matchStatus: `${match.status}`,
matchDate: `${match.utcDate}`,
matchDay: `${match.matchday}`,
lastUpdated: `${match.lastUpdated}`,
homeTeamScore: match.score.fullTime.homeTeam,
awayTeamScore: match.score.fullTime.awayTeam,
homeWinOdds: `${match.odds.homeWin}`,
drawOdds: `${match.odds.draw}`,
awayWinOdds: `${match.odds.awayWin}`,
matchResults: `${match.score.winner}`,
matchduration: `${match.score.duration}`,
fullTimeHomeTeam: `${match.score.fullTime.homeTeam}`,
fullTimeAwayTeam: `${match.score.fullTime.awayTeam}`,
halfTimeHomeTeam: `${match.score.halfTime.homeTeam}`,
halfTimeAwayTeam: `${match.score.halfTime.awayTeam}`,
extraTimeHomeTeam: `${match.score.extraTime.homeTeam}`,
extraTimeAwayTeam: `${match.score.extraTime.awayTeam}`,
penaltiesHomeTeam: `${match.score.penalties.homeTeam}`,
penaltiesAwayTeam: `${match.score.penalties.awayTeam}`,
});
}
}
});
return batch.commit().then(() => {
console.log("im done");
}).catch((err) => {
console.log('Mac! there was an error while doing the job: ', err);
});
}
async function getReady() {
await stage1();
}
What jumps out to my eye is your .forEach(async match => { on the 4th line. .forEach() is NOT asynchronous - it will NOT wait, it will continue through - which is likely why the WriteBatch is being closed before asynchronous operations try to write to it.
At a minimum you will want to use something like Promise.All(...whatever.map()) (and discard the result, if you wish) to make the entire thing asynchronous.
To be honest, I haven't even looked at anything after that - there may well be other issues.
I'm using Firebase Cloud Functions to delete old nodes inside the Firebase Database. Almost 1 month ago I asked a similar question in which the problem was that the values were in seconds instead of milliseconds, but the problem persists. How to fix this problem?
My index.js:
'use strict';
var functions = require('firebase-functions');
var admin = require('firebase-admin');
admin.initializeApp();
// Cut off time. Child nodes older than this will be deleted.
const CUT_OFF_TIME = 30000; // 30 seconds in milliseconds.
/**
* This database triggered function will check for child nodes that are older than the
* cut-off time. Each child needs to have a `timestamp` attribute.
*/
exports.deleteOldItems = functions.database.ref('/posts/{id1}/{id2}/timestamp').onWrite(async (change) => {
var ref = change.after.ref.parent; // reference to the parent
var now = Date.now();
var cutoff = now - CUT_OFF_TIME;
var oldItemsQuery = ref.orderByChild('timestamp').endAt(cutoff);
var snapshot = await oldItemsQuery.once('value');
// create a map with all children that need to be removed
var updates = {};
snapshot.forEach(child => {
updates[child.key] = null;
});
// execute all updates in one go and return the result to end the function
return ref.update(updates);
});
and my database struture
"posts" : {
"38d1d0aa-d774-4a69-a78e-44d02b285669" : {
"-LggzAxeAYXF7tOav_vF" : {
"timestamp" : 1559827888988
}
},
"58fe50ae-db93-4a22-996f-7a28d82583ba" : {
"-Lggze2bvHZx2gJeM8OA" : {
"timestamp" : 1559828012239
}
}
},
i have function i am using to do updates to my databased based on Cron Job. It looks like this ( worth saying i had a lot of help here )
exports.minute_job =
functions.pubsub.topic('minute-tick').onPublish((event) => {
var ref = admin.database().ref("comments")
ref.once("value").then((snapshot) => {
var updates = {};
snapshot.forEach(commentSnapshot => {
var comment = commentSnapshot.val();
var currentRating = comment.rating - comment.lastRating;
var newScore = ((Math.abs(comment.internalScore) * 0.95) + currentRating) * -1;
if(newScore < 0.000001) { newScore = 0.000001}
updates[commentSnapshot.key + "/lastRating"] = comment.rating;
updates[commentSnapshot.key + "/internalScore"] = newScore;
});
ref.update(updates);
})
});
Its all working perfectly, except i am getting this warning from Firebase logs:
"Function returned undefined, expected Promise or value"
Thanks for any help
Since your Cloud Function doesn't return a value, the Google Cloud Functions engine contain doesn't know when the code is finished. In many cases this means that GCF will simply terminate the contain of your function right after the closing }) has executed. But at that point, your code is likely still loading data from the database, and it definitely hasn't update the database yet.
The solution is to return a promise, which is just an object that will signal when you're done with the database. The good news is that both once() and update() already return promises, so you can just return those:
exports.minute_job =
functions.pubsub.topic('minute-tick').onPublish((event) => {
var ref = admin.database().ref("comments")
return ref.once("value").then((snapshot) => {
var updates = {};
snapshot.forEach(commentSnapshot => {
var comment = commentSnapshot.val();
var currentRating = comment.rating - comment.lastRating;
var newScore = ((Math.abs(comment.internalScore) * 0.95) + currentRating) * -1;
if(newScore < 0.000001) { newScore = 0.000001}
updates[commentSnapshot.key + "/lastRating"] = comment.rating;
updates[commentSnapshot.key + "/internalScore"] = newScore;
});
return ref.update(updates);
})
});
Now Google Cloud Functions know that your code is still working after the }), because you returned a promise. And then when your update() is done, it resolves the promise it return and Google Cloud Functions can close the container (or at the very least: stop charging you for its usage).
Every minute I have a script that push a new record in my firebase database.
What i want is delete the last records when length of the list reach a fixed value.
I have been through the doc and other post and the thing I have found so far is something like that :
// Max number of lines of the chat history.
const MAX_ARDUINO = 10;
exports.arduinoResponseLength = functions.database.ref('/arduinoResponse/{res}').onWrite(event => {
const parentRef = event.data.ref.parent;
return parentRef.once('value').then(snapshot => {
if (snapshot.numChildren() >= MAX_ARDUINO) {
let childCount = 0;
let updates = {};
snapshot.forEach(function(child) {
if (++childCount <= snapshot.numChildren() - MAX_ARDUINO) {
updates[child.key] = null;
}
});
// Update the parent. This effectively removes the extra children.
return parentRef.update(updates);
}
});
});
The problem is : onWrite seems to download all the related data every time it is triggered.
This is a pretty good process when the list is not so long. But I have like 4000 records, and every month it seems that I screw up my firebase download quota with that.
Does anyone would know how to handle this kind of situation ?
Ok so at the end I came with 3 functions. One update the number of arduino records, one totally recount it if the counter is missing. The last one use the counter to make a query using the limitToFirst filter so it retrieve only the relevant data to remove.
It is actually a combination of those two example provided by Firebase :
https://github.com/firebase/functions-samples/tree/master/limit-children
https://github.com/firebase/functions-samples/tree/master/child-count
Here is my final result
const MAX_ARDUINO = 1500;
exports.deleteOldArduino = functions.database.ref('/arduinoResponse/{resId}/timestamp').onWrite(event => {
const collectionRef = event.data.ref.parent.parent;
const countRef = collectionRef.parent.child('arduinoResCount');
return countRef.once('value').then(snapCount => {
return collectionRef.limitToFirst(snapCount.val() - MAX_ARDUINO).transaction(snapshot => {
snapshot = null;
return snapshot;
})
});
});
exports.trackArduinoLength = functions.database.ref('/arduinoResponse/{resId}/timestamp').onWrite(event => {
const collectionRef = event.data.ref.parent.parent;
const countRef = collectionRef.parent.child('arduinoResCount');
// Return the promise from countRef.transaction() so our function
// waits for this async event to complete before it exits.
return countRef.transaction(current => {
if (event.data.exists() && !event.data.previous.exists()) {
return (current || 0) + 1;
} else if (!event.data.exists() && event.data.previous.exists()) {
return (current || 0) - 1;
}
}).then(() => {
console.log('Counter updated.');
});
});
exports.recountArduino = functions.database.ref('/arduinoResCount').onWrite(event => {
if (!event.data.exists()) {
const counterRef = event.data.ref;
const collectionRef = counterRef.parent.child('arduinoResponse');
// Return the promise from counterRef.set() so our function
// waits for this async event to complete before it exits.
return collectionRef.once('value')
.then(arduinoRes => counterRef.set(arduinoRes.numChildren()));
}
});
I have not tested it yet but soon I will post my result !
I also heard that one day Firebase will add a "size" query, that is definitely missing in my opinion.