Reputation: 1391
I'm tyring to create a new user with some additional data (eg: subscribeToEmail field etc.). From what I have read online, the way to go about doing this is to authenticate a user (eg: by using createUserWithEmailAndPassword
), and then using the uid
that I obtain from that to create a new document in a users collection. That is what I'm trying to do below:
const handleSignup = async (formData) => {
const {email, password, ...otherUserData} = formData;
const {user} = await auth.createUserWithEmailAndPassword(email, password);
console.log("generating user doc at signup...");
await generateUserDocument(user, otherUserData); // creates user doc (called 2nd)
}
The generateUserDocument
will create a user document (if it doesn't already exist) in the users
collection using the uid of the user
obtained from the createUserWithEmailAndPassword
function call. I also have also set up an auth state change event handler:
auth.onAuthStateChanged(async (userAuth) => { // get the current user
if (userAuth) {
const user = await generateUserDocument(userAuth); // user (should) already exists, so generateUserDocument will fetch the user rather than creating a new user doc, but this doesn't happen, as it is called 1st and not 2nd
dispatch(login(user)); // login logic
} else {
dispatch(logout());
}
});
The issue here is, when I call createUserWithEmailAndPassword
the onAuthStateChanged
callback triggers, which then calls generateUserDocument
before I have actually created a user document with generateUserDocument(user, otherUserData);
inside of the handleSignup method. In other words, the fetch user method: generateUserDocument
inside of .onAuthStateChange()
is being invoked before the user is actually created, which is done by the generateUserDocument
inside of the handleSignup
method. As a result, the user data I'm fetching inside of authStateChange doesn't include the details I'm after.
Is there a way to fix this so that my function call after the auth.createuserWithEmailAndPassword()
is called before the onAuthStateChange event handler is executed (rather than after)? I have thaught about using something like .onSnapshot()
perhaps, but I'm thinking that this might be a little overkill as the user data table shouldn't really need to be continously be listened too, as it will rarely changed. Preferably there is a lifecycle method that gets invoked before onAuthStateChanged
that I could use to populate my users collection, but I haven't been able to find much on that.
For reference, I have been following this article regarding associating additional user data with a auth-user record.
Upvotes: 1
Views: 477
Reputation: 83163
Is there a way to fix this so that my function call after the
auth.createuserWithEmailAndPassword()
is called before theonAuthStateChange
event handler is executed (rather than after)?
No, there is no out-of-the-box way, because
createuserWithEmailAndPassword()
the user will also be signed in to your application (see the doc), andonAuthStateChange()
observer is triggered on sign-in or sign-out.So you indeed need to wait the user Firestore doc is created before proceeding. In my opinion, the best approach is the one you have mentioned, i.e. setting a listener to the user
document.
You can do that in such a way you cancel the listener right after the first time you get the data from the user doc, as shown below. This way the user doc is not continuously being listened to.
auth.onAuthStateChanged((userAuth) => { // No more need to be async
// get the current user
if (userAuth) {
const userDocRef = firestore.collection('users').doc(userAuth.uid);
const listener = userDocRef.onSnapshot((doc) => {
if (doc.exists) {
console.log(doc.data());
// Do any other action you need with the user's doc data
listener(); // Calling the unsubscribe function that cancels the listener
dispatch(login(user));
}
});
} else {
dispatch(logout());
}
});
Another approach could be to use a Callable Cloud Function which, in the backend, creates the user in the Auth service AND the document in Firestore. Your handleSignup
function would be as follows:
const handleSignup = async (formData) => {
const createUser = firebase.functions().httpsCallable('createUser');
await createUser({formData});
// The user is created in the Auth service
// and the user doc is created in Firestore
// We then need to signin the user, since the call to the Cloud Function did not do it!
const {email, password, ...otherUserData} = formData;
await auth.signInWithEmailAndPassword(email, password);
// The onAuthStateChanged listener is triggered and the Firestore doc does exist
}
Upvotes: 3