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

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");
}
});

Related

Uncaught TypeError: Cannot read properties of null (reading 'email')

I am trying to get the email of the currently logged in user which I will then use to get the name data field from the document corresponding to this user. However, I keep getting this error. Any idea on how to fix this issue would be highly appreciated.
Additionally, whenever I make any change in regard to the email issue, I get the following error:
Uncaught TypeError: _firebase__WEBPACK_IMPORTED_MODULE_4__.db.collection is not a function
And when I refresh the page it returns back to the error in the title.
const auth = getAuth();
const user = auth.currentUser;
const userEmail = user.email;
let clubName;
db.collection("users").where("email", "==", userEmail).get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
console.log(doc.id, " => ", doc.data());
let data = doc.data();
clubName = data.name;
});
}).catch(function(error) {
console.log("Error getting documents: ", error);
});
console.log("THE DATA YOU ARE LOOKING FOR: " + clubName);
const q = query(collection(db, "requests"), where("SendTo", "==", clubName));
NOTE: in the code above I am using 2 different firebase databases one called users which has the fields {email, name, password, job} and the other called requests which have the following fields {From, SendTo, type, content, header}
auth.currentUser might be null because the auth object has not finished initializing.
Try using onAuthStateChanged assuming you are using firebase v9.
import { getAuth, onAuthStateChanged } from "firebase/auth";
const auth = getAuth();
onAuthStateChanged(auth, (user) => {
if (user) {
// User is signed in
} else {
// User is signed out
}
});

How do I write a cloud function which creates a user in the Firestore database once a new user gets authenticated?

So far I have written the following code:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp(functions.config().firebase);
exports.addAccount = functions.auth.user().onCreate((event) => {
const user = event.data; // The firebase user
const id = user.uid;
return admin
.database()
.ref("/users/" + id)
.set("ok");
});
The idea here is to execute a cloud function once a new user gets created. As this happens, I want to create a new node in the Firestore database. Under the 'users' collection I want to add a document with the uid, which contains a bit of information, in this case a String that says 'ok'.
When deploying this function it produces an error. How exactly should I go about creating it?
The admin.database() returns the Realtime Database service. To use Firestore, use firebase.firestore(). Try refactoring the code as shown below:
exports.addAccount = functions.auth.user().onCreate((event) => {
const user = event.data; // The firebase user
const id = user.uid;
return admin
.firestore()
.collection("users")
.doc(id)
.set({ uid: id, ...user });
});

Create a firestore doc for each auth user in Nextjs (only using sign in with Google)

I am building a user auth system with Nextjs
I am trying to create a document within firestore for each user in my firebase authentication system. I was easily able to do this in previous projects when creating an account with email and password but with the 'sign in with google' feature I can't seem to figure out how.
I don't want to create a new document every time the user logs in..
My only idea is this:
When user signs in, loop through all firestore documents and see if the users e-mail matches any firestore doc email. If not, create document, else return.
I feel like there is another way though..
Simplest way would be to make a custom hook that can be used anywhere across the application.
First in the _app file inside useeffect hook simply try to get the data from doc if data exist well it means user document is already there and if data does not exists, we need to create a document for that, quite simple. Let's see the code now
Make sure you read comments written inside the code to better understand
In _app.js,
useEffect(async () => {
// now this checks if user is logged in or not
const unsubscribe = auth.onAuthStateChanged(async (userAuth) => {
if (userAuth) {
// if logged in it simply passes the userAuth object to handle user profile
// which is a custom hook to check if document for this user pre-exist or not!
// if there wont be any document it will go and create a document and return
// that document.
// If already there is a document created it will simply return that.
const userRef = await handleUserProfile(userAuth);
userRef.onSnapshot((snapshot) => {
// later you can save currentUsr value in any of the state to use it later
const currentUsr = {
id: snapshot.id,
...snapshot.data(),
};
}
});
}
}
});
return () => unsubscribe();
}, []);
Now the custom hook to check if document is already there or not, here comes the tricky part.
export const handleUserProfile = async (userAuth) => {
// as a second check it check if falsy values are returned
if (!userAuth) return;
const { uid } = userAuth;
// first it tries to get data from that uid
const userRef = firestore.doc(`users/${uid}`);
const snapshot = await userRef.get();
// checks if snapshot exist
if (!snapshot.exists) {
// if snapshot does not exist, it will simply create a document with document
// name as this 'uid'
const { displayName, email } = userAuth;
const timeStamp = new Date();
try {
// making use of same userRef that we created above to create
await userRef.set({
displayName,
email,
createdAt: timeStamp,
});
} catch (error) {}
}
// if snapshot exist it will simply return the userRef which contains the
// document only.
return userRef;
};
Voila! :)
There is no reason why you should not use the onAuthStateChanged event on auth. A write would cost you the same as a read to check if the data is already there. But with a read you would sometimes need also a write. In total only writes every time come less expensive in read/write actions.
Just listen to auth state changes and update your firestore data each time it changes:
firebase.auth().onAuthStateChanged(async (user) => {
if (user) {
await firebase.firestore()
.collection("users")
.doc(user.uid)
.set(data, {merge:true});
// User is signed in.
}
});
Make sure to use set with merge turned on. That will ensure that the data will be created if it doens't exist and update only the field you want to update.
Also make sure to store the data under the user uid. With that you ensure that each user has an unique idenfier. It is a bad practice to store users under the email. One of the reasons for that is that emails could have chars that are not supported as keys so would need to remove those when saving and add them again when reading the keys.
Firestore won't create duplicate docs if created when signing in with Google.. so this works:
const signInWithGoogle = () => {
fire
.auth()
.signInWithPopup(google_provider)
.then((result) => {
/** #type {firebase.auth.OAuthCredential} */
var credential = result.credential;
// This gives you a Google Access Token. You can use it to access the Google API.
var token = credential.accessToken;
// The signed-in user info.
var user = result.user;
// ...
})
.catch((error) => {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// The email of the user's account used.
var email = error.email;
// The firebase.auth.AuthCredential type that was used.
var credential = error.credential;
// ...
})
// CREATE USER DATA IN FIRESTORE
.then(async () => {
const data = {
//ADD DATA HERE
};
await fire
.firestore()
.collection("users")
.doc(fire.auth().currentUser.email)
.set(data);
});
};

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

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.

How to retrieve data from Firebase using DialogFlow Inline Editor

I am pretty new to DialogFlow. I am wondering how can I retrieve data from Firebase through the Inline Editor of DialogFlow. Hope you can help me!
Thats how you can communicate with firebase from dialogflow
const functions = require('firebase-functions');
const firebaseAdmin = require('firebase-admin');
const DialogflowApp = require('actions-on-google').DialogflowApp;
Initialize Firebase Admin SDK.
firebaseAdmin.initializeApp(functions.config().firebase);
Interaction with firebase Collection users in fulfillment function
let userId = app.getUser().userId;
admin.firestore().collection('users').where('userId', '==', userId).limit(1).get()
.then(snapshot => {
let user = snapshot.docs[0]
if (!user) {
// If user is not in DB, its their first time, Welcome them!
app.ask('Welcome to my app for the first time!');
// Add the user to DB
firebaseAdmin.firestore().collection('users').add({
userId: userId
}).then(ref => {
console.log('Added document with ID: ', ref.id);
});
} else {
// User in DB
app.ask('Welcome back!')
}
});
}
// Map function hanlder to Dialogflow's welcome intent action 'input.welcome'
const actionMap = new Map('input.welcome', start)
app.handleRequest(actionMap);

Categories

Resources