how to verify user email in firebase react native(Expo) - javascript

In my application user authentication is done every thing is working fine but I want implement user email verification feature in my app I am done with user authentication and storing user details in Firestore
my signup method code :
const onRegister = async (email, password, username) => {
try {
const authUser = await firebase
.auth()
.createUserWithEmailAndPassword(email, password);
db.collection("users").add({
owner_uid: authUser.user.uid,
displayname: username,
email: authUser.user.email,
photoURL: await getrandompicture(),
});
await firebase.auth().currentUser.updateProfile({
displayName: username,
photoURL: await getrandompicture(),
});
} catch (error) {
Alert.alert(error.message);
}
};
my authnavigation :
const Authnavigation = () => {
const [currentUser, setcurrentUser] = useState(null);
const userHandler = (users) =>
users ? setcurrentUser(users) : setcurrentUser(null);
useEffect(
() => firebase.auth().onAuthStateChanged((user) => userHandler(user)),
[]
);
return (
<>
{currentUser ? (
<Singninstack userID={currentUser.uid} />
) : (
<Signoutstack />
)}
</>
);
};
export default Authnavigation;

You can call sendEmailVerification right after creating the user.
await authUser.user.currentUser.sendEmailVerification()
.then(() => {
// Email verification sent!
// ...
});

Related

How can I show the user is still Pending (not active yet)?

