I am trying to build a chat application with React and firebase. As soon as the user registers, the authentication works perfectly as well as their pictures get stored in the storage bucket but the user is not stored in the firestore database under the users collection.
I am getting this error in the console:
Below is a code snippet from my register component:
try {
const res = await createUserWithEmailAndPassword(auth, email, password);
const storageRef = ref(storage, displayName);
console.log(storageRef);
const uploadTask = uploadBytesResumable(storageRef, file);
console.log(uploadTask);
uploadTask.on(
(error) => {
setErr(true);
console.log(err);
},
() => {
getDownloadURL(uploadTask.snapshot.ref).then(async (downloadURL) => {
await updateProfile(res.user, {
displayName,
photoURL: downloadURL,
});
await setDoc(doc(db, "users", res.user.uid), {
uid: res.user.uid,
displayName,
email,
photoURL: downloadURL,
});
//Document containing the user's chats
await setDoc(doc(db, "userChats", res.user.uid), {});
navigate("/");
});
}
);
} catch {
setErr(true);
}
CodePudding user response:
Mixing Promise-based APIs and Callback-based APIs will lead to trouble. By exclusively sticking to one type, you can ensure that behaviour will be as you expect.
The UploadTask
class is "Thenable" - it has a then()
and catch()
method which will be fed the upload complete and upload error events. This means you can use it with await
or like any other Promise API.
Because you are doing further Promise-based work in the upload complete event, using UploadTask.on
should be avoided and only be used for updating progress of the upload. As your code doesn't use this progress update feature, I'll be omitting it, but it would be done using something like:
uploadTask.on('state_changed', (snapshot) => /* progress update */);
Additionally, close to the end of your code you attempt to add an empty document to Firestore for the user's messages. This will do nothing as empty documents are automatically deleted by Firestore on the backend. As such, it will be omitted from the code below.
By applying these changes, your code becomes:
try {
// ⬇ you can unwrap user from the userCredential object, better than using "res"
const { user } = await createUserWithEmailAndPassword(auth, email, password);
const storageRef = ref(storage, displayName);
console.log(storageRef);
const uploadTask = uploadBytesResumable(storageRef, file);
console.log(uploadTask);
// if using it, add progress updater here
// uploadTask.on('state_changed', (snapshot) => /* progress update */);
const uploadSnapshot = await uploadTask; // waits for upload completion
const downloadURL = await getDownloadURL(uploadSnapshot.ref);
await updateProfile(user, {
displayName,
photoURL: downloadURL,
});
await setDoc(doc(db, "users", user.uid), {
uid: user.uid,
displayName,
email,
photoURL: downloadURL,
});
navigate("/");
} catch {
console.log(err); // don't forget to log the error so you can investigate
setErr(true);
}
Ideally, you would move most of this logic outside of your component into your UserContext using something similar to:
const userCtx = useContext(YourAuthUserContext);
userCtx.createUserAndUserData({ email, password, displayName, file })
.then((user) => {
navigate("/");
})
.catch((err) => {
console.log("createUserAndUserData failed:", err);
setErr(true);
});