How to use Firebase Firestore .push() method in Javascript front end - javascript

So let me start by saying Google's Firebase documentation is horrible. I am trying to append child values to a list stored on a document in Firestore db when a user adds an item to their cart on an eCommerce site. Simplest thing ever (or should be).. All of the documentation says to use .push() and there is not one actionable example of how to implement it. This is not enough for me to understand how to use it:
// Create a new post reference with an auto-generated id
var newPostRef = postListRef.push();
newPostRef.set({
// ...
});
Where does postListRef come from and how do I instantiate a reference that works with .push()? I've been flat out guessing for the last 3 days off and on about how to use the function, from getting the database reference to adding a value. Below are 2 of the ways I have tried to use it. This is such a stupidly simple question and it drives me insane that after hours of googling there is absolutely not one complete example anywhere I could find. Please offer a complete explanation with sample code in what may seem to be an unnecessary level of detail to you on how to implement .push() with firestore in a vanilla javascript front end script! Bonus points if you can explain how to remove a single item from the list as well.
Documentation Links:
https://firebase.google.com/docs/database/web/lists-of-data,
https://firebase.google.com/docs/database/admin/save-data
const db = firebase.firestore();
//set with a function that checks to see if user is logged in
var userUid;
//set if the user is not signed in to persist the cart
var guestUid;
function addItemToCart() {
console.log("Add item to cart triggered..");
const productName = "Assorted Oranges";
const productId = "123";
if (userUid != null) {
var testRef = firebase.firestore().collection('Carts').doc(userUid).push();
testRef.set({
productName: productName,
productId: productId });
} else {
guestUid = getUUID();
var testRef = db.push({
productName: productName,
productId: productId
});
}
}
No matter how I try to use it I keep getting a variation of the error: .push() is not a function like:
product-single:126 Uncaught TypeError: firebase.firestore(...).collection(...).doc(...).push is not a function
at addItemToCart (product-single:126)

The push() method is part of the Firebase Realtime Database API, not of the Firestore API. While both databases are part of Firebase, they each have their own API and are not compatible.
To add a document to a Firestore collection, you'd do something like:
var testRef = firebase.firestore().collection('Carts').doc(userUid);
testRef.set({
productName: productName,
productId: productId
});

Related

Firestore startAfter() returning the same data in infinite scrolling when ordered by descending timestamp

