Changing Parent State with Arrow Function Inside a Function - reactjs

I have a Register User Function Which Looks Like this:
onRegisterUser = () => {
const { email, password, isLoading} = this.state;
const { navigation } = this.props;
registerUser(
email,
password,
() =>
this.setState({
isLoading: !this.state.isLoading,
}),
navigation
);
};
The Function Receives the Input email, pass and isLoading state from the Register Screen and does the following:
import { Alert } from "react-native";
import firebase from "./firebase";
import { newUser } from "./database";
export const registerUser = (email, password, toggleLoading) => {
toggleLoading();
const isInputBlank = !email || !password;
if (isInputBlank) {
Alert.alert("Enter details to signup!");
toggleLoading();
}
//If Everything OK Register User
else {
//CR: change to async-await
firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then(() => {
newUser(firebase.auth().currentUser.uid);
})
.catch(function (error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
if (errorCode == "auth/weak-password") {
alert("The password is too weak.");
} else if (errorCode == "auth/invalid-email") {
alert("Email is Invalid");
} else if (errorCode == "auth/email-already-in-use") {
alert("Email is Already in use!");
} else {
alert(errorMessage);
}
console.log(error);
});
}
};
My problem is that the toggleLoading(); Inside if (isInputBlank) doesn't do anything
I'm trying to change the isLoading state if I get an error (Empty Input in this Example) but it does nothing,
It works only one time in the start and that's it.
If the Alert is Activated when i close it the loading screen Remains
What Am I missing?

Try this on your set loading function
() =>
this.setState((prevState) => ({
isLoading: !prevState.isLoading
})),

should it not be better to chain to the original promise like so:
export const registerUser = (email, password) => {
if (!email && ! password) {
return Promise.reject('Email and Password required'); // or whatever message you like to display
}
return (
yourCallToFirebase()
.then(() => newUser())
.catch(() => {
let errorMessage;
// your error handling logic
return Promise.reject(errorMessage);
})
)
};
usage
onRegisterUser = () => {
const { email, password, isLoading} = this.state;
const { navigation } = this.props;
this.setState({ isLoading: true })
registerUser(email,password)
.then(() => {
// your logic when user gets authenticated (ex. navigate to a route)
})
.catch((errorMessage) => {
// display feedback (like a toast)
})
.finall(() => this.setState({ isLoading: false }));
};

Related

How to wait for cookie before setting authContext and login state. React, Firebase

