firebase/firestore save user to collection - javascript

I am trying to save a user to a collection in my Firestore. It seems that the users are being created, I can see them in the Authentication tab, however they are not saving to my collection inside my firestore. There doesn't seem to be any errors inside my console either. I've been struggling with this for a couple hours now and i'm not even sure how to debug this.
export const authMethods = {
signup: (email, password, setErrors, setToken) => {
firebase
.auth()
.createUserWithEmailAndPassword(email, password)
// make res asynchronous so that we can make grab the token before saving it.
.then(async res => {
const token = await Object.entries(res.user)[5][1].b
// set token to localStorage
await localStorage.setItem('token', token)
// grab token from local storage and set to state.
setToken(window.localStorage.token)
const userUid = firebase.auth().currentUser.uid
const db = firebase.firestore()
db.collection('/users')
.doc(userUid)
.set({
email,
password,
})
console.log(res)
})
.catch(err => {
setErrors(prev => [...prev, err.message])
})
},
....
}
Any ideas?

Remove await from localStorage.setItem it isn't an asynchronous function.
You'll also need to add await to db.collection("/users").doc(userUid)
This is another approach that you could do. Let a cloud function handle that for you.
Whenever a user is created the following function is triggered.
export const onUserCreate = functions.auth
.user()
.onCreate(async (user, context) => {
await admin.firestore().collection("users").doc(user.uid).set({
id: user.uid,
emailAddress: user.email,
verified: user.emailVerified,
});
});
If you need more information about cloud functions read the following.
https://firebase.google.com/docs/functions/get-started

Related

How to await the creation of a document created in cloud functions with onSnapshot

I want to securely create a user document onCreate that is in sync with the auth.user database in Firebase v9.
I think it wouldn't be secure to let a registered user create a user document. So I wrote a cloud function which triggers on functions.auth.user().onCreate() and creates a user document.
Problem:
I have the problem keeping them in sync as the onSnapshotmethod which should await for the user document to exists already returns a promise if the user document does not yet exists. Sometimes it works and sometimes not. So I don't know when I can update the by the cloud function created user document.
Question:
Why does the onSnapshot sometimes work and sometimes not. How can I fix it?
Here is a link to a helpful Article which seem to doesn't work in v9. Link
I tried and searched everywhere. I can't believe this is not a standard feature and still a requested topic. This seems so basic.
Error
error FirebaseError: No document to update: as const user = await createAccount(displayName, email, password); returns even if user is not yet in doc.data()
Sign Up function
interface SignUpFormValues {
email: string;
password: string;
confirm: string;
firstName: string;
lastName: string;
}
const createAccount = async (
displayName: string,
email: string,
password: string
) => {
// Create auth user
const userCredential = await createUserWithEmailAndPassword(
auth,
email,
password
);
// -> Signed in
// Update Profile
const user = userCredential.user;
const uid = user.uid;
await updateProfile(user, {
displayName: displayName,
});
// IMPORTANT: Force refresh regardless of token expiration
// auth.currentUser.getIdToken(true); // -> will stop the onSnapshot function from resolving properly
// Build a reference to their per-user document
const userDocRef = doc(db, "users", uid);
return new Promise((resolve, reject) => {
const unsubscribe = onSnapshot(userDocRef, {
next: (doc) => {
unsubscribe();
console.log("doc", doc); // -> returning undefined
console.log("doc.data()", doc.data()); // -> returning undefined
resolve(user); // -> returning undefined
},
error: (error) => {
unsubscribe();
console.log("error", error);
reject(error);
},
});
});
};
const handleSignUp = async (values: SignUpFormValues) => {
const { firstName, lastName, email, password } = values;
const displayName = `${firstName} ${lastName}`;
try {
setError("");
setLoading(true);
// Create user account
const user = await createAccount(displayName, email, password);
console.log("createAccount -> return:", user); // -> problem here sometimes return undefined
// Update user
const newUserData = {
displayName: displayName,
firstName,
lastName,
};
// Build a reference to their per-user document
const userDocRef = doc(db, "users", user.uid);
await updateDoc(userDocRef, newUserData);
// Send Email verification
await authSendEmailVerification(user);
// Logout
await logout();
navigate("/sign-up/email-verification", { state: values });
} catch (error: any) {
const errorCode = error.code;
const errorMessage = error.message;
console.log("error", error);
console.log("error", errorCode);
if (errorCode === "auth/email-already-in-use") {
const errorMessage =
"Failed to create an account. E-Mail address is already registered.";
setError(errorMessage);
console.log("error", errorMessage);
} else {
setError("Failed to create account.");
}
}
setLoading(false);
};
Cloud function which triggers the user onCreate
// On auth user create
export const authUserWriteListener = functions.auth
.user()
.onCreate(async (user, context) => {
console.log("user:", user);
const userRef = db.doc(`users/${user.uid}`);
await userRef.set({
email: user.email,
createdAt: context.timestamp,
firstTimeLogin: true,
});
return db.doc("stats/users").update({
totalDocsCount: FieldValue.increment(1),
});
});
The issue is that the Cloud Function code runs asynchronously. There is no guarantee that it will run quickly enough to have the document created in Firestore between the end of createAccount() and your call to updateDoc(). In fact, if your system has been idle for a while it could be a minute (or more!) for the Cloud Function to execute (do a search for "cold start firebase cloud functions").
One option, depending on your design, might be to not take in first name and last name during sign up? But instead take the user to a "profile page" once they are logged in where they could modify aspects of their profile (by that time the user profile document hopefully is created). On that page, if the get() returns no document, you could put up a notification to the user that the system "is still processing their registration" or something like that.