I'm writing a profile page with chronological user posts (latest post on top) using Firestore on Ionic by setting orderBy() to "timestamp" descending. I'm using Ionic's infinite loading to load more posts when the user reaches the bottom, but the result is that Firestore loads the exact same posts over and over again. Please help!
Hi!
Sorry if this is a beginner question, but I've been wrapping my head around this for sever hours to no avail. The pagination works properly when in ascending order, but loads the same posts when in descending order. I've looked at trying the following alternatives, but they would not be cost-efficient:
Change the limit when user reaches the bottom: this would lead to reading all the posts again and again
Do it in ascending order but reverse the array: would defeat the purpose of pagination
use a query (where) to grab the documents before x timestamp: works in theory, but is a bit hacky and I would really want to know how startAfter() works since it's already there.
.
'''
READ_posts__profile(uid,limit, start) : Promise<[]>
{
return new Promise((resolve, reject) =>
{
if (start == null)
{
resolve();
}
else if(start == 'head')
{
start = new Date();
}
console.log(start);
this._DB.collection(this.ref.posts + uid + '/userposts' )
.orderBy("timestamp", 'desc').startAfter(start).limit(limit)
.get()
.then((querySnapshot) =>
{
let obj : any = [];
console.log(obj);
querySnapshot
.forEach((doc : any) =>
{
obj.push({
id : doc.id,
content : doc.data().content,
likes : doc.data().likes,
timestamp : doc.data().timestamp,
comments : doc.data().comments
});
});
resolve(obj);
})
.catch((error : any) =>
{
reject(error);
});
});
}
'''
Expected result: Infinite scrolling of posts from latest to oldest
Result: Infinite scrolling of first x posts again and again (limit is x)
Thank you for taking the time to read. Hope to hear from you guys soon!
UPDATE FOR FUTURE REFERENCE:
Rather than using the doc itself in .startAfter(), it worked with using doc.timestamp like so:
this._DB.collection(this.ref.posts + uid + '/userposts' )
.orderBy("timestamp", "desc").startAfter(start.timestamp).limit(this.limit)
.get()
.then(querySnapshot =>
{
In my case I added the query like this
query.startAfter(lastId)
where it should have been
query = query.startAfter(lastId)
The problem is that startAfter() is expecting a query cursor for the parameter, not the timestamp that you are passing.
A query cursor identifies the document:
Paginate data with query cursors | Firebase
What you need to do is save the doc to a variable at the class level and then pass that in with the next one:
// in the class variables at the top
latestEntry: any;
// then save a reference to it
this.latestEntry = data[data.length - 1].doc;
// Now you can use the latestEntry to query with startAfter
.startAfter(this.latestEntry)
This is the general theory. When I got this working myself in an app I used the code in this answer.
It's going to need quite a lot of rewriting of your code to do this, which is beyond the scope of the time I have right now, but hopefully this will help you resolve it yourself.
The problem is, that you have to match your last query order:
The order of the field values must ""match the order of the order by clauses of the query""
so if you first make a query like this
collectionReference
.whereEqualTo("id", someId)
.orderBy("timestamp", Query.Direction.DESCENDING)
.limit(5)
.get()
.addOnSuccessListener { documentSnapshots ->
//do some stuff
}
Then your next query should be like:
collectionReference
.whereEqualTo("id", someId)
.orderBy("timestamp", Query.Direction.DESCENDING) //FIELD
.limit(5)
.startAfter(lastVisible.get("timestamp"))
.get()
.addOnSuccessListener { documentSnapshots ->
//do some stuff
}
So you see that startAfter({somecriteria}) has to match the orderBy({somecriteria}) of your first query where you got your last doc visible from, otherwise it will return your first query again.
The query should work if you make sure that the data type are the same between timestamp in firestore and variable start
I understand the OP uses Javascript SDK, I think I find a workaround for this issue. unfortunately currently I am using Flutter, but I think you can tweak it easily.
so the idea is to convert the Date or DateTime object from the language (Dart, Javascript, Kotlin etc) to Timestamp object from the Firestore library. and then pass that Timestamp object to startAfter
in Flutter, you can do it like this
import 'package:cloud_firestore/cloud_firestore.dart'; // to import Timestamp class from Firestore
var lastCreatedAtDateTime = yourObject.createdAt; // this is Date Time Object from your language
var firestoreTimestamp = Timestamp.fromDate(lastCreatedAtDateTime); // convert Date to be Firestore's timestamp
query = query.startAfter([firestoreTimestamp)]);

How can I repeat a Firestore query until a condition is met?

So I am working on a storing users information in my database and in addition to the standard data I get from a Firebase User, I wanted to add my own custom data. I am setting up a profile page for each of my users and I wanted to generate a random 10 digit number to be their profileID. I then store it in my database just in case I need it. However, I need them to be unique numbers and for them not to repeat so I'm trying to query the database to check if the profileID that I generated is already in use and if so, generate another one. However, I can't figure out how to repeat a query in Firestore other than maybe using a loop but that doesn't seem like it would work.
const usersRef: AngularFirestoreCollection = this.afs.collection('users');
var query = usersRef.ref.where("profileID", '==', profileID);
query.get().then((querySnapshot) => {
if (!querySnapshot.empty) {
profileID = this.getRandomProfileID();
}
})
A recursive function like this seems the way to go:
function getRandomProfileID() {
return new Promise(function(resolve, reject) {
var profileID = ....
var query = usersRef.ref.where("profileID", '==', profileID);
query.get().then((querySnapshot) => {
if (!querySnapshot.empty) {
this.getRandomProfileID();
}
else {
resolve(profileID);
}
})
})
}
And then you'd call it with:
getRandomProfileID().then(function(profileID) {
console.log(profileID);
});
Or if you're using a more modern version of JavaScript:
var profileID = await getRandomProfileID();
console.log(profileID);
As Doug pointed out, with async/await you can even do without the recursive function entirely:
while (true) {
let profileID = ....
let snapshot = await usersRef.ref.where("profileID", '==', profileID).get()
if (!snapshot.empty) {
break;
}
}
console.log("Found unique ID: " + profileID);
But there are some things still to consider with this approach:
Another user may generate the same profile ID between the time you generate and check it, and actually creating it. If this is a concern, you should probably use a transaction for the function above, and have it create the profile document. If it's not a realistic concern, you should still ensure in security rules that no user can ever overwrite a document that was already created by another user.
If you're going to use your own profile ID in a lot of places to identify the user, consider creating an additional collection where each document name/ID is the profile ID, and the document contents are the key into the users collection. If you're going to uniquely identify the users by their profile ID, consider using the profile ID as the key of their document in the users collection, instead of whatever key you know use.

How to Add Child on Firebase and then Get it and Display the information?

I'm trying to understand Firebase and I've been reading the useless Docs and looking on Here but none seem to help me understand the basics of adding and displaying from Firebase. I'm trying to add a Child to Salesperson containing the name and number of cars sold so I can then retrieve them and display them on a table.
var salespersonRef = database.ref('Salespersons');
$("#add-user").on("click", function(event) {
event.preventDefault();
name = $("#name-input").val().trim();
cars_sold = $("#cars_sold-input").val().trim();
salespersonRef.child({
name: name,
cars_sold: cars_sold,
});
salespersonRef.on("value", function(snapshot) {
// Log everything that's coming out of snapshot
console.log(snapshot.val());
console.log(snapshot.val().name);
console.log(snapshot.val().cars_sold);
// Change the HTML to reflect
$("#name-display").text(snapshot.val().name);
$("#cars_sold-display").text(snapshot.val().cars_sold);
So thats what I have at the moment for adding and displaying. It does display the current addition but it overwrites anything I add and doesnt make another child
This code doesn't do anything:
salespersonRef.child({
name: name,
cars_sold: cars_sold,
});
If you're trying to add a new child under salespersonRef, it should be:
salespersonRef.push({
name: name,
cars_sold: cars_sold,
});
Every time you can push() it generates a new unique location under that reference.

Write pre-determined node id when pushing to Firebase array

This question has been asked before, but none of the solutions provided seem to help my problem:
I have an array of data on Firebase, that I loop through to generate content in my app, using a "for" loop and therefore an index value. This works fine with the predetermined sets of data, as the IDs are simple array numerics but when a user adds some new data, which is added to the array with push(), firebase creates a unique node key (such as -KyWRU7RRCE_V1w_OiZx) for the new set of data which for some reason prevents my loop from working (no errors shown in console).
If I go to firebase and manually ensure that the new set of data has a numeric value, loop starts working again. How do I make it so that when the user pushes new data to the array, the key being generated is numeric? I tried this:
// Push new routine to Firebase.
var firebaseRef = firebase.database().ref();
var createdExercise = JSON.parse(localStorage.newRoutine);
$("#save").click(function()
{
firebaseRef.child("workouts").key(some_indexed_value).push(createdExercise);
});
but the console returns the following error: Uncaught TypeError: firebaseRef.child(...).key is not a function.
In case it is useful, here is the loop I am using:
// Initialize Firebase.
firebase.initializeApp(config);
// Reference data.
var dbRef = firebase.database().ref().child("workouts");
// Sync with Firebase in real time.
dbRef.on("value", snap =>
{
var workouts = snap.val();
for (var i = 0; i < workouts.length; i++)
{
var routine = workouts[i].title;
var time = 3;
var exercises = workouts[i].exercises;
var icons;
$("#cardList").append(createCards(routine));
}
});
And a pic of my JSON data on Firebase:
Answer to your question is using .set() instead of .push() like this
firebaseRef.child("workouts").child(some_indexed_value).set(createdExercise);
this will create a node under workouts with your custom key.
but it is recommended to use push keys and use forEach() method to get the result than using for loop

Creating a paginated chronological query in Firestore

Hopefully a relatively simple question here with using Firestore querys
I am trying to create essentially a news feed, sorting the newest content from the oldest content. That part is pretty easy, I use:
var first = db.collection("feeds/0/active").orderBy("timestamp", "desc").limit(3);
Which retrieves the 3 newest entries in the news feed. My then idea is in the next query, to pull down the next 3 items in the feed. So if we are going by age, 4,5,6 in the collection in terms of how new the items are.
To do this I grab the last item in query one, and use that node's timestamp as my start at value in query 2:
var first = db.collection("feeds/0/active").orderBy("timestamp", "desc").limit(3);
first.get().then(function (documentSnapshots) {
// Get the last visible document
var lastVisible = documentSnapshots.docs[documentSnapshots.docs.length-1];
var next = db.collection("feeds/0/active").orderBy("timestamp", "desc").startAt(lastVisible.data().timestamp).limit(3);
next.get().then(function(docSn){
console.log("SECOND QUERY!")
docSn.forEach(function(doc) {
console.log(doc.data().message)
})
})
The Result of this code returns the same as the first query returns, the nodes 1, 2, 3 despite trying to tell the second query to start at node3
I also tried passing in a javascript object instead:
var datevalue = Date.parse(lastVisible.data().timestamp)
var next = db.collection("feeds/0/active").orderBy("timestamp", "desc").startAt(datevalue).limit(3);
Which also unfortunately did not work.
I also tried passing in the entire snapshot item, and got the error
"Malforormed Calls from JS: field sizes are different.
[[9,38,38,38],[0,0,1,0],
etc.."
Not really clue where to start with this as I have read through the docs and any examples and I could find and can't seem to figure it out. The only other way I can think of implementing this is by using a Cloud Function to number each node upon creation.. but that feels hacky
Any help would be huge! Thanks
I have a collection called "books" with a field called "created" of type Timestamp. This is not a string. This is not a Date. Please see the Timestamp docs. You need to specify in your settings that you want to use the new Timestamp type instead of Date for your timestamp fields. (See docs for firestore settings)
Example of initiating your firestore and set the TimestampsInSnapshots setting:
var firebaseApp = firebase.initializeApp({
apiKey: [APIKEY],
authDomain: [FIREBASEAPPDOMAIN],
projectId: [PROJECTID]
});
var firestore = firebase.firestore();
var settings = { timestampsInSnapshots: true }; // force Timestamp object instead of Date
firestore.settings(settings);
When this is done, i've run the below code and I get the expected results. First query returns three results. Newest first, latest last.
The second query returns the next three items.
var first = db.collection('books').orderBy("created", "desc").limit(3);
console.log("FIRST QUERY!")
first.get().then(function(documentSnapshots) {
documentSnapshots.forEach(function (doc) {
console.log(doc.data().created.toMillis())
});
// Get the last visible document
var lastVisible = documentSnapshots.docs[documentSnapshots.docs.length - 1];
var next = db.collection('books').orderBy("created", "desc")
.startAfter(lastVisible.data().created).limit(3);
next.get().then(function (docSn) {
console.log("SECOND QUERY!")
docSn.forEach(function (doc) {
console.log(doc.data().created.toMillis())
});
});
});
The console from the above code:
FIRST QUERY!
1540573716371
1540573676652
1540573668643
SECOND QUERY!
1540573658893
1540573536420
1540536647891
Are you checking the entire list returned? Or, just the first node?
If you use startAt, then the first node in the second list will be the last node in the first list (docs). I suggest using startAfter (docs).
Example:
var db = firebase.firestore()
var query = db.collection('feeds/0/active').orderBy('timestamp', 'desc').limit(3)
query.get().then((firstSnapshot) => {
var first3 = snapshot.docs
query.startAfter(first3[2]).get().then((nextSnapshot) => {
var next3 = snapshot.docs
})
})

Categories