Query current user .once('value') in Firestore - javascript

I am transitioning a Firebase real-time database to a Firebase Firestore database but am having trouble finding the appropriate reference to query the current user.
onAuthUserListener = (next, fallback) =>
this.auth.onAuthStateChanged(authUser => {
if (authUser) {
this.user(authUser.uid)
.once('value')
.then(snapshot => {
const dbUser = snapshot.val();
// default empty roles
if (!dbUser.roles) {
dbUser.roles = [];
}
// merge auth and db user
authUser = {
uid: authUser.uid,
email: authUser.email,
emailVerified: authUser.emailVerified,
providerData: authUser.providerData,
...dbUser,
};
next(authUser);
});
} else {
fallback();
}
});
Most specifically, what would be the replacement for once('value') and snapshot.val();?
I had thought that
.onSnapshot(snapshot => {
const dbUser = snapshot.val();
...

The equivalent of once('value' in Firestore is called get(), and the equivalent of val() is data(). Calling get() returns a promise, so:
.get().then(snapshot => {
const dbUser = snapshot.data();
...
If you have a collection of users, where the profile of each user is stored within a document with their UID as its ID, you can load that with:
firebase.firestore().collection('users').doc(authUser.uid)
.get()
.then(snapshot => {
const dbUser = snapshot.val();
Note that this is pretty well covered in the documentation on getting data, so I'd recommend spending some time there and potentially taking the Cloud Firestore codelab.

Related

How to retrieve the user's info that is currently logged in from Firestore/Firebase?

Hey guys I have a collection on Firestore users and each collection has displayName, email, and uid and trying to get the displayName of the user that is currently logged in.
At the moment the code below is fetching all the users correctly but not the specific user that logged in. The commented out code is kinda a solution that show around but I guess I am not doing it correctly. I have been trying for a while now and cant really find a way, it should be something simple but don't know what I am missing.
const [users, setUser] = useState([])
useEffect(() => {
;(async () => {
const users: any = []
const db = getFirestore()
const userRef = collection(db, 'users')
// const userName = getAuth().currentUser?.displayName
// const userDocument = query(userRef, where('displayName', '==', userName))
// console.log('name', userName)
try {
const snapshot = await getDocs(userRef)
snapshot.docs.forEach((doc) => {
// const { displayName } = doc.data()
users.push({
...doc.data(),
id: doc.id,
// displayName,
})
})
console.log(users)
setUser(users)
} catch (err: any) {
console.log(err.message)
}
})()
}, [])
You need the logged in user's data / displayName from 'users' collection
You have already connected with firebase/firestore that means
you are maintaining a valid firebase config file / credentials
If the above information is correct then this can be a solution for you
Import firebase from the firebase config file
Then get the current user's uid and userProfile
const uid = firebase.auth().currentUser.uid;
const currentUserProfile = firebase.firestore().collection("users").doc(uid);
Then you can get the required data in useEffect as required
useEffect(() => {
currentUserProfile.get()
.then((doc) => {
// const currentUserInfo = doc.data();
const currentUserDisplayName = doc.data().displayName;
// console.log(currentUserDisplayName)
// now you can update your state with this value from here if required
})
.catch((err) => {alert("Error in displaying Name!")})
}, [])

Can't make Firestore to get only docs from logged user id

I am an UX Designer and I'm pretty new to working with Firebase.
I've been trying to develop a system on webflow integrated with Firebase using JavaScript and the Firebase SDK to a personal project and got stuck with this problem.
I have managed to create the authentication system, the signup system and everything is working as it should.
However, when I try to fetch data from Firestore userdata collection, I am not being able to get the current user id and pass it to the WHERE string on my query.
If I run the query without the WHERE, it works perfectly, bringing me all the documents in the userdata collection, but when I try to do it only for a specific user, it fails.
I already tried a lot of things which I think wasn't the right method to do this, the JavaScript below was my last attempt. I think I'm just too new on this and can't understand how to pass the id variable into the query.
Here is the link to the project: https://poupei.webflow.io/. Just click "Criar conta" to create an account, you can use a fake email and 6 digit password and then login.
// Import the functions you need from the SDKs you need
import { initializeApp } from "https://www.gstatic.com/firebasejs/9.4.0/firebase-app.js";
import { getAuth, onAuthStateChanged, signOut } from "https://www.gstatic.com/firebasejs/9.4.0/firebase-auth.js";
import { getFirestore, collection, getDocs, query, where, doc } from "https://www.gstatic.com/firebasejs/9.4.0/firebase-firestore.js"
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries
// Your web app's Firebase configuration
const app = initializeApp({
apiKey: "AIzaSyAZUIyxf4Lsw6D9JOzVuNslsGJ8gXkPBVY",
authDomain: "poupei-app.firebaseapp.com",
projectId: "poupei-app",
storageBucket: "poupei-app.appspot.com",
messagingSenderId: "837432279066",
appId: "1:837432279066:web:119bc86e42fb87ac17d1a3"
});
// Initialize Firebase
const auth = getAuth()
const db = getFirestore();
onAuthStateChanged(auth, (user) => {
if (user) {
// User is signed in, see docs for a list of available properties
// https://firebase.google.com/docs/reference/js/firebase.User
const userID = user.id;
console.log("Logged In");
console.log(userID);
// ...
} else {
// User is signed out
window.location.replace("https://poupei.webflow.io/");
}
});
const q = query(collection(db, "userdata"), where("id", "==", userID));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
const docs = doc.data();
document.getElementById('nome').innerHTML = docs.nome;
document.getElementById('sobrenome').innerHTML = docs.sobrenome;
document.getElementById('email').innerHTML = docs.email;
document.getElementById('saldo').innerHTML = docs.saldo;
});
document.getElementById('logoutBtn').addEventListener('click', function(){
signOut(auth).then(() => {
// Sign-out successful.
window.location.replace("https://poupei.webflow.io/");
}).catch((error) => {
// An error happened.
});
});
</script>
´´´
#Allennick has the cause of the problem correct in their answer, but the solution won't work.
Signing in to Firebase (as well as loading data from Firestore and most other modern cloud APIs) is an asynchronous operation. While the user is being signed in (or the data is being loaded) your main code continues to run. Then when the user is signed in, your callback code is executed.
It's easiest to see this flow by running in a debugger, or adding some logging:
console.log("Attaching auth state listener");
onAuthStateChanged(auth, (user) => {
if (user) {
console.log("Got user state");
}
});
console.log("Starting database query");
const q = query(collection(db, "userdata"), where("id", "==", userID));
const querySnapshot = await getDocs(q);
When you run this code it logs:
Attaching auth state listener
Starting database query
Got user state
This is probably not the order you expected, but it perfectly explains why you're not getting the user data from the database: the query executes before the user is ever loaded.
The solution to this problem is always the same: any code that needs to react to the current user state, needs to be inside the onAuthStateChanged callback, be called from there, or otherwise synchronized.
The simplest fix is to move your database code into the callback, like this:
onAuthStateChanged(auth, async (user) => {
if (user) {
const userID = user.id;
// 👇 Now that the user us know, we can load their data
const q = query(collection(db, "userdata"), where("id", "==", userID));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
const docs = doc.data();
document.getElementById('nome').innerHTML = docs.nome;
document.getElementById('sobrenome').innerHTML = docs.sobrenome;
document.getElementById('email').innerHTML = docs.email;
document.getElementById('saldo').innerHTML = docs.saldo;
});
document.getElementById('logoutBtn').addEventListener('click', function(){
signOut(auth).then(() => {
// Sign-out successful.
window.location.replace("https://poupei.webflow.io/");
}).catch((error) => {
// An error happened.
});
} else {
// User is signed out
window.location.replace("https://poupei.webflow.io/");
}
});
Also see:
firebase.auth().currentUser is null at page load
Is there any way to get Firebase Auth User UID?
firebase.initializeApp callback/promise?
I think that the query doesn't know what userID is because you are declaring that variable inside authStateChange. Try to move the declaration of userID to global scope + add a console.log() before executing the query to see if the userID is set correctly.
Or just put the code that performs the query inside the onAuthStateChanged code so that you can use the userID.
(Posted answer on behalf of the question author to move it to the answer space).
Updating with the code that worked for me with the help of Frank. Thanks Frank!
onAuthStateChanged(auth, async (user) => {
if (user) {
const userID = user.email;
console.log(userID);
const q = query(collection(db, "userdata"), where("email", "==", userID));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
const docs = doc.data();
document.getElementById('nome').innerHTML = docs.nome;
document.getElementById('sobrenome').innerHTML = docs.sobrenome;
document.getElementById('email').innerHTML = docs.email;
document.getElementById('saldo').innerHTML = docs.saldo;
document.getElementById('meta').innerHTML = docs.objetivo;
})
} else {
console.log("nada");
}
});

Constructing an object with two fetches from the database does not update some fields

I am listening to a firestore database (lets call it ALBUMS) and when a new object gets created, I fetch the info from firestore. After fetching the info from ALBUMS database, I receive userId as a field. Here is my listener:
firebase
.firestore()
.collection("ALBUMS")
.doc(albumId)
.onSnapshot((snapshot) => {
const objectThatIWantToSet = snapshot.docs.map((doc) => {
const data = doc.data();
const id = doc.id;
const userId = doc.cP.proto.fields.userId.stringValue;
...
// I want to fetch some more info from another db before constructing my object
});
});
};
After retrieving userId, I need to fetch some more info from another database: I want to fetch userName from database users. Fetching itself is pretty straightforward:
firebase
.firestore()
.collection("USERS")
.doc(userId)
.get()
.then((snapshot) => {
if (snapshot.exists) {
//take the fields that I need
}
});
What I do have problem with is the following: from both fetches I need to construct an object, s.t. it will contain the info from PHOTOS database and from the USERS database. This info, I need to keep it in store. So what I tried is the following:
function FetchInfo(props) {
const [obj, setObj] = useState(null);
// I need to set it in useeffect
useEffect(() => {
firebase
.firestore()
.collection("ALBUMS")
.doc(albumId)
.onSnapshot((snapshot) => {
const finalObject = snapshot.docs.map((doc) => {
const data = doc.data();
const id = doc.id;
//I am getting userId from the first db
const userId = doc.cP.proto.fields.creator.stringValue;
let photoInfo = {
photoId: doc.id,
photoURL: doc.cP.proto.fields.photoURL.stringValue,
createdAt: firebase.firestore.FieldValue.serverTimestamp(),
//I need to insert user data here
};
firebase
.firestore()
.collection("users")
.doc(userId)
.get()
.then((snapshot) => {
if (snapshot.exists) {
photoInfo.user = {
userName: user.name,
userProfilePic: user.profilePicURL
};
//here updated result does have users data
console.log("updated photoInfo: ", photoInfo);
}
});
//my actual result does not have users data
console.log("actual photoInfo: ", photoInfo);
return photoInfo;
});
//my finalObject does not contain users info
//finalObj is array of photoInfo objects
setObj(finalObject);
});
}), []);
I tried to use async await synthax inside useEffect, but it didnt let me put await infront of the second firebase call (it only let me put await infront of the first one, which didnt help).
Any ides would be welcome!
EDIT : the fields which I need from the first db (ALBUMS) are photoId, photoURL, createdAt and userId.
The fields which I need from the second database (USERS) are userName and userProfilePic.
So my final object should be array of objects (I call them photoInfo) which have the following json format:
photoInfo = {
photoId: //taken from the ALBUMS db,
photoURL: //taken from the ALBUMS db,
createdAT: //taken from the ALBUMS db,
user: {
userName: //taken from the USERS db,
userProfilePic: //taken from USERS db
}
}
Again, I am returning array of photoInfo objects.
So far I can easily construct the photoInfo obj, only with the fields from ALBUMS db; the fields from USERS db dont get updated.
What are you trying to do by this?
const userId = doc.cP.proto.fields.creator.stringValue;
If creator is a field containing a string value in the document then you can simply access it by:
const data = doc.data();
const id = doc.id;
const userId = data.creator
Try refactoring the useState as follows.
useEffect(() => {
// Adding a listener on doc
firebase
.firestore()
.collection("ALBUMS")
.doc(albumId)
.onSnapshot(async (snapshot) => {
// An array of requests to get user info
const userReqs = []
// Initialized final object
const finalObject = {}
// Running a loop on the array field*
snapshot.data().array_field.forEach((doc) => {
const data = doc.data();
const id = doc.id;
const userId = data.creator;
finalObject[id] = {
_id: doc.id,
text: "",
createdAt: firebase.firestore.FieldValue.serverTimestamp(),
...data,
};
userReqs.push(
firebase
.firestore()
.collection("users")
.doc(userId)
.get()
.then((snapshot) => {
if (snapshot.exists) {
finalObject[id]["user"] = snapshot.data()
//here updated result does have users data
console.log(`Added user to ${id}`, snapshot.data());
}
})
)
})
await Promise.all(userReqs)
console.log("Final Obj", finalObject);
setObj(Object.keys(finalObject).map((key) => finalObject[key]));
});
}, []);

Firebase Cloud Firestore Read-Write Data

i am trying to signup and save user info into firestore. Save operation is fine but i want to search that info but nothing happening. Here is my code
Signup
firestore
.collection("users")
.doc(user.userId)
.collection("profile")
.add({ ...user })
.then(() => {
auth.onAuthStateChanged((u) => {
if (u) {
u.updateProfile({
displayName: user.displayName,
});
}
});
});
Fetch All users data
firestore
.collection("users")
.get()
.then(function (querySnapshot) {
querySnapshot.forEach(function (doc) {
//doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
});
});
You should be able to achieve by using a similar code as the below one. It very similar to yours, but with some differences, where we separate the referencing to the database and let the querySnapshot iterates by itself, so the data can be returned. I usually use this format to return data from collections.
var db = admin.firestore()
var usersReference = db.collection("users");
usersReference.get().then((querySnapshot) => {
querySnapshot.forEach((userDoc) => {
console.log(userDoc.id)
var userDocData = userDoc.data()
console.dir(userDocData)
})
})
This should work, so, in case it doesn't return anything, probably your saving opearation is not working properly. This will return both the user's id and the whole data from it.

Update data using firestore cloud function

I need help, i'm trying to update my comments collections using cloud function, but my code doesn't seem to work. My function succesfully run but doesn't update my avatarUrl when my userPhotoUrl is update
Here the whole path of the collection that i want to update : "/comments/{postId}/comments/{commentId}"
my firestore collection
exports.onUpdateUser2 = functions.firestore
.document("/users/{userId}")
.onUpdate(async (change, context) => {
const userUpdate = change.after.data();
const userId = context.params.userId;
const newPhotoUrl = userUpdate.photoUrl;
console.log("userId",userId);
console.log("newPhotoUrl",newPhotoUrl);
const querySnapshot = await admin.firestore().collection("comments").get();
querySnapshot.forEach(doc => {
console.log("doc",doc.data());
const postId = doc.id;
const comments = admin.firestore().collection("comments").doc(postId).collection("comments").where("userId","==",userId).get();
comments.forEach(doc2 => {
return doc2.ref.update({avatarUrl: newPhotoUrl});
});
});
});
Thank you,
UPDATE
I try to change the code, by using then to deal with these various promises but i don't really know why commentsRef.get() seem to return me empty querySnapshots, because the comments collections in my firestore database have multiple documents where in each documents there is a another comments collections where in this seconds comments collections there is a bunch of documents containing data. With this whole path i don't know how to iterate until being in the documents containing the data that i need to update. Can someone help me please ?
exports.onUpdateUserUpdateComments = functions.firestore
.document("/users/{userId}")
.onUpdate(async (change, context) => {
const userUpdate = change.after.data();
const userId = context.params.userId;
const newPhotoUrl = userUpdate.photoUrl;
console.log("userId",userId);
console.log("newPhotoUrl",newPhotoUrl);
const commentsRef= admin.firestore().collection("comments");
return commentsRef.get().then(querySnapshot => {
return querySnapshot.forEach(doc => {
return admin
.firestore()
.collection("comments")
.doc(postId)
.collection("comments")
.where("userId", "==", userId)
.get()
.then(doc => {
if (doc.exists) {
doc.ref.update({avatarUrl: newPhotoUrl});
}
return console.log("The function has been run.");
});
});
});
});
Without trying it, it should be something like this:
return admin.firestore().collection("comments")
.doc(postId)
.where("userId", "==", userId)
.get()
.then(doc => {
if (doc.exists) {
doc.ref.update({avatarUrl: newPhotoUrl});
}
return console.log("The function has been run.");
});
Regardless, following Doug Stevenson's advice, you shouldn't start learning JS in Cloud Functions, as those nested loops are a bit strange and you may lack a good starting point for learning.

Categories

Resources