I created logged in form wherein it will pop-out a toastify when logged in correctly. So my problem right now is I have a logic wherein the user can only access the website when his account is active, but my toastify is not popping correctly. I'm using reduxjs/toolkit my state.
So what I'm trying to achieve here:
1.) Show an error message if the account is not existing or wrong password
2.) Show Account still pending if the user correctly input his username and password but still not active
3.) Navigate to login page when account is active
api/login.js
const login = async (req,res, next) =>{
try {
const user = await User.findOne({studentNumber: req.body.studentNumber})
if(!user) return next(createError(404, "User not found!"))
const isPasswordCorrect = await bcrypt.compare(req.body.password, user.password)
if(!isPasswordCorrect) return next(createError(400,"Wrong password or Username!"))
const accessToken = jwt.sign({id: user._id, isAdmin: user.isAdmin}, 'secretkey')
const {password, ...others} = user._doc
if(others.status === 'active'){
res.status(200).json({...others, accessToken})
}
else{
next(createError(203, "Account is still pending.."))
}
} catch (error) {
next(error);
}
}
authSlice.js
const authSlice = createSlice({
name: 'auth',
initialState: {
currentUser: null,
isFetching : false,
isError: false,
isSuccess: false,
message: ''
},
reducers: {
resetState: (state) =>{
state.currentUser = null;
state.isFetching = false
state.isError = false
state.isSuccess = false
},
loginStart: (state)=>{
state.isFetching = true;
state.isError = false;
},
loginSuccess: (state, action) =>{
state.isFetching = true;
state.isSuccess = true;
state.currentUser = action.payload
state.message = action.payload
},
loginFailure: (state, action) =>{
state.isSuccess = false;
state.isFetching = false;
state.isError = true;
state.message = action.payload
},
}
})
In authSlice.js, I have a problem that it automatically run the loginFailure, even though the user correctly input it's credential.
apiCall.js
const loginUser = async (user, dispatch) => {
dispatch(loginStart());
try {
const res = await publicRequest.post("/auth/login", user);
dispatch(loginSuccess(res.data));
} catch (err) {
dispatch(loginFailure());
}
};
Login.jsx
const {isError, currentUser, message} = useSelector((state) => state.auth)
const dispatch = useDispatch()
const navigate = useNavigate()
const {register, handleSubmit, formState: {errors}} = useForm({
studentNumber: '',
password: ''
})
const onSubmit = ({studentNumber,password}) =>{
let user ={studentNumber, password}
loginUser(user,dispatch)
}
useEffect(() =>{
if(isError){
toast.error(message)
}
dispatch(resetState())
},[dispatch, isError])
useEffect(() =>{
if(currentUser){
toast.success(message)
}
},[dispatch, currentUser])
Login.jsx
const Login () =>{
const {isError, currentUser, message} = useSelector((state) => state.auth)
const dispatch = useDispatch()
const navigate = useNavigate()
const {register, handleSubmit, formState: {errors}} = useForm({
studentNumber: '',
password: ''
})
const onSubmit = ({studentNumber,password}) =>{
let user ={studentNumber, password}
loginUser(user,dispatch)
}
useEffect(() =>{
if(isError){
toast.error(message)
}
dispatch(resetState())
},[dispatch, isError])
useEffect(() =>{
if(currentUser){
toast.success(message)
}
},[dispatch, currentUser])
return (
<form onSubmit={handleSubmit(onSubmit)}>
<TextField name="studentNumber" {...register('studentNumber)} type="text" />
<TextField name="password" {...register('password)} type="password" />
</form>
)
}
EDIT
Please ignore the form field if I type it wrong, I just copy paste it and missed something
In apiCall.js, don't just do dispatch(loginFailure());, instead inspect the response and if it has the 203 status code, dispatch the action differently. Maybe login failure, but with a payload that signals to your reducer that the user has logged in successfully, but their account is not active yet.
You probably also want to add another boolean flag to your state, something like state.auth.isActive, that is only true if the user account is not pending anymore. Or alternatively, if state.auth.currentUser is an object with more info, maybe the active/pending flag is contained in there already (in this case don't duplicate it).

Firebase `displayName` returns Null - React JS

I use React js Material UI, when I call currentUser.displayName it returns null and when I call currentUser.email it works fine for me.
Here's the function to register a new account to firebase and to update the displayName value from a textFiled which is firstName and lastName :
const firstNameRef = useRef();
const lastNameRef = useRef();
const emailRef = useRef();
const passwordRef = useRef();
const registerHandler = async (e) => {
e.preventDefault();
const firstName =
firstNameRef.current.value.charAt(0).toUpperCase() +
firstNameRef.current.value.slice(1);
const lastName =
lastNameRef.current.value.charAt(0).toUpperCase() +
lastNameRef.current.value.slice(1);
const fullName = `${firstName} ${lastName}`;
const email = emailRef.current.value;
const password = passwordRef.current.value;
// password verification
if (password.length < 6) {
setPasswordError("Password must be at least 6 digits!");
return;
}
// create account
try {
setLoading(true);
setIsError("");
setEmailError("");
setPasswordError("");
await register(email, password)
.then((response) => {
response.user.updateProfile({
displayName: fullName,
});
setIsError("");
setEmailError("");
setPasswordError("");
})
.catch((error) => {
setEmailError(
"The email address is already in use by another account."
);
});
} catch {
setIsError("Error Creating your Account!");
}
setLoading(false);
setSnackBarOpen(true);
};
and here's context code which wraps the app component:
const AuthContext = createContext(null);
export const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState();
const [loading, setLoading] = useState(true);
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((user) => {
setCurrentUser(user);
setLoading(false);
});
return unsubscribe;
}, []);
const login = async (email, password) => {
await auth.signInWithEmailAndPassword(email, password);
};
const register = async (email, password) => {
await auth.createUserWithEmailAndPassword(email, password);
};
const logout = async () => {
await auth.signOut();
};
const resetPassword = async (email) => {
await auth.sendPasswordResetEmail(email);
};
return (
<AuthContext.Provider
value={{ currentUser, login, register, logout, resetPassword }}
>
{!loading && children}
</AuthContext.Provider>
);
};
export const useAuth = () => {
return useContext(AuthContext);
};
and here's my first name & last name TextFields in JSX:
<TextField
fullWidth
label="First Name"
disabled={loading}
type="text"
required
inputRef={firstNameRef}
/>
<TextField
fullWidth
disabled={loading}
label="Last Name"
type="text"
required
inputRef={lastNameRef}
/>
Looks like the problem is here:
const register = async (email, password) => {
await auth.createUserWithEmailAndPassword(email, password);
};
You are not returning the response from here so you should do this instead:
const register = async (email, password) => {
return await auth.createUserWithEmailAndPassword(email, password);
};

Cannot read properties of undefined (reading 'indexOf') with Firebase v9 Modular React JS

I got this simple form that registers users and send the uid and email data to a collection on firestore, im using latest version of Firebase 9 w/ modular.
The authentication works great, but the firestore part doesnt. It throws me an error:
TypeError: Cannot read properties of undefined (reading 'indexOf')
at Function.fromString (path.ts:229)
at Ac2 (reference.ts:374)
at Login.jsx:29
I dont know what does that mean, I am trying to upload the email and uid information of the registered user to firestore with the order of 'users / (and here the useruid)/ all the user data' but throws me that error.
I leave my code:
import React, { useState, useEffect, useCallback } from 'react'
import { auth, db } from "../firebase"
import { createUserWithEmailAndPassword } from "firebase/auth";
import { collection, doc, addDoc, setDoc } from "firebase/firestore";
function Login() {
const [email, setEmail] = useState('')
const [pass, setPass] = useState('')
const [error, setError] = useState('')
const [userData, setUserData] = useState('')
const ProcessData = e => {
e.preventDefault()
registro()
}
const registro = useCallback(async() => {
try {
await createUserWithEmailAndPassword(auth, email, pass)
.then((userCredential) => {
const user = userCredential.user;
setUserData(user)
})
await addDoc(collection(db, 'users', userData.uid), {
email: userData.email,
uid: userData.uid
})
setEmail('')
setPass('')
setError('')
} catch (error) {
const errorCode = error.code;
const errorMessage = error.message;
setError(errorMessage)
console.log(error)
}
}, [email, pass, error, userData])
return (
<div>
<h3>Registro</h3>
<form onSubmit={ProcessData}>
<label>Email Adress</label>
<input
type="email"
placeholder="Email Address"
onChange={e => setEmail(e.target.value)}
value={email}
/>
<label>Password</label>
<input
type="password"
placeholder="Password"
onChange={e => setPass(e.target.value)}
value={pass}
/>
<button type="submit">Registrarse</button>
</form>
</div>
)
}
export default Login
You don't necessarily have to read data from userData state while adding a document. Try passing the params directly from user object returned so any path segment in doc() won't be undefined:
try {
const { user } = await createUserWithEmailAndPassword(auth, email, pass)
setUserData(user)
// db must not be undefined
// try console.log(db) to ensure it's a Firestore instance
await setDoc(doc(db, 'users', user.uid), {
// ^^^ ^^^<-- DocumentReference and not CollectionReference
email: user.email,
uid: user.uid
})
} catch (e) {
console.log(e)
}
Also you were passing 2 path segments in collection() which might return an error since a CollectionReference path takes odd number of path segments. If you want user's UID as document ID then use setDoc since addDoc will generate a random ID.
Issue
Line 29 of Login: await addDoc(collection(db, 'users', userData.uid),
{
userData.uid is likely the culprit since userData is still the initial empty string ('') state value. Remember, React state updates are asynchronously processed, so the setUserData(user) won't have updated the userData state yet when it's referenced a few lines below in collection(db, 'users', userData.uid).
Solution
I suggest splitting out the second half of the registro function into an useEffect hook with a dependency on the userData state to issue the side-effect of adding the document.
function Login() {
const [email, setEmail] = useState('');
const [pass, setPass] = useState('');
const [error, setError] = useState('');
const [userData, setUserData] = useState(null);
useEffect(() => {
if (userData) {
const { email, uid } = userData;
try {
addDoc(collection(db, 'users', uid), { email, uid });
} catch (error) {
const { code, message } = error;
setError(message);
console.log(error);
}
}
}, [userData]);
const ProcessData = e => {
e.preventDefault();
registro();
}
const registro = useCallback(async() => {
try {
await createUserWithEmailAndPassword(auth, email, pass)
.then((userCredential) => {
const { user } = userCredential;
setUserData(user);
});
setEmail('');
setPass('');
setError('');
} catch (error) {
const { code, message } = error;
setError(message);
console.log(error);
}
}, [email, pass, error, userData])
return (
...
)
}

When I register I cannot see the user name until I refresh or login

when I register it take me to the dashboard page where there is a welcome note with user name but the issue is I cannot see the user name until I refresh or login again. I am sharing both my registration action.js code and my dashboard
my registration.js code
const Register = (props) => {
const { classes } = props
const [name, setName] = useState('')
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const dispatch = useDispatch();
const onRegister = async () => {
dispatch(startRegister(name, email, password));
props.history.push('/dashboard')
}
action.js
export const startRegister = (name, email, password) => {
// const { name, email, password } = props
return () => {
return firebase.auth().createUserWithEmailAndPassword(email, password)
.then((currentUser) => {
// Signed in
const user = firebase.auth().currentUser;
user.updateProfile({
displayName: name,
}).then(function () {
console.log('Updated');
// Update successful.
}).catch(function (error) {
// An error happened.
console.log(error);
});
// history.push('/dashboard')
// ...
})
.catch((error) => {
var errorMessage = error.message;
console.log(errorMessage);
// ..
});
};
};
dashboard.js
const Dashboard = props => {
const { classes } = props
const onSignout = async (e) => {
await firebase.auth().signOut()
//props.history.push('/')
}
const username = useSelector(state => state.auth.name);
return (
<main className={classes.main}>
<Paper className={classes.paper}>
<Typography component="h1" variant="h5">
{`Welcome User ${username ? username : ''}`}
At a glance I belive the issue is because of javascript promises, firebase.auth().createUserWithEmailAndPassword is an asynchronous function.
Try this
In registration.js
const onRegister = async () => {
const userData = {name, email, password}
dispatch(startRegister(props, userData));
}
In action.js
export const startRegister = (props, userData) => {
const { name, email, password } = userData;
return () => {
return firebase.auth().createUserWithEmailAndPassword(email, password)
.then((currentUser) => {
// Signed in
const user = firebase.auth().currentUser;
user.updateProfile({
displayName: name,
}).then(function () {
console.log('Updated');
// Navigate to dashboard after data is been saved
props.history.push('/dashboard');
}).catch(function (error) {
// An error happened.
console.log(error);
});
})
.catch((error) => {
var errorMessage = error.message;
console.log(errorMessage);
// ..
});
};
};

Wait for promise to be resolved from React.Context, then render button

I have a function that fires when a user signs into the app.
signin: (email, password, setErrors, setUser, setUserIdToken) => {
firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then(res => {
const user = res.user
const isVerified = user.emailVerified
firebase
.auth()
.currentUser.getIdTokenResult()
.then(idTokenResult => {
setUserIdToken(idTokenResult)
})
.catch(error => console.log(error))
setUser(user)
const db = firebase.firestore()
if (isVerified) {
db.collection('/users')
.doc(user.uid)
.update({ isVerified: true })
}
})
.catch(err => {
setErrors(prev => [...prev, err.message])
})
},
I have another component that uses the user and userIdToken from the signIn method.
const Home = () => {
const { handleSignout, user, userIdToken } = useContext(firebaseAuth)
const { emailVerified, email } = user
const { claims } = userIdToken
return (
<div>
Home page component
<SurveyResults />
{emailVerified && email.endsWith('xxx') && !claims.admin ? (
<button type="button">hi</button>
) : null}
<button type="submit" onClick={handleSignout}>
sign out
</button>
</div>
)
}
export default Home
I don't have access to the properties of userIdToken immediatly when Home renders, as I am still waiting for the promise to resolve..But I also need to check the props of userIdToken to render a button. I'm not sure how to do that?

Categories

Resources