Create a user document after creating a user in authentication in firebase - reactjs

I'm trying to figure out how to create a user document in firestore after a user auth record is created.
My current attempt is below.
When i add the async/await the code generates error messages. When I remove them, the authentication part works to create a user record in the authentication part of firebase, but the firestore record is not created. No error message is generated.
Can anyone see what's going wrong?
import React, {useState} from 'react';
import { auth, firestore } from '../../../services/firebase/firebase';
import { useHistory } from 'react-router-dom';
import Button from '#material-ui/core/Button';
import TextField from '#material-ui/core/TextField';
import Dialog from '#material-ui/core/Dialog';
import DialogActions from '#material-ui/core/DialogActions';
import DialogContent from '#material-ui/core/DialogContent';
import DialogContentText from '#material-ui/core/DialogContentText';
import DialogTitle from '#material-ui/core/DialogTitle';
import { Buttons } from '../navigation/styles';
export default function FormDialog() {
const [open, setOpen] = React.useState(false);
let [loading, setLoading] = useState(false);
const history = useHistory();
const [displayName, setDisplayName ] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState(null);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const handleSubmit = async (event) => {
setLoading(true);
event.preventDefault();
auth.createUserWithEmailAndPassword( email, password)
THE ATTEMPT BELOW LOGS THE UID, BUT SAYS TypeError: user.updateProfile
is not a function
.then((res) => {
console.log("logging user", (auth.currentUser.uid) );
const user = auth.currentUser.uid;
return user.updateProfile({
displayName: displayName
})
firestore.collection('users').doc(auth.currentUser.uid)
.set({
fullName: displayName,
createdAt: firestore.fieldValue.serverTimestamp(),
})
})
THIS IS ANOTHER ATTEMPT, WHICH ASLO DOESNT WORK TO MAKE THE USER
DOCUMENT IN FIRESTORE
.then(() => {
if (auth.currentUser != null) {
auth.currentUser.updateProfile({
displayName: displayName
})
firestore.collection('users').doc(auth.currentUser.uid)
.set({
fullName: displayName,
createdAt: firestore.fieldValue.serverTimestamp(),
})
}
})
//THIS IS ANOTHER ATTEMPT, IN THE ALTERNATIVE TO THE ABOVE, WHICH ALSO
DOESNT WORK
.then((res) => {
const user = auth.currentUser;
return user.updateProfile({
displayName: displayName
})
firestore.collection('users').doc(auth.currentUser.uid)
.set({
fullName: displayName,
createdAt: firestore.fieldValue.serverTimestamp(),
})
})
.then(() => {
history.push("/");
})
.catch(error => {
console.error(error);
})
.then(() => {
clear();
})
.then(() => {
handleClose()
})
.finally(() => {
setLoading(false);
});
};
THIS IS A FURTHER ATTEMPT, WHICH I CAN'T TEST BECAUSE SOMETHING ABOUT THE THEN STATEMENT THAT TRIES TO PUSH HISTORY IS CONSIDERED TO HAVE A PARSING ERROR. I CAN'T FIND ANY TUTORIALS ABOUT HOW TO FIGURE OUT SOLVING THOSE.
const createUserDocument = async (user, displayName) => {
if (!user) return;
const userRef = firestore.doc(`users/${user.uid}`);
const snapshot = await userRef.get();
if (!snapshot.exists) {
const { email } = user;
const { displayName } = displayName;
try {
await userRef.set({
displayName,
email,
createdAt: new Date(),
});
} catch (error) {
console.log('Error in creating user', error);
}
}
};
const handleSubmit = async (event) => {
setLoading(true);
event.preventDefault();
try {
const { user } = await auth.createUserWithEmailAndPassword(
email,
password
);
await createUserDocument(user, { displayName });
} catch (error) {
console.log('error', error);
}
.then(() => {
history.push("/");
})
.then(() => {
clear();
})
.then(() => {
handleClose()
})
.finally(() => {
setLoading(false);
});
};
//continuing after all current attempts
const onChangeHandler = event => {
const { name, value } = event.currentTarget;
if (name === "userEmail") {
setEmail(value);
} else if (name === "userPassword") {
setPassword(value);
} else if (name === "displayName") {
setDisplayName(value);
}
};
const clear = () => {
setDisplayName("");
setEmail("");
setPassword("");
};
return (
<div>
<Buttons onClick={handleClickOpen}>
Join
</Buttons>
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Join the waitlist</DialogTitle>
<DialogContent>
<DialogContentText>
Join
</DialogContentText>
<TextField
autoFocus
margin="dense"
label="Full name"
type="text"
fullWidth
name="displayName"
value={displayName}
placeholder="Jill Green"
id="displayName"
onChange={event => onChangeHandler(event)}
/>
<TextField
margin="dense"
label="Email Address"
type="email"
fullWidth
name="userEmail"
value={email}
placeholder="email address"
id="userEmail"
onChange={event => onChangeHandler(event)}
/>
<TextField
margin="dense"
label="Password"
type="password"
fullWidth
name="userPassword"
value={password}
id="userPassword"
placeholder="Minimum 6 characters"
onChange={event => onChangeHandler(event)}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button
onClick={handleSubmit}
color="primary">
Register
</Button>
</DialogActions>
</Dialog>
</div>
);
}

