Internal Error when creating new user with Firebase Auth in React - reactjs

I am working with Firebase for authentication in my application, and below is the function I am calling on my form. Before I had added the .then() with the updateProfile inside, it was working fine. However, now when I create a new user, the user's email and password is stored, but I get the error Internal error: Too much recursion and the displayName is never set.
const handleSignUp = useCallback(
async (event) => {
event.preventDefault();
const { fullName, email, password } = event.target.elements;
try {
await app
.auth()
.createUserWithEmailAndPassword(email.value, password.value)
.then((result) => {
const user = app.auth().currentUser;
return user.updateProfile({
displayName: fullName,
});
});
} catch (error) {
alert(error);
}
history.push("/dashboard");
},
[history]
);
I've tried different ways of calling updateProfile (using the function argument & the user variable as in the example below. I am still getting this error.
Is there a way to write this code to avoid that recursion error?

First, I suggest you to choose between using async/await or then to handle promises.
Second, you can use createUserWithEmailAndPassword return value, as such:
const userCredentials = await app.auth().createUserWithEmailAndPassword(email.value, password.value);
const user = userCredentials.user;
await user.updateProfile({ displayName: fullName });
The relevant documentation: https://firebase.google.com/docs/reference/js/firebase.auth.Auth#createuserwithemailandpassword

Related

users are not stored in the firestore database but are visible in authentication

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

Failing to call two APIs one after another and redirecting the user to another page

I'm using Reactjs for my frontend and Springboot and Firebase for my backend. Basically, when a user registers for an account, I will call two APIs to post the account to both Firebase and my Springboot server, the reason being that I need to make use of the user data collected inside my own Springboot server.
The problem I'm facing is that after a user registers for an account, the Springboot API is not called after Firebase API is called. (I call the Firebase API first and then the Springboot one) It seems to me that after calling the Firebase API, everything stops and the code doesn't go any further, thus calling no more API.
How do I make sure that I can call both APIs one after another and then redirect the user to the next page without any interference?
Registration on submit in Reactjs:
const handleOnSubmit=(event: React.FormEvent<HTMLFormElement>)=> {
if (password !== secondPassword) {
setPasswordsMatched(false);
console.log("passwords matched when password!==secondPassword:" + passwordsMatched);
} else if(!username){
setUsernameExists(false);
}else if(!email){
setEmailExists(false);
}else if(!password||!secondPassword){
setPasswordExists(false);
}else{
if(subscribedStatus){
let subscribed:string="subscribed";
firebaseAuthServiceSignUpWithEmailAndPassword(username,email,password,subscribed,handleSignupSuccess);
}else{
let subscribed:string="unsubscribed";
firebaseAuthServiceSignUpWithEmailAndPassword(username,email,password,subscribed,handleSignupSuccess);
}
}
}
//This is the callback function put inside the Firebase API to see if Firebase accepts the registration. If yes, the user is redirected to "/verification-email"
const handleSignupSuccess=(signupStatus:boolean)=>{
setSignupSuccess(signupStatus);
if(signupStatus){
firebaseAuthServiceEmailVerification(setEmailVerificationSent);
navigate("/verification-email");
}
}
Firebase API:
export const firebaseAuthServiceSignUpWithEmailAndPassword= (username:string,email: string, password: string, subscribed:string,callback: (isSuccess:boolean)=>void) =>{
const auth = getAuth();
createUserWithEmailAndPassword(auth, email, password)
.then(async ( userCredential) => {
// Signed in
const user = userCredential.user;
await postAccount(username, email, password, user.uid, subscribed);
callback(true);
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
callback(false);
// ..
});
}
Springboot API:
export const postAccount=(username:string,email:string,password:string,firebaseUid:string,subscribedStatus:string)=>{
axios.post(`http://localhost:8080/user/${username}/${email}/${password}/${firebaseUid}/${subscribedStatus}`
)
.then((res)=>{
}).catch((error)=>{
console.log(error);
})
}
You mostly need a return statement in postAccount function
Quick fix
export const postAccount=(username:string,email:string,password:string,firebaseUid:string,subscribedStatus:string)=>{
// return here
return axios.post(`http://localhost:8080/user/${username}/${email}/${password}/${firebaseUid}/${subscribedStatus}`
)
.then((res)=>{
return res; // may be return here too
}).catch((error)=>{
console.log(error);
})
}
// async function
const handleOnSubmit= async (event: React.FormEvent<HTMLFormElement>)=> {
if (password !== secondPassword) {
setPasswordsMatched(false);
console.log("passwords matched when password!==secondPassword:" + passwordsMatched);
} else if(!username){
setUsernameExists(false);
}else if(!email){
setEmailExists(false);
}else if(!password||!secondPassword){
setPasswordExists(false);
}else{
if(subscribedStatus){
let subscribed:string="subscribed";
// wait till completion
await firebaseAuthServiceSignUpWithEmailAndPassword(username,email,password,subscribed,handleSignupSuccess);
}else{
let subscribed:string="unsubscribed";
// wait till completion
await firebaseAuthServiceSignUpWithEmailAndPassword(username,email,password,subscribed,handleSignupSuccess);
}
}
}
Slightly better fixs:
With multiple API call its better to use async calls
export const firebaseAuthServiceSignUpWithEmailAndPassword = async (username:string, ...) => {
try {
const auth = getAuth();
const userCredentials = await createUserWithEmailAndPassword(auth, email, password)
const user = userCredential.user;
const res = await postAccount(username, email, password, user.uid, subscribed);
// may be some extra checks
//if (res.success) {
// callback(true);
//}
callback(true);
} catch(error: any) {
// handle error
callback(false);
}
}
export const postAccount = async (username: string, ...) => {
return await axios.post(`http://localhost:8080/user/${username}/${email}/${password}/${firebaseUid}/${subscribedStatus}`)
}
Hope it helps in someway