I have a createUser function that sends an axios req to express and takes some time to finish creating a firebase authenticated user, create a token and send token back to user as a cookie.
I also have a getLoggedIn function that takes the cookie and sends it to express to get authenticated and then adjust my authContext accordingly.
My function looks like this: await createUser(registerData ).then(() => getLoggedIn())
My problem is that getLoggedIn() is being called early, before the createUser is done sending back the cookie. Any idea how to fix this? Is there a way to listen for a cookie change? I dont see other people doing that, do I have the wrong approach in general?
Thanks for any help.
New User Form
const { getLoggedIn, loggedIn } = useContext(AuthContext);
const register = async (e) => {
e.preventDefault();
const registerData = {
email,
password,
passwordVerify,
};
try {
await createUser(registerData).then(() => getLoggedIn());
} catch (err) {
console.log(err);
}
}};
CreateUser
export const createUser = async (props) => {
console.log("New User Creation Initiated", props);
const { email, password, passwordVerify } = props;
let userObject = { tokenId: null };
try {
await firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then(({ user }) => {
console.log("createUserWithEmailAndPassword", user);
user
.getIdToken()
.then((tokenId) => {
userObject = { tokenId: tokenId };
axios.post(`${domain}/auth`, userObject);
})
.catch((err) => console.log("new user error", err));
})
.catch((err) => {
console.log("createUser Error", err);
});
return;
} catch (err) {
console.log("New User Creation Error: ", err);
}
};
AuthContext
const AuthContextProvider = (props) => {
console.log("AuthContextProvider Initiated");
const [loggedIn, setLoggedIn] = useState(null);
const getLoggedIn = async () => {
console.log("getLoggedIn Initiated");
let validatedUser = await axios.get(`${domain}/auth/loggedIn`);
console.log("AuthContextProvider validatedUser", validatedUser);
setLoggedIn(validatedUser.data);
};
useEffect(() => {
getLoggedIn();
}, []);
return (
<AuthContext.Provider value={{ loggedIn, getLoggedIn }}>
{props.children}
</AuthContext.Provider>
);
};
Express
router.post("/", async (req, res) => {
console.log("---User signup initiated---");
try {
const { tokenId } = req.body;
console.log("tokenId passed from frontend: ", tokenId);
admin
.auth()
.verifyIdToken(tokenId)
.then((decodedToken) => {
const { email, uid } = decodedToken;
console.log("Fetched UID: ", uid);
console.log("Fetched email: ", email);
const Users = db.collection("users");
Users.doc(`${uid}`).set({
email: email,
posts: [],
});
console.log("---jwt signing initiated---");
const token = jwt.sign(
{
user: { email, uid },
},
process.env.JWT_SECRET
);
console.log("token log: ", token);
return res
.cookie("token", token, {
httpOnly: true,
sameSite: "none",
secure: true,
})
.send();
});
} catch (err) {
console.log(err);
}
});
router.get("/loggedIn", (req, res) => {
console.log("login validation initiated");
try {
const token = req.cookies.token;
if (!token) {
console.log("no token cookie");
return res.json(null);
}
const validatedUser = jwt.verify(token, process.env.JWT_SECRET);
console.log("Token cookie: ", validatedUser);
res.json(validatedUser);
} catch (err) {
console.log("loggedIn", err);
}
});
You can use onAuthStateChanged like this in your AuthContext.
const AuthContextProvider = (props) => {
console.log("AuthContextProvider Initiated");
const [loggedIn, setLoggedIn] = useState(null);
const getLoggedIn = async () => {
console.log("getLoggedIn Initiated");
let validatedUser = await axios.get(`${domain}/auth/loggedIn`);
console.log("AuthContextProvider validatedUser", validatedUser);
setLoggedIn(validatedUser.data);
};
const handleAuthStateChanged = user => {
if(user) { //user login
getLoggedIn()
} else { //user logout
setLoggedIn(null)
}
}
useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged((user) => {
handleAuthStateChanged(user)
})
return unsubscribe
}, []);
return (
<AuthContext.Provider value={{ loggedIn, getLoggedIn }}>
{props.children}
</AuthContext.Provider>
);
};
In my createUser function I moved my axios call and returned it outside of the firebase.auth() sequence of promises. Seems to work now.
export const createUser = async (props) => {
console.log("New User Creation Initiated", props);
const { email, password, passwordVerify } = props;
let userObject = { tokenId: null };
try {
await firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then(({ user }) => {
console.log("createUserWithEmailAndPassword", user);
user
.getIdToken()
.then((tokenId) => {
userObject = { tokenId: tokenId };
})
.catch((err) => console.log("new user error", err));
})
.catch((err) => {
console.log("createUser Error", err);
});
return axios.post(`${domain}/auth`, userObject);
} catch (err) {
console.log("New User Creation Error: ", err);
}
};

How can I make my function wait until the user data is updated in my useContext provider?