Apart from ongoing issues trying to figure out how to record timestamps in firebase, this works to create the user document record.
const handleSubmit = async (event) => {
setLoading(true);
event.preventDefault();
auth.createUserWithEmailAndPassword(
email,
password
)
.then(credential => {
if (credential && credential.user) {
firestore.collection("users")
.doc(credential.user.uid)
.set({
email: email,
displayName: displayName,
// createdAt: firestore.Timestamp.now()
// createdAt: firestore.fieldValue.serverTimestamp()
// createdAt: firebase.firestore.fieldValue.serverTimestamp()
});
history.push("/");
}
})

Related

setting state not working in react in a weird way

I made a hook to interact with my api:
import { useContext, useCallback } from "react";
import { toastContext } from "../trds";
export default function useAPIHook(){
const launchToast = useContext(toastContext);
const returnFunction = useCallback(async ({
user, url, fetchOptions = {}, okCallback = () => {}, failedCallback = () => {}
}) => {
const idToken = await user.getIdToken();
const apiResponse = await fetch(`${process.env.REACT_APP_BACKEND_URL}${url}?idToken=${idToken}`, fetchOptions);
const apiReturn = await apiResponse.json();
if(apiResponse.ok) okCallback(apiReturn);
else{
if(apiResponse.status === 400)
launchToast(apiReturn.error, 'error');
else{
console.log(apiReturn.error);
launchToast('Something went wrong. Contact the developer!', 'error');
}
failedCallback();
}
}, [launchToast]);
return returnFunction;
}
and then the users hook:
import { useContext, useState, useEffect } from "react";
import { userContext } from "../contexts/user";
import { toastContext } from "../trds";
import useAPIHook from "./useAPIHook";
export default function useUsers(){
const currentUser = useContext(userContext);
const api = useAPIHook();
const launchToast = useContext(toastContext);
const [users, setUsers] = useState([]);
useEffect(() => {
(async () => {
await api({
user: currentUser,
url: '/users',
okCallback: (returnedUsers) => {
setUsers(returnedUsers);
},
failedCallback: () => {
launchToast('Failed while fetching users!', 'error')
}
});
})();
}, [api, currentUser, launchToast]);
const createNewUser = async (userData) => {
await api({
user: currentUser,
url: '/users',
fetchOptions:{
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(userData)
},
okCallback: (newUser) => {
setUsers(prevState => [...prevState, newUser]);
launchToast('New user has been created!', 'success')
},
failedCallback: () => {
launchToast('Failed while creating user!', 'error')
}
});
}
const updateUser = async (userData) => {
await api({
user: currentUser,
url: `/users/${userData._id}`,
fetchOptions:{
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(userData)
},
okCallback: (updatedUser) => {
setUsers(prevUsers => {
return prevUsers.map(user => user._id === updatedUser._id ? updateUser : user);
});
launchToast('User has been updated!', 'success')
},
failedCallback: () => {
launchToast('Failed while updating user!', 'error')
}
});
}
const deleteUser = async (userData) => {
await api({
user: currentUser,
url: `/users/${userData._id}`,
fetchOptions:{
method: 'DELETE',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(userData)
},
okCallback: (deletedUser) => {
setUsers(prevUsers => {
return prevUsers.filter(user => user._id !== deletedUser._id);
});
launchToast('User has been deleted!', 'success');
},
failedCallback: () => {
launchToast('Failed while deleting user!', 'error')
}
});
}
return { users, createNewUser, updateUser, deleteUser }
}
my problem is that in the createNewUser function at okCallback, the state wont update with the newUser pushed to the state. nothing happens, like no errors, nothing. I debugged newUser 100 times and its good. setting the users state in the other functions (updateUser, deleteUser) is working properly. does anybody have an idea what could it be?
UPDATE: its not working in updateUser neither, only works in deleteUser
UPDATE 2: the updateUser and createNewUser are being called in my user form modal:
import { useEffect, useState, useContext, useCallback } from "react";
import { Button, Modal, toastContext } from "../trds";
import useUsers from "../hooks/useUsers";
const initialState = {
email: '',
name: '',
role: 'viewer'
};
export default function UserFormModal({userData, isOpen, onClose}){
const launchToast = useContext(toastContext);
const { createNewUser, updateUser } = useUsers();
const [data, setData] = useState(initialState);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
if(userData) setData(userData);
}, [userData]);
const handleSubmit = async () => {
if(!data.email || !data.name){
return launchToast('You have to provide email and name!', 'error');
}
setIsLoading(true);
if(userData){
await updateUser(data)
} else {
await createNewUser(data);
}
setData(initialState);
onClose();
setIsLoading(false);
}
const handleClose = useCallback(() => {
setData(initialState);
onClose();
}, [onClose]);
return(
<Modal
title={userData ? 'Update user' : 'Create user'}
isOpen={isOpen}
onClose={handleClose}
>
<trds-stack style={{gap: 'var(--space--l)'}}>
<input
type="email"
name="userEmail"
placeholder="Email address"
value={data.email}
onChange={(e) => setData(prevState => ({...prevState, email: e.target.value}))}
/>
<input
type="text"
name="userName"
placeholder="Name"
value={data.name}
onChange={(e) => setData(prevState => ({...prevState, name: e.target.value}))}
/>
<trds-stack style={{gap: 0}}>
<label>Role</label>
<select
name="userRole"
onChange={(e) => setData(prevState => ({...prevState, role: e.target.value}))}
value={data.role}
>
<option value="viewer">Viewer</option>
<option value="technician">Technician</option>
<option value="admin">Admin</option>
</select>
</trds-stack>
<Button
text={userData ? 'Update user' : 'Create user'}
loading={isLoading}
onClick={handleSubmit}
/>
</trds-stack>
</Modal>
)
}
SO, it may have correlation...

