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')
}
})
Related
I am using the following code to obtain the users idToken before sending it to the backend as an authorisation header:
const user = firebase.auth().currentUser
const idToken = await user.getIdToken()
sent like this:
var res = await axios.post(backUrl + "account/load_balance", {
uid: uid,
id: id
},
{
headers: {
Authorization: 'Bearer ' + idToken
}});
It works well but on one of my pages the request is sent to the server before idtoken variable has filled and the user is still null.
I have read that i need to implement onAuthStateChanged as it waits for the token before triggering: https://firebase.google.com/docs/auth/web/manage-users#web-version-8
firebase.auth().onAuthStateChanged((user) => {
if (user) {
// User is signed in, see docs for a list of available properties
// https://firebase.google.com/docs/reference/js/firebase.User
var uid = user.uid;
// ...
} else {
// User is signed out
// ...
}
});
However i am unsure how to implement this in to my code.
Can anyone advise?
Full code:
const RoutingForPortfolio = (props) => {
let uid = localStorage.getItem("account-info");
let { id } = useParams();
const loadBlockchainData = async (dispatch) => {
if (id === null || id === undefined) {
id = "test";
}
const user = firebase.auth().currentUser
const idToken = await user.getIdToken()
console.log(idToken)
var res = await axios.post(backUrl + "account/load_balance", {
uid: uid,
id: id
},
{
headers: {
Authorization: 'Bearer ' + idToken
}});
if (res.data === null) {
await wait(2);
document.location.href = "/logout"
return;
}
else {
// const web3 = new Web3(new Web3.providers.HttpProvider('https://data.stocksfc.com:3200'));
// dispatch(web3Loaded(web3));
const account = res.data.address;
dispatch(web3AccountLoaded(account));
localStorage.setItem("account-address", account);
if (res.data.token_flag && res.data.exchange_flag) {
await dispatch(setLoginUserName(res.data.name));
await dispatch(setLoginUserEmail(res.data.email));
if (res.data.balance !== null) {
await dispatch(etherBalanceLoaded(res.data.balance[0]));
await dispatch(tokenBalanceLoaded(res.data.balance[1]));
await dispatch(exchangeEtherBalanceLoaded(res.data.balance[2]));
await dispatch(exchangeTokenBalanceLoaded(res.data.balance[3]));
}
}
else {
Swal.fire({
icon: "error",
title: "Error...",
text: "Error 485 - Please report to admin",
});
return;
}
}
};
useEffect(() => {
if (uid) {
loadBlockchainData(props.dispatch);
}
}, [props.dispatch, uid]);
return (
<>
{uid ? (
<div>
<Portfolio id={id} />
</div>
) : (
<Login />
)}
</>
);
};
As you correctly identified, firebase.auth().currentUser is a synchronous action that only gets the user object when it is called. You've also correctly surmised that you instead need to use firebase.auth().onAuthStateChanged() to wait to check if the user is logged in.
This can be achieved by wrapping an onAuthStateChanged listener into a Promise where it is immediately detached after being called once.
function getValidatedUser() {
return new Promise((resolve, reject) => {
const unsubscribe = firebase.auth()
.onAuthStateChanged(
(user) => {
unsubscribe();
resolve(user);
},
reject // pass up any errors attaching the listener
);
});
}
This now allows you to use:
const user = await getValidatedUser();
if (!user) {
// todo: handle no user signed in, such as:
throw new Error("User not signed in!");
}
// if here, user is User object
const idToken = await user.getIdToken()
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);
})
}, [])
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 }));
};
I'm trying to create sign up form in react-native using Firebase.I've used Fetch Blob and Document Picker libraries for getting image and upload it to firebase. And I'm also trying to save the user's name, email, and password in realtime database. But unfortunately, the user data is not going to save in database except the image is uploaded in the firebase storage.
Here is my Firebase Auth Code
handleSignupOnPress = () => {
const {image, email, password} = this.state;
let validation = this.validateData();
console.warn(validation);
if (validation == true) {
this.toggleLoading();
firebaseService
.auth()
.createUserWithEmailAndPassword(email, password)
.then(() => {
// console.warn("User SignUp Successfully");
this.uploadImage(image);
})
.catch(error => {
this.toggleLoading();
var errorCode = error.code;
var errorMessage = error.message;
alert(errorMessage);
// console.warn("ERROR => ", errorCode, errorMessage);
});
}
};
Here is image Upload Code
// First Uploading image and download Image URI then call saveUserToDB()...
uploadImage(uri, mime = 'image/jpeg') {
return new Promise((resolve, reject) => {
const uploadUri =
Platform.OS === 'ios' ? uri.replace('file://', '') : uri;
let uploadBlob = '';
const imageRef = firebaseService
.storage()
.ref('images')
.child(uuid.v4());
fs.readFile(uploadUri, 'base64')
.then(data => {
return Blob.build(data, {type: `${mime};BASE64`});
})
.then(blob => {
uploadBlob = blob;
return imageRef.put(blob, {contentType: mime});
})
.then(() => {
uploadBlob.close();
const downnloadImageURI = imageRef.getDownloadURL().then(url => {
this.setState(
{
imageURI: url,
},
() => {
alert('ImageURI ==> ', this.state.imageURI);
this.saveUserInfo();
},
);
});
return downnloadImageURI;
})
.then(url => {
resolve(url);
})
.catch(error => {
this.toggleLoading();
reject(error);
});
});
}
Here is code for saving user's data
saveUserInfo = () => {
const {userName, email, password, imageURI} = this.state;
const {navigate} = this.props.navigation;
const uid = firebaseService.auth().currentUser.uid;
const params = {
image: imageURI,
username: userName,
email: email,
password: password,
};
//firebaseService.database().ref('/Users').push(params)
firebaseService
.database()
.ref('/Users')
.child(uid)
.set(params)
.then(res => {
this.toggleLoading();
navigate('Login');
})
.catch(err => {
alert(err);
});
};
Here are screenshots of Firebase Console
Are the "Rules" in database given permission to "Write"
Go to the firebase console and open your project.
Go to the database and search for "Rules" tab.
Check the rules are set as below
{
/* Visit https://firebase.google.com/docs/database/security to learn more about security rules. */
"rules": {
".read": true,
".write": true
}
}
I've solved this issue. The issue was in this piece of code.
const downnloadImageURI = imageRef.getDownloadURL().then(url => {
this.setState(
{
imageURI: url,
},
() => {
alert('ImageURI ==> ', this.state.imageURI);
this.saveUserInfo();
},
);
setState was not working and calback was not fired.
And I've made it like this way
const downnloadImageURI = imageRef.getDownloadURL().then(url => {
this.saveUserInfo(url)
);}
I'm using AWS Cognito Javascript SDK in a react application. I have a user that was created in the AWS Console by an admin, and when the user is logged in for the first time they have to reset their password. I go through the newPasswordRequired flow, and when I call the completeNewPasswordChallenge function with the parameters, the onFailure callback is ran. When I log the error I get, {code: "UnknownError", message: "Unknown error"}. However, when I check the AWS Console, the user in the user pool is changed from FORCE_CHANGE_PASSWORD to CONFIRMED.
My code is:
class LoginScreenContainer extends Component {
constructor(props) {
super(props);
this.state = {
isInvalidForm: null,
isFirstLogin: false,
user: null,
userAttr: null
}
this.onFormSubmission = this.onFormSubmission.bind(this);
this.updatePassword = this.updatePassword.bind(this);
}
onFormSubmission = (username, password) => {
const poolData = {
UserPoolId : AWSConfig.cognito.USER_POOL_ID,
ClientId : AWSConfig.cognito.APP_CLIENT_ID
}
const userPool = new CognitoUserPool(poolData);
const userData = {
Username: username,
Pool: userPool
}
const cognitoUser = new CognitoUser(userData);
const authenticationData = {
Username : username,
Password : password
}
const authenticationDetails = new AuthenticationDetails(authenticationData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: (result) => {
console.log(result);
},
onFailure: (err) => {
console.log("Authenticate user failure");
console.log(err);
this.setState({ isInvalidForm: true });
},
newPasswordRequired: (userAttributes) => {
delete userAttributes.email_verified;
delete userAttributes.phone_number_verified;
userAttributes.name = authenticationDetails.username;
console.log(userAttributes);
this.setState({
isFirstLogin: true,
user: cognitoUser,
userAttr: userAttributes
});
}
});
}
updatePassword = (newPassword) => {
const cognitoUser = this.state.user;
const userAttr = this.state.userAttr;
cognitoUser.completeNewPasswordChallenge(newPassword, userAttr, {
onSuccess: (result) => {
console.log("NEW PASSWORD COMPLETED: ");
console.log(result);
},
onFailure: (err) => {
console.log(err);
}
});
}
render() {
return (
<div>
{this.state.isFirstLogin ? (
<NewPasswordForm updatePassword={this.updatePassword} />
) : (
<LoginScreenComponent isInvalidForm={this.state.isInvalidForm} onFormSubmission={this.onFormSubmission}/>
)}
</div>
);
}
}
I believe you need to call completeNewPasswordChallenge within the newPasswordRequired callback.
newPasswordRequired: (userAttributes, requiredAttributes) => {
delete userAttributes.email_verified
cognitoUser.completeNewPasswordChallenge(newPw, userAttributes, {
onSuccess: result => {
AWS.config.credentials.refresh(err => {
if (err) {
throw err
} else {
// do something
}
})
},
newPasswordRequired: (userAttributes, requiredAttributes) => {
delete userAttributes.email_verified
// phone number as well
cognitoUser.completeNewPasswordChallenge(newPw, userAttributes, this.newPasswordRequired)
},
onFailure: err => {
throw err
}
})
},
I believe you have MFA on your account and you need to handle it from callback:
mfaSetup: (challengeName, challengeParameters) => { ... }
When you're handling mfaSetup form cognitoUser.authenticateUser() callback all is good if it's required, but from completeNewPasswordChallenge() callback there is no mfaSetup() in typings, which I believe AWS colleagues should fix it ASAP.
That's why you have empty error code, please check response tab in network dev tools on post req you made. I believe you'll find there MFA_SETUP challenge to solve.