new to react here, I want a new user to enter their details on their first sign in. This includes enterting a username, name, profile picture etc.
When they have submitted their details, I wait for confirmation from firebase and then I want to forward them to their profile (the link structure is domain/p/:username).
However, every time I try it, it ends up trying to head to domain/p/undefined?
When I use react dev tools to inspect, I can see that the username was successfully sent up to my state provider, so I think it's just a matter of timing thats the problem.
Heres the welcome page functions:
//The first method begins the update and checks if the username already exists.
const update = async (e) => {
if (
firstName.trim() === "" ||
lastName.trim() === "" ||
username.trim() === "" ||
bio.trim() === "" ||
addressOne.trim() === "" ||
city.trim() === "" ||
county.trim() === "" ||
postCode.trim() === "" ||
photos.length === 0
) {
window.alert("Invalid data!\nOnly Address line 2 can be empty");
} else {
var usernameRef = db
.collection("users")
.where("username", "==", username);
usernameRef.get().then((docs) => {
if (docs.size === 1) {
docs.forEach((doc) => {
if (doc.id === currentUser.uid) {
sendUpdate();
} else {
window.alert("Username taken");
}
});
} else {
sendUpdate();
}
});
}
};
//This method puts the initial data into firebase except the profile picture
function sendUpdate() {
setLoading("loading");
db.collection("users")
.doc(currentUser.uid)
.set(
{
username: username,
name: firstName,
surname: lastName,
bio: bio,
address1: addressOne,
address2: addressTwo,
notifications: [],
city: city,
county: county,
postcode: postCode,
newUser: false,
},
{ merge: true }
)
.then(() => {
updatePhoto();
})
.catch((err) => console.log(err));
}
//This method uploads the profile picture, then gets the downloadURL of the photo just uploaded and puts it into the user document created in method 2.
//It also trys to send the user to their profile afterwards, but it always ends up as undefined.
const updatePhoto = async () => {
const promises = [];
var userREF = db.collection("users").doc(currentUser.uid);
photos.forEach((photo) => {
const uploadTask = firebase
.storage()
.ref()
.child(
`users/` + currentUser.uid + `/profilePicture/profilePicture.jpg`
)
.put(photo);
promises.push(uploadTask);
uploadTask.on(
firebase.storage.TaskEvent.STATE_CHANGED,
(snapshot) => {
const progress =
(snapshot.bytesTransferred / snapshot.totalBytes) * 100;
if (snapshot.state === firebase.storage.TaskState.RUNNING) {
console.log(`Progress: ${progress}%`);
}
},
(error) => console.log(error.code),
async () => {
const downloadURL = await uploadTask.snapshot.ref.getDownloadURL();
userREF
.update({
profilePicture: downloadURL,
})
.then(async () => {
updateUserData().then(() => {
setLoading("complete");
setTimeout(() => {
history.push("/p/" + userData.username);
}, 3000);
});
});
}
);
return "completed";
});
};
Here is my AuthContext provider: (the function UpdateUserData() is what updates the data after its been put into firebase)
import React, { useContext, useState, useEffect } from "react";
import { auth, db } from "../firebase";
const AuthContext = React.createContext();
export function useAuth() {
return useContext(AuthContext);
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState();
const [userData, setUserData] = useState();
const [loading, setLoading] = useState(true);
function signup(email, password) {
return auth.createUserWithEmailAndPassword(email, password);
}
function login(email, password) {
return auth.signInWithEmailAndPassword(email, password);
}
async function updateUserData() {
if (currentUser) {
var userData = db.collection("users").doc(currentUser.uid);
await userData
.get()
.then((doc) => {
if (doc.exists) {
setUserData(doc.data());
return "success";
}
})
.catch((error) => {
console.log("Error getting document:", error);
return "error";
});
}
}
function logout() {
setUserData();
return auth.signOut();
}
function resetPassword(email) {
return auth.sendPasswordResetEmail(email);
}
function updateEmail(email) {
return currentUser.updateEmail(email);
}
function updatePassword(password) {
return currentUser.updatePassword(password);
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((user) => {
setCurrentUser(user);
setLoading(false);
if (user) {
var userData = db.collection("users").doc(auth.currentUser.uid);
userData
.get()
.then((doc) => {
if (doc.exists) {
setUserData(doc.data());
}
})
.catch((error) => {
console.log("Error getting document:", error);
});
}
});
return unsubscribe;
}, []);
const value = {
currentUser,
userData,
updateUserData,
login,
signup,
logout,
resetPassword,
updateEmail,
updatePassword,
};
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
);
}
And as you can see, once the undefined page has been attempted to load, we can see the username did in fact end up in userData from my context provider:
TIA!
You can resolve this issue by move the redirect link out side of you updatePhoto and put it in useEffect (or any other option base on code flow) then just set an state or check the needed data like userdata.userName is already exists, if its undefined prevent redirect and you can display loader component for example, else execute redirect...
Basic Example:
useEffect(() => {
if(userData.username){
history.push("/p/" + userData.username);
}
}, [userData.username])
const myUpdateFunction = useCallBack(() => {
fetch().then(v => {
setUserData(v);
})
}, [])