Persist auth state in react/react native for Firebase

I am using react native for an ios app and firebase for authentication. Every time I leave the app and come back, it asks for a login. I want to persist the firebase login but don't really know where to put it.
I know I need to put this in:
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.LOCAL)
I have the following signIn function that runs when the login button is pressed on the signInScreen:
const signIn = async () => {
setLoading(true);
try {
await firebase.signIn(email, password);
const uid = firebase.getCurrentUser().uid;
const userInfo = await firebase.getUserInfo(uid);
const emailArr = userInfo.email.split("#");
setUser({
username: emailArr[0],
email: userInfo.email,
uid,
isLoggedIn: true,
});
} catch (error) {
alert(error.message);
} finally {
isMounted.current && setLoading(false);
}
};
I have the following signIn stuff in my firebaseContext:
const Firebase = {
getCurrentUser: () => {
return firebase.auth().currentUser;
},
signIn: async (email, password) => {
return firebase.auth().signInWithEmailAndPassword(email, password);
},
getUserInfo: async (uid) => {
try {
const user = await db.collection("users").doc(uid).get();
if (user.exists) {
return user.data();
}
} catch (error) {
console.log("Error #getUserInfo", error);
}
},
logOut: async () => {
return firebase
.auth()
.signOut()
.then(() => {
return true;
})
.catch((error) => {
console.log("Error #logout", error);
});
},
};
Where do I put the persist code I listed above from the docs?
Thanks!
When do you check if someon is signed in or not?
From the code shown it looks like you check it manuelly by calling currentUser. You have to consider that the persistance of auth state is asynchronous. That means if you call currentUser on auth before the localy saved auth state is loaded you would get there null and thing that the user is not signed in.
To get the auth state Firebase recommend to use the onAuthStateChanges event listener. With that you can listen to auth state changes no matter if you logged in or the persistet auth state is loaded.
The usage is very simple:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
} else {
// No user is signed in.
}
});
That is the reson I asked where you check if someon is signed in or not. If I could see that code I could help you adopt it to use that event listener.

Login status in React

I created authorization in javascript. Then if success login I redirect to React project with url parameter http://localhost:3000/?phoneNum=%2B77072050399
Then in React I get userId by using the passed url parameter => phoneNumber using axios.
I realized it in App.js. Code below:
let url = window.location.href;
let params = (new URL(url)).searchParams;
const userPhoneNum = encodeURIComponent(params.get('phoneNum'));
const [userToken, setUserToken] = useState(null);
const getUserToken = async() => {
try {
const data = await axios
.get(`https://stormy-escarpment-89406.herokuapp.com/users/getToken?phone_number=${userPhoneNum}`)
.then(response => {
setUserToken(response.data);
})
.catch(function(error) {
console.log('No such user! Error in getting token!');
});
} catch (e) {
console.log(e);
}
}
useEffect(() => {
getUserToken();
console.log(userToken);
}, userToken);
So, when I go to next page localhost:3000/places, it is requesting for userToken again with parameter null, because there is no param phoneNum.
How to make it to request only one time and save the userId after it is taken in main page. So, then only when I click LogOut button reset the variable where userID is saved.
If you want to do that without using any third party libraries you can use browser's in built storage api
So, when you receive the token, you can store that in the local storage of the browser using localstorage.setItem and later when you wan to see if the token is there or not just read from there using localStorage.getItem
const getUserToken = async() => {
try {
const data = await axios
.get(`https://stormy-escarpment-89406.herokuapp.com/users/getToken?phone_number=${userPhoneNum}`)
.then(response => {
setUserToken(response.data);
Storage.setItem('token',JSON.stringify(response.data))
})
.catch(function(error) {
console.log('No such user! Error in getting token!');
});
} catch (e) {
console.log(e);
}
}
For Logout you can simply remove the token using localStorage.removeItem
You can easily achieve this by using the react-cookie library
npm i react-cookie
Can be easily implemented in your code by
cookies.set('key', value, { path: '/' });
cookies.get('key')
After getting the userNumber form the param
const userPhoneNum = encodeURIComponent(params.get('phoneNum'));
cookies.set('userphoneNum', userPhoneNum);
View the documentation for more information
https://www.npmjs.com/package/react-cookie

Connect Firebase Auth with Firestore in React

I'm trying to figure out how to connect users with data in my firestore. I didn't find anything about how to do it in React.
My idea, in the register page of my app, is:
async function register() {
try {
await auth.createUserWithEmailAndPassword(email, password).then(data => {
auth.currentUser.updateProfile({
displayName: name
})
db.collection('usersdata').doc(data.user.uid).set({nome: name})
props.history.replace('/success')
})
} catch(error) {
alert(error.message)
}
}
In this way I'm creating a new user and also a new entry in my "usersdata" collection. That entry has the same ID and the same name of the user.
After the login I can get the active user name using:
const userName = getCurrentUsername()
function getCurrentUsername() {
return auth.currentUser && auth.currentUser.displayName
}
All of this is working. But here it comes my problem:
I would like to add new data to the current user, for example "age" and "nationality".
But I don't know how to exactly get access to that user from firestore instead of auth.
And I also need to return only his data. Then, after a research, I need to return data from all users, and I guess that is the method:
const [datidb, setDatidb] = useState([])
useEffect(() => {
const fetchData = async () => {
const data = await db.collection('usersdata').get()
setDatidb(data.docs.map(doc => doc.data()))
}
fetchData()
}, [])

Resources