User is logged in and X-XSRF-TOKEN is passed in the headers but just displaying 401 Unauthorized in Laravel Sanctum & NextJs

I am developing a NextJs app with a Laravel backend. I chose to go with Token based authentication. Logging in and fetching user data via Postman is working fine. But it's only returning 401 Unauthorized on the NextJs app for fetching user data even though logging in is fine.
I used this https://github.com/laravel/breeze-next as an example. I implemented the same as it has.
This is the error message that I received on console log.
GET http://localhost:8000/api/v1/auth-user 401 (Unauthorized)
CODES
axios.js
import Axios from 'axios';
const axios = Axios.create({
baseURL: process.env.NEXT_PUBLIC_BACKEND_URL,
headers: {
'X-Requested-With': 'XMLHttpRequest',
},
withCredentials: true,
});
export default axios;
useAuth.js
import useSWR from 'swr';
import axios from '../lib/axios';
import { useEffect } from 'react';
import { useRouter } from 'next/router';
export const useAuth = ({ middleware, redirectIfAuthenticated } = {}) => {
const router = useRouter();
const {
data: user,
error,
mutate,
} = useSWR('/api/v1/auth-user', () =>
axios
.get('/api/v1/auth-user')
.then((res) => res.data)
.catch((error) => {
if (error.response.status !== 409) throw error;
router.push('/verify-email');
})
);
const csrf = () => axios.get('/sanctum/csrf-cookie');
const register = async ({ setErrors, ...props }) => {
await csrf();
setErrors([]);
axios
.post('/register', props)
.then(() => mutate())
.catch((error) => {
if (error.response.status !== 422) throw error;
setErrors(error.response.data.errors);
});
};
const login = async ({ setErrors, setStatus, ...props }) => {
await csrf();
setErrors([]);
setStatus(null);
axios
.post('/login', props)
.then(() => mutate())
.catch((error) => {
if (error.response.status !== 422) throw error;
setErrors(error.response.data.errors);
});
};
const forgotPassword = async ({ setErrors, setStatus, email }) => {
await csrf();
setErrors([]);
setStatus(null);
axios
.post('/forgot-password', { email })
.then((response) => setStatus(response.data.status))
.catch((error) => {
if (error.response.status !== 422) throw error;
setErrors(error.response.data.errors);
});
};
const resetPassword = async ({ setErrors, setStatus, ...props }) => {
await csrf();
setErrors([]);
setStatus(null);
axios
.post('/reset-password', { token: router.query.token, ...props })
.then((response) =>
router.push('/login?reset=' + btoa(response.data.status))
)
.catch((error) => {
if (error.response.status !== 422) throw error;
setErrors(error.response.data.errors);
});
};
const resendEmailVerification = ({ setStatus }) => {
axios
.post('/email/verification-notification')
.then((response) => setStatus(response.data.status));
};
const logout = async () => {
if (!error) {
await axios.post('/logout').then(() => mutate());
}
window.location.pathname = '/login';
};
useEffect(() => {
if (middleware === 'guest' && redirectIfAuthenticated && user)
router.push(redirectIfAuthenticated);
if (window.location.pathname === '/verify-email' && user?.email_verified_at)
router.push(redirectIfAuthenticated);
if (middleware === 'auth' && error) logout();
}, [user, error]);
return {
user,
register,
login,
forgotPassword,
resetPassword,
resendEmailVerification,
logout,
};
};
login.js
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import { useAuth } from '../hooks/useAuth';
const LoginScreen = () => {
const [email, setEmail] = useState(null);
const [password, setPassword] = useState(null);
const [shouldRemember, setShouldRemember] = useState(false);
const [errors, setErrors] = useState([]);
const [status, setStatus] = useState(null);
const router = useRouter();
const { login, user } = useAuth({
middleware: 'guest',
redirectIfAuthenticated: '/',
});
useEffect(() => {
if (router.query.reset?.length > 0 && errors.length === 0) {
setStatus(atob(router.query.reset));
} else {
setStatus(null);
}
}, [errors, router]);
const submitHandler = (e) => {
e.preventDefault();
login({ email, password, remember: shouldRemember, setErrors, setStatus });
console.log(email, password);
};
return (
<div>
<form onSubmit={submitHandler}>
<input type="email" onChange={(e) => setEmail(e.target.value)} />
<br />
<input type="password" onChange={(e) => setPassword(e.target.value)} />
<br />
<button type="submit">Sign In</button>
</form>
</div>
);
};
export default LoginScreen;

