Firestore: How do I update email and store it in my firestore collection as a single batch operation?

Viewed 30

I am writing react native project, where users can sign in with their username and password. As firebase authentication does not have the functionality to sign in with username and password I do a bit of a trick. On registration I store user's email in my Users collection along with other useful users info. So when a user tries to sign in with username and password, I go to my Users collection, look for the respective username, and fetch the corresponding email address. So in disguise, I use firestore method .signInWithEmailAndPassword(email, password) . Not ideal, but it does the job.

However, the problem is when a user wants to update his/hers email address to a new one. Then, I surely want to update the email value within my users collection, so I can perform proper sign in. Here is the code to update user's email:

const onChangeEmail = async () => {
    await firebase
      .auth()
      .signInWithEmailAndPassword(email, password)
      .then(async function (userCredential) {
        await userCredential.user.updateEmail(newEmail).then(async () => {
          await updateEmailInFirestore(newEmail);
        });
      })
      .catch((error) => {
        console.log("Error while updating email: ", error);
      });
  };

Where updateEmailInFirestore does the following:

export const updateEmailInFirestore = async (newEmail) => {
  if (!newEmail) {
    return;
  }
  await firebase
    .firestore()
    .collection("Users")
    .doc(firebase.auth().currentUser.uid)
    .update({ email: newEmail })
    .then(() => console.log("email was updated in user collection: ", newEmail))
    .catch((error) => console.log("error while updating email: ", error));
};

The above code works fine and does the job. However, the problem I am having is the following scenario: what if userCredential.user.updateEmail manages to execute successfully, but then updateEmailInFirestore fails or throws an exception? Then the value, which I have in my database wont be consistent with the user's updated email address and my login will fail misserably.

Is there a way that I can execute both userCredential.user.updateEmail and updateEmailInFirestore as batch operation, so that either both succeed or both fail? I have written batch operations before but they are along the lines of const batch = firebase.firestore().batch(); and my first operation is not related to firestore, but to firebase authentication?

1 Answers

There's nothing much you can do when it comes to ensuring both the promises are resolved or not. Firebase Auth and Firestore are 2 different products so there is no concept like batched write. You can use a Promise.all() and ask user to retry in case either of the promises fail as shown below.

const updateUserEmail = async () => {
  try {
    await Promise.all([
      user.updateEmail(newEmail),
      userDoc.update({
        email: newEmail
      })
    ])
  } catch (e) {
    console.log("Updating email failed")
    // prompt user to retry
  }
}

A better and widely used solution is to create an email of format username@noreply.yourapp.com and use that with createUserWithEmailAndPassword(). When a user updates their username (if you want to allow that functionality), then you just have to update the email to newUsername@noreply.yourapp.com. You won't need Firestore for email if you use this method.

Do note that you won't be able to use functionalities like verify email as these email do not exist.

Related