React dispatch not working (userService function not triggered in userAction)

When I submit my form, it triggers an action login (from userActions). In this action, I use dispatch to use my userService which makes an API call.
When I submit it, the dispatch is not working. If I console.log the result of the action I have my code that appears, like this:
Action was called // Custom message
dispatch => {
dispatch(request({
email
}))
_services_userService__WEBPACK_IMPORTED_MODULE_1__["userService"].login(email, password).then( appSate => {return appSate;},error => {console.lo…
I am supposed to retrieve my user... What is wrong here ?
LoginForm.js
handleFormSubmit(e) {
e.preventDefault();
const credentials = {
email: this.state.email,
password: this.state.password
}
if (credentials) {
let test = login(credentials);
console.log("Action was called");
console.log(test);
this.setState(redirect => true)
}
}
userActions.js -> login()
export const login = (email,password) => {
console.log('is in action');
return dispatch => {
dispatch(request({ email }));
userService.login(email,password)
.then(
appSate => {
return appSate;
},
error => {
console.log(error);
}
);
};
function request(user) { return { type: userConstants.LOGIN_REQUEST,user } }
}
userService.js -> login()
function login(credentials) {
console.log("In userService login function");
return axios.post('/api/login',credentials)
.then(response => {
if (response.data.success) {
console.log("Login Successful!");
let userData = {
firstname: response.data.user.firstname,
surname: response.data.user.surname,
id: response.data.user.id,
email: response.data.user.email,
auth_token: response.data.access_token,
};
let appState = {
isLoggedIn: true,
user: userData
};
localStorage.setItem("appState",JSON.stringify(appState));
return appState;
}
});
}
I think you forgot return statement userActions.js. Try this
export const login = (email,password) => {
console.log('is in action');
return dispatch => {
dispatch(request({ email }));
return userService.login(email,password)
.then(
appSate => {
return appSate;
},
error => {
console.log(error);
}
);
};
function request(user) { return { type: userConstants.LOGIN_REQUEST,user } }
}

How to pass additional data to a function that adds things to an object?

I am trying to create a user profile document for regular users and for merchants on Firebase. I am trying to add additional to data this document when a merchant signs up, but haven't succeeded. The difference is that merchants are supposed to have a roles array with their roles. If this is not the right approach to deal with differentiating users, I'd also be happy to hear what's best practice.
My userService file
async createUserProfileDocument(user, additionalData) {
console.log('additionalData: ', additionalData) //always undefined
if (!user) return
const userRef = this.firestore.doc(`users/${user.uid}`)
const snapshot = await userRef.get()
if (!snapshot.exists) {
const { displayName, email } = user
try {
await userRef.set({
displayName,
email,
...additionalData,
})
} catch (error) {
console.error('error creating user: ', error)
}
}
return this.getUserDocument(user.uid)
}
async getUserDocument(uid) {
if (!uid) return null
try {
const userDocument = await this.firestore.collection('users').doc(uid).get()
return { uid, ...userDocument.data() }
} catch (error) {
console.error('error getting user document: ', error)
}
}
This is what happens when the user signs up as a merchant in the RegisterMerchant component:
onSubmit={(values, { setSubmitting }) => {
async function writeToFirebase() {
//I can't pass the 'roles' array as additionalData
userService.createUserProfileDocument(values.user, { roles: ['businessOnwer'] })
authService.createUserWithEmailAndPassword(values.user.email, values.user.password)
await merchantsPendingApprovalService.collection().add(values)
}
writeToFirebase()
I am afraid this might have something to do with onAuthStateChange, which could be running before the above and not passing any additionalData? This is in the Middleware, where I control all of the routes.
useEffect(() => {
authService.onAuthStateChanged(async function (userAuth) {
if (userAuth) {
//is the below running before the file above and not passing any additional data?
const user = await userService.createUserProfileDocument(userAuth) //this should return the already created document?
//** do logic here depending on whether user is businessOwner or not
setUserObject(user)
} else {
console.log('no one signed in')
}
})
}, [])
There is onCreate callback function which is invoked when user is authenticated.
Here's how you could implement it
const onSubmit = (values, { setSubmitting }) => {
const { user: {email, password} } = values;
const additionalData = { roles: ['businessOnwer'] };
auth.user().onCreate((user) => {
const { uid, displayName, email } = user;
this.firestore.doc(`users/${uid}`).set({
displayName,
email,
...additionalData
});
});
authService.createUserWithEmailAndPassword(email, password);
}

How to post with Axios in React?

This my first time in React and Axios. I have a login form and sign up form and don't have any database. I want any mock API to simulate a login and sign up which provides a token in the response, in order to save it in a local storage in order to keep the user logged in. Also how do I prevent the user to go the home page (login/logout screen). When they type for example www.blabla.com, I want, if the token exists they still in the app, otherwise the token will be erased.
I tried to fetch data from mock API by axios.get(), it worked but it still static
componentDidMount() { // this For Testing Until Now
axios.get('https://jsonplaceholder.typicode.com/users')
.then(res => {
console.log(res);
this.setState({
users: res.data
}, () => {
console.log('state', this.state.users)
})
});
}
I want to communicate with API that allows my to fetch data and post data to it. This is my login function
handleLogin(e) {
e.preventDefault();
const email = e.target.elements.email.value;
const password = e.target.elements.password.value;
let userData = {};
if(validator.isEmpty(email) || validator.isEmpty(password) || !validator.isEmail(email)) {
this.setState({
error: 'You Have To Complete Your Data Correctly'
}, () => {
console.log('failed');
});
} else {
userData = {email, password};
const { users } = this.state;
if(users.find(item => item.email === userData.email)) {
const index = users.findIndex(item => item.email === userData.email);
this.props.history.push(`./create/${users[index].username}`);
}
}
}
and this is my signup function
handleAddNewUser(e) {
e.preventDefault();
const name = e.target.elements.userName.value.toLowerCase().trim();
const email = e.target.elements.userEmail.value.toLowerCase().trim();
const password = e.target.elements.pass.value;
const repassword = e.target.elements.re_pass.value;
let userInfo = {};
const { users } = this.state;
console.log(name, email);
if (validator.isEmpty(name) || validator.isEmpty(email) ||
validator.isEmpty(password) || validator.isEmpty(repassword) ||
!validator.isEmail(email) || !validator.equals(password, repassword)) {
this.setState({
error: 'You Have to enter valid data, Make Sure That The Fields are Complete',
open: true
});
} else {
userInfo = { name, email, password };
if (
users.find(item => item.name === userInfo.name) ||
users.find(item => item.email === userInfo.email)
) {
this.setState({
error: 'This username or email is used',
open: true
});
} else {
this.setState({
users: this.state.users.concat(userInfo),
success: true
}, () => {
// this.props.history.push(`./create/${userInfo.name}`);
// console.log(users)
});
console.log(users)
}
}
}
You can use axios.post() to send post request.
// POST
const userData = {
email: 'demouser#gmail.com',
username: 'demouser',
password: '1a2b3c4d5e' //This should be encoded
}
axios.post('https://example.com/createUser', userData)
.then(res => {
responseData = res.data
if (responseData.status == 'success') {
const user = responseData.user
...
} else {
alert('Something went wrong while creating account')
}
})

Resources