auth.currentUser.updateProfile is not a function

I'm trying to change the username and the user's profile img of the user but it misses the function I've imported. I've tried using auth.currentUser.updateProfile but it does not recognize it as a function I cannot even put await on front of the function.
It happens when doneHandle is called. Here the auth is basically the "const auth = getAuth(app);" initialization of the application.
import React, { useContext, useState } from 'react';
import { AuthContext } from '../App';
import { updateProfile } from 'firebase/auth';
export default function Dashboard() {
const { currentUser, auth } = useContext(AuthContext);
const [content, setContent] = useState('');
const [open, setOpen] = useState(false);
const [dia, setDia] = useState('');
const handleClickOpen = (e) => {
setDia(e.target.value);
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const doneHandle = (e) => {
if (e.target.value === 'Profile') {
auth.currentUser
.updateProfile({
displayName: content,
})
.then(function (error) {
console.log(error);
});
} else {
currentUser
.updateProfile({
photoURL: content,
})
.then(function (error) {
console.log(error);
});
}
};
return (
<Removed some code that was not related to the ques>
<Dialog open={open} onClose={handleClose}>
<DialogContent>
<DialogContentText>
{dia === 'Profile'
? 'Enter your name down below.'
: 'Please paste the direct url to the image. '}
</DialogContentText>
<TextField
autoFocus
margin='dense'
id='name'
type='email'
fullWidth
variant='standard'
value={content}
onChange={(e) => {
setContent(e.target.value);
}}
/>
</DialogContent>
<Button
value={dia}
onClick={(e) => {
doneHandle(e);
}}
>
Done
</Button>
</Dialog>
</div>
</div>
);
}
You're importing updateProfile as a top-level function from the v9 modular SDK, but then try to invoke a updateProfile method on the user object - which is the namspaced syntax for the v8 SDK. You can't just mix and match those syntaxes.
As shown in the code sample in the documentation on updating a user profile, you should call the top-level function and pass the current user as arguments:
updateProfile(currentUser, {
displayName: content
})

Firebase Error TypeError: Cannot create property '_canInitEmulator' on string 'k#k.fr'

I m trying to implement an Firebase 9 auth system on my ReactJS project. Unfornutually I have one error like this when I m trying to auth with Email and Password.
I use also Login with google account and It work fine. I try to do some adjustments but always the same error append. I know how I can fix this.
Ask if u have more question about my code ;)
Uncaught (in promise) TypeError: Cannot create property '_canInitEmulator' on string 'k#k.fr'
at _performFetchWithErrorHandling (index.ts:131:1)
at _performApiRequest (index.ts:89:1)
at _performSignInRequest (index.ts:189:1)
at signInWithPassword (email_and_password.ts:45:1)
at EmailAuthCredential._getIdTokenResponse (email.ts:116:1)
at _processCredentialSavingMfaContextIfNecessary (mfa_error.ts:72:1)
at _signInWithCredential (credential.ts:37:1)
at signInWithCredential (credential.ts:69:1)
at signInWithEmailAndPassword (email_and_password.ts:267:1)
at onClick (Login.js:41:1)
Do you have any idea ?
init-firebase.js
// Initialize Firebase
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
const googleProvider = new GoogleAuthProvider();
const signInWithGoogle = async () => {
try {
const res = await signInWithPopup(auth, googleProvider);
const user = res.user;
const q = query(collection(db, "users"), where("uid", "==", user.uid));
const docs = await getDocs(q);
if (docs.docs.length === 0) {
await addDoc(collection(db, "users"), {
uid: user.uid,
name: user.displayName,
authProvider: "google",
email: user.email,
});
}
} catch (err) {
console.error(err);
alert(err.message);
}
};
const logInWithEmailAndPassword = async (email, password) => {
try {
await signInWithEmailAndPassword(auth, email, password);
} catch (err) {
console.error(err);
alert(err.message);
}
};
const registerWithEmailAndPassword = async (name, email, password) => {
try {
const res = await createUserWithEmailAndPassword(auth, email, password);
const user = res.user;
await addDoc(collection(db, "users"), {
uid: user.uid,
name,
authProvider: "local",
email,
});
} catch (err) {
console.error(err);
alert(err.message);
}
};
const sendPasswordReset = async (email) => {
try {
await sendPasswordResetEmail(auth, email);
alert("Password reset link sent!");
} catch (err) {
console.error(err);
alert(err.message);
}
};
const logout = () => {
signOut(auth);
};
export {
auth,
db,
signInWithGoogle,
logInWithEmailAndPassword,
registerWithEmailAndPassword,
sendPasswordReset,
logout,
signInWithEmailAndPassword,
};
Login.js
import React, { useEffect, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { auth, signInWithEmailAndPassword, signInWithGoogle } from "../lib/init-firebase";
import { useAuthState } from "react-firebase-hooks/auth";
function Login() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [user, loading, error] = useAuthState(auth);
const navigate = useNavigate();
useEffect(() => {
if (loading) {
// maybe trigger a loading screen
return;
}
if (user) navigate("/dashboard");
}, [user, loading]);
return (
<div className="body-size">
<input
type="text"
className="login__textBox"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="E-mail Address"
/>
<input
type="password"
className="login__textBox"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
<button
className="login__btn"
onClick={() => signInWithEmailAndPassword(email, password)}
>
Login
</button>
</div>
</div>
</div>
);
}
export default Login;

How do I wait for successful sign in with firebase using a popup dialoug and redux saga

I have a popup dialog where the user can sign in. It works however I want the dialog to wait until the sign in is successful before it closes. I am also using Redux and I tried adding await before emailSignInStart(email, password) but that doesn't work. Here is my Signin Form:
const SigninForm = ({ emailSignInStart, handleClose }) => {
const { switchToSignup } = useContext(AccountContext);
const [userCredentials, setCredentials] = useState({
email: '',
password: '',
});
const { email, password } = userCredentials;
const signIn = async event => {
event.preventDefault();
try {
emailSignInStart(email, password);
handleClose(); <---------------------- Don't call this until sign in successful
} catch(error){
}
};
const handleChange = event => {
const { value, name } = event.target;
setCredentials({ ...userCredentials, [name]: value });
};
return <BoxContainer>
<Marginer direction={'vertical'} margin={15} />
<FormContainer>
<Input name={'email'} type={'email'} onChange={handleChange} placeholder={'Email'} />
<Input name={'password'} type={'password'} onChange={handleChange} placeholder={'Password'} />
<Marginer direction={'vertical'} margin={10} />
<MutedLink href={'#'}>Forgot Password?</MutedLink>
<Marginer direction={'vertical'} margin={'1.6em'} />
<Submit type={'submit'} onClick={signIn}>Sign In</Submit>
<Marginer direction={'vertical'} margin={'1em'} />
<MutedLink href={'#'}>
Don't have an account?
<BoldLink href={'#'} onClick={switchToSignup}>Sign Up</BoldLink>
</MutedLink>
</FormContainer>
</BoxContainer>;
};
const mapDispatchToProps = dispatch => ({
emailSignInStart: (email, password) =>
dispatch(emailSignInStart({ email, password })),
});
export default connect(null, mapDispatchToProps)(SigninForm);
In my user sagas I have:
export function* getSnapshotFromUserAuth(userAuth, additionalData) {
try {
const userRef = yield call(
createUserProfileDocument,
userAuth,
additionalData
);
const userSnapshot = yield userRef.get();
yield put(
signInSuccess({ id: userSnapshot.id, ...userSnapshot.data() })
);
} catch (error) {
yield put(signInFailure(error.message));
}
}
export function* signInWithEmail({ payload: { email, password } }) {
try {
const { user } = yield auth.signInWithEmailAndPassword(email, password);
yield getSnapshotFromUserAuth(user);
return user;
} catch (error) {
yield put(signInFailure(error.message));
}
}
export function* onEmailSignInStart() {
yield takeLatest(UserActionTypes.EMAIL_SIGN_IN_START, signInWithEmail);
}
In my user actions I have:
export const emailSignInStart = emailAndPassword => ({
type: UserActionTypes.EMAIL_SIGN_IN_START,
payload: emailAndPassword,
});
export const signInSuccess = user => ({
type: UserActionTypes.SIGN_IN_SUCCESS,
payload: user,
});
export const signInFailure = error => ({
type: UserActionTypes.SIGN_IN_FAILURE,
payload: error,
});
I don't know why but I got this working. All I did is remove the handleClose call after the emailSignInStart(email, password); and it all works. No where does it call the close dialog anymore but it still closes after the sign in is successful.

Resources