Users duplicate on Firestore when I use googleSignIn

I have two ways to register a user in Firebase: through email and through Google Sign In.
I perform the user registration by email as follows:
signUp() {
const auth = getAuth();
const db = getFirestore();
createUserWithEmailAndPassword(
auth,
this.createEmail,
this.createPassword
).then(
(userCredential) => {
const user = userCredential.user;
this.$router.push("/");
addDoc(collection(db, "users"), {
email: this.createEmail,
name: this.createName,
});
},
);
},
In other words, in addition to saving the user in Firebase Authentication, I also send their name and email to Firestore. And this is my first question:
Is it the most effective way to save the username and future data that will still be added to it?
Finally, login by Google is done as follows:
googleSignIn() {
const auth = getAuth();
const provider = new GoogleAuthProvider();
signInWithPopup(auth, provider)
.then((result) => {
this.$router.push("/");
addDoc(collection(db, "users"), {
email: result.user.email,
name: result.user.displayName,
});
})
},
Here a problem arises because if a user logs in more than once in Firebase Authentication everything is ok, but in Firebase Firestore a user is created for each new login with Google.
How do I handle this issue of storing users in Firestore, especially users coming from Google Login?
First, I'd move the router.push() statement below addDoc() so I can confirm that the document has been added and then user is redirected to other pages. In case of Google SignIn, you can check if the user is new by accessing the isNewUser property by fetching additional information. If true, then add document to Firestore else redirect to dashboard:
signInWithPopup(auth, provider)
.then(async (result) => {
// Check if user is new
const {isNewUser} = getAdditionalUserInfo(result)
if (isNewUser) {
await addDoc(collection(db, "users"), {
email: result.user.email,
name: result.user.displayName,
});
}
this.$router.push("/");
})
It might be a good idea to set the document ID as user's Firebase Auth UID instead of using addDoc() which generated another random ID so it's easier to write security rules. Try refactoring the code to this:
signInWithPopup(auth, provider)
.then(async (result) => {
// Check if user is new
const {isNewUser} = getAdditionalUserInfo(result)
const userId = result.user.uid
if (isNewUser) {
await setDoc(doc(db, "users", userId), {
email: result.user.email,
name: result.user.displayName,
});
}
this.$router.push("/");
})

Firebase Firestore is not creating a doc - any idea how to debug?

I've been struggling with a weird error. Can't create a doc in firebase. There are no security rules to speak of, just:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write;
}
}
}
Firestore is initialised the normal way and is active:
import { Firebase } from "../db";
let firebase = Firebase();
let firestore = firebase.firestore();
But nothing happens after this is run other than printing "here1", the other consoles aren't doing anything and the userid doc is not being created and no collection and doc under it.
export const addEnquiry = async (data) => {
let user = await firebase.auth().currentUser;
data.uid = user.uid;
console.log("here1");
const enquiry = await firestore.collection("users").doc(data.uid).collection("enquiries").doc();
return await enquiry
.set(data)
.then((doc) => {
console.log("here2");
return true;
})
.catch((err) => {
console.log("here3");
console.log(err);
return false;
});
};
The above doesn't print anything other than "here1" and gets stuck on the setting of the doc. The doc isn't created in Firestore either.
Any idea what might be wrong and how to debug it? Wasted a good 4 hours on trying to figure it out and worried if Firestore is so buggy that it's unsafe to use it in production.
First of all, I assure you Firebase is not buggy at all, we have it running on several production applications and they're running fantastic.
Second, I think your issue here is that you're passing a function as the second argument in the set() method, which is nowhere that I can find in the API reference. Instead, it returns a promise. Your code should look like this:
firebase.firestore()
.collection("users")
.doc(uid)
.set({ uid: uid })
.then((doc) => { console.log(doc.id) })
.catch((err) => { console.log(err) })
Cheers.
Here is an example which will work for you:
file test.mjs
import { Firestore } from '#google-cloud/firestore';
const firestore = new Firestore()
export default (uid) => firestore.collection("users")
.doc(uid)
.set({ uid })
.then(() => console.log('success')) // documentReference.set() returns: Promise < void >
.catch(err => console.error(err))
It's super weird, but what solved the issue for me is adding an unnecessary doc.get() like so:
export const addEnquiry = async (data) => {
let user = await firebase.auth().currentUser;
data.uid = user.uid;
console.log("here1");
const enquiry = await firestore.collection("users").doc(data.uid).collection("enquiries").doc();
const x = await firestore.collection("users").doc(data.uid).get();
// ^^^ added the above line I don't actually need, but somehow it
// establishes a connection to firebase or something which allows the
// promise below to resolve, rather than just hang!
// Doesn't resolve without it for whatever reason!
return await enquiry
.set(data)
.then((doc) => {
console.log("here2");
return true;
})
.catch((err) => {
console.log("here3");
console.log(err);
return false;
});
};
When removing the line, the function hangs again. So have to keep it in for now!
A bit worrying that we have to use such workaround hacks to make a simple write to the firestore, to work, Firebase!
Nonetheless, hope it helps someone facing this undebuggable problem.

React with Cloud Firestore

Im trying to switch from realtime database to cloud firestore in my react app.
In my firebase.js, I have a definition of users as set out below.
class Firebase {
constructor() {
app.initializeApp(config).firestore();
this.auth = app.auth();
// this.db = app.firebase.database()
this.db = app.firestore();
}
doCreateUserWithEmailAndPassword = (email, password) =>
this.auth.createUserWithEmailAndPassword(email, password);
doSignInWithEmailAndPassword = (email, password) =>
this.auth.signInWithEmailAndPassword(email, password);
doSignOut = () =>
this.auth.signOut();
doPasswordReset = email =>
this.auth.sendPasswordResetEmail(email);
doPasswordUpdate = password =>
this.auth.currentUser.updatePassword(password);
// *** User API ***
user = uid => this.db.ref(`users/${uid}`);
users = () => this.db.ref('users');
}
This worked to get users when I was using realtime database, but now I get an error message that says:
this.db.ref is not a function
Why can't I reference this.db anymore and how do I navigate the cloud firestore docs to find the equivalent?
I have seen this post which recommends keeping the realtime database for some things. Is there a list of things that cloud firestore can't do and is getting a list of users one of them?
I have seen this documentation and tried:
user = uid => this.db.DocumentReference(`users/${uid}`);
users = () => this.db.DocumentReference('users');
This produces the same error message as using this.db.ref
There is no ref() in firestore, if you want to access a document you need to use the method document():
db.collection("users").add({
first: "Ada",
last: "Lovelace",
born: 1815
})
.then(function(docRef) {
console.log("Document written with ID: ", docRef.id);
})
.catch(function(error) {
console.error("Error adding document: ", error);
});
Please check the following doc for firestore :
https://firebase.google.com/docs/firestore/quickstart

Error with Firestore update in a Cloud Function

I try to put a listener on Firebase that will replicate a value in the matching element in Firestore.
exports.synchronizeDelegates = functions.database.ref(`delegates/{userId}/activities`).onUpdate((event) => {
const userKey = event.data.ref.parent.key
console.log("User Key:" + userKey)
return admin.database().ref(`delegates/${userKey}/email`).once('value', snapshot => {
let email = snapshot.val()
console.log("Exported Email:" + email)
const userRef = admin.firestore().collection('users')
const firestoreRef = userRef.where('email', "==", email)
firestoreRef.onSnapshot().update({ activities: event.data.toJSON() })
}).then(email => {
console.log("Firebase Data successfully updated")
}).catch(err => console.log(err))
}
)
This function is able to retrieve and locate the elemnt needed to target the right document in firestore, but the .update()function still error firestoreRef.update is not a function
I try several ways to query but I still have this error.
How to properly query then update a document in this scenario?
The onSnapshot() method of Query introduces a persistent listener that gets triggered every time there's a new QuerySnapshot available. It keeps doing this until the listener is unsubscribed. This behavior is definitely not what you want. Also, there's no update() method on QuerySnapshot that your code is trying to call.
Instead, it looks like you want to use get() to fetch a list of documents that match your query, then update them all:
exports.synchronizeDelegates = functions.database.ref(`delegates/{userId}/activities`).onUpdate((event) => {
const userId = event.params.userId
console.log("User Key:" + userKey)
return admin.database().ref(`delegates/${userId}/email`).once('value', snapshot => {
let email = snapshot.val()
console.log("Exported Email:" + email)
const usersRef = admin.firestore().collection('users')
const query = usersRef.where('email', "==", email)
const promises = []
query.get().then(snapshots => {
snapshots.forEach(snapshot => {
promises.push(snapshot.ref.update(event.data.val()))
})
return Promise.all(promises)
})
}).then(email => {
console.log("Firebase Data successfully updated")
}).catch(err => console.log(err))
}
Note that I rewrote some other things in your function that were not optimal.
In general, it's a good idea to stay familiar with the Cloud Firestore API docs to know what you can do.

Categories