React Native - How to deal with asynchronism with AsyncStorage - javascript

I am facing to asynchronism problem :
I create a user in firebase, generating a unique ID for it.
I get this unique ID.
I call an async function to persist this ID with AsyncStorage method.
Problem : The asyncStorage method is called before I get back the generated ID from my user creation. How to deal with this ?
This is my code :
class Subscription extends Component {
constructor() {
super();
this.state = {
email: '',
password: ''
}
}
persistUserId = (userID) => {
try {
AsyncStorage.setItem('userId', userID); // Here, user ID is undefined
} catch (error) {
console.log(error.message);
}
};
updateInputValue = (value, prop) => {
const state = this.state;
state[prop] = value;
this.setState(state);
}
registerUser = () => {
var generatedUserId = '';
firebase
.auth()
.createUserWithEmailAndPassword(this.state.email, this.state.password) // Authentication
.then((res) => {
var user = { // Set Javascript Object to insert
email: this.state.email
}
database.collection("users").add({ // Create the new user generating an ID
'email': user.email,
}).then(function(docRef) {
generatedUserId = docRef.id; // Get the generated ID (The one to persist witch asyncstorage)
}).then(function() {
this.persistUserId(generatedUserId) // Call the AsyncStorage to persist the ID
})
this.props.navigation.navigate('AppPage') // Go to next page.
})
.catch(error => {
alert(error.message)
})
}

For persisting data. According to react-native doc. You need to use async await keyword:
_storeData = async () => {
try {
await AsyncStorage.setItem(
'#MySuperStore:key',
'I like to save it.'
);
} catch (error) {
// Error saving data
}
}
for your case:
persistUserId = async (userID) => {
try {
await AsyncStorage.setItem('userId', userID); // Here, user ID is undefined
} catch (error) {
console.log(error.message);
}
};
Note: Persisting data is async process. That's why you need to use async await
You need to update your firebase then catch as well. Either use bind or use arrow function. Here is updated version:
firebase
.auth()
.createUserWithEmailAndPassword(this.state.email, this.state.password) // Authentication
.then((res) => {
var user = {
// Set Javascript Object to insert
email: this.state.email,
};
database
.collection("users")
.add({
// Create the new user generating an ID
email: user.email,
})
.then( (docRef) => {
generatedUserId = docRef.id; // Get the generated ID (The one to persist witch asyncstorage)
})
.then( () => {
this.persistUserId(generatedUserId); // Call the AsyncStorage to persist the ID
});
this.props.navigation.navigate("AppPage"); // Go to next page.
})
.catch((error) => {
alert(error.message);
});

Related

What is the difference between passing in argument as object vs string into a function

I have below is a method that signs a user up with their email and password and create a document in Firebase when the user enters their info into a form and clicks submit:
const onSubmitHandler = async (e) => {
e.preventDefault();
try {
const { user } = await createAuthUserWithEmailAndPassword(email, password);
console.log(user, 'user')
await createUserDocumentFromAuth(user, { displayName })
}catch(err) {
if(err === 'auth/email-already-in-use') {
alert('Account with this email already exists');
return;
}else {
console.log(err)
}
}
}
For this function call:
await createUserDocumentFromAuth(user, { displayName })
where displayName can be a string, such as ElonMusk.
In the actual createUserDocumentFromAuth, I am calling the setDoc method, which is one from Firebase to set a user document:
export const createUserDocumentFromAuth = async ( userAuth, additionalInfo = {} ) => {
if(!userAuth) return;
console.log(additionalInfo, 'additionalInfo')
const userDocRef = doc(db, 'users', userAuth.uid);
const userSnapshot = await getDoc(userDocRef);
if(!userSnapshot.exists()) {
const { displayName, email } = userAuth;
const createdAt = new Date();
try {
// set the doc here
await setDoc(userDocRef, {
displayName,
email,
createdAt,
...additionalInfo
});
} catch(err) {
console.log('err creating the user', err)
}
};
return userDocRef;
}
The reason I passed { displayName } in manually is because there is a case where the server's response to createAuthUserWithEmailAndPassword(email, password) has displayName to be null, but we want the user to have a displayName registered in the database.
My question is:
Why does displayName only work when it is passed in as an object and not just in its normal form? For example:
await createUserDocumentFromAuth(user, { displayName })
will replace the displayName: null
But not when I pass in:
await createUserDocumentFromAuth(user, displayName)
What is this technique called in JavaScript?
If you look into createUserDocumentFromAuth, you'll see that it's expects two arguments userAuth and additionalInfo, both arguments are expected to be objects.
It later uses data from additionalInfo to add/overwrite anything in userAuth when calling setDoc() method.
So, I'd recommend add
console.log(userDocRef, {
displayName,
email,
createdAt,
...additionalInfo
});
to see what what data is being sent to setDoc()

React useEffect is not triggering on redirect

i have a function called login that redirects the user to the main page if everything was ok. Then, on the main page, i want to fetch some user info with useEffect using the token the was stored when the user logged in, but nothing happens. Only when i refresh the page i get the data.
login function
export const login = ({ email, password, history }) => {
return async (dispatch) => {
try {
const response = await fetch("http://localhost:5000/api/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email,
password,
}),
});
const data = await response.json();
if (data.status === 200) {
localStorage.setItem("userToken", data.user);
history.push("/");
} else {
dispatch(
setNotification({
variant: "error",
message: data.message,
})
);
}
} catch (e) {
console.log(e.message);
}
};
};
fetch user funtion
export const fetchUser = () => {
return async (dispatch) => {
try {
const response = await fetch("http://localhost:5000/userInfo", {
headers: {
"x-access-token": localStorage.getItem("userToken"),
},
});
const data = await response.json();
dispatch(setUser({
id: data.id,
fullname: data.fullname,
email: data.email
}))
} catch (error) {}
};
};
useEffect on my main page
useEffect(() => {
dispatch(fetchUser());
}, []);
backend function
module.exports.getCurrentUser = async (req, res) => {
const token = req.headers["x-access-token"];
try {
const verifyToken = jwt.verify(token, "123");
const user = await User.findOne({ email: verifyToken.email });
return res.json({
id: user._id,
fullname: user.fullname,
email: user.email
})
} catch (error) {}
};
The 2nd parameter to useEffect tells it when it needs to run. It only runs if one of the values in the array has changed. Since you pass an empty array, none of the values in it have changed.
This is presuming your app probably starts at '/', then detects there is no user so switches to the login screen. When it goes back to the root, it only executes useEffect if something in the array has changed from the previous render.
As it is, the isMounted doesn't make much sense. This could simply be:
useEffect(() => {
dispatch(fetchUser());
});
You're calling setUser, but what is calling your login function?

Getting data from Cognito triggers

I have an existing table of users. And I need to get data on user login.
Is it possible to use some cognito trigger for it?
I was trying to use postAuthentication:
postAuthentication:
handler: triggers.postAuthentication
timeout: 10
environment:
GET_USER_LAMBDA: ${file(./env.yml):${'GET_USER_LAMBDA'}}
events:
- cognitoUserPool:
pool: ${file(./env.yml):${'POOL'}}
trigger: PostAuthentication
existing: true
module.exports.postAuthentication = (event, context, callback) => {
try {
const firstName = event.request.userAttributes['custom:firstName'];
const lastName = event.request.userAttributes['custom:lastName'];
lambda.invoke({
FunctionName: GET_USER_LAMBDA,
Payload: JSON.stringify({
query: `${firstName}_${lastName}`
}, null, 2)
})
.promise()
.then(data => {
const body = JSON.parse(data['Payload']).body;
if (body && body.Items && body.Items[0]) {
event.request.clientMetadata = {};
event.request.clientMetadata.body = JSON.stringify(body.Items[0]);
callback(null, event);
} else {
callback(new Error(`Couldn't fetch USER`));
}
});
} catch (error) {
context.done(error);
}
};
The lambda.invoke successfully returns data and there is no any errors but I can't find clientMetadata on front-end.
What trigger should I use and how to get user data?

Firebase Callable Cloud Function returning undefined

I'm creating a callable cloud function which creates a users profile in the cloud firestore db and also add custom claims...
I call it in the front-end as follows
let setUpUser = functions.httpsCallable('setUpUser');
...other code
.then(async ()=>{
try {
const user = await setUpUser({
displayName: this.state.name,
email: user.user.email,
type: this.state.type,
organization: this.state.organization
});
console.log(user);
}
catch (e) {
console.error(e);
}
})
and the in the cloud function I try call the cloud function and set it up using the code below
exports.handler = ((data,context,admin,db )=>{
let uid = context.auth.uid;
return db.doc('users/'+uid).set({
displayName:data.displayName,
email: data.email,
type:data.type,
organization:data.organization
})
.then(()=>{
if (data.type === "teacher"){
return admin.auth().setCustomUserClaims(uid,{
isTeacher:true,
})
}
return;
})
.then(() => {
return admin.auth().getUser(uid).then((userRecord)=>{
return userRecord
})
})
.catch(error =>{
throw new functions.https.HttpsError(error);
});
})
But the log in the front-end is Cannot read property 'user' of undefined

How do i retrieve data from Async storage and use it in my component?

I have saved a user_id and token in Async storage and i can retrieve it in via console log. with the retrive function. So i know the set function is working perfectly, the functions in deviceStorage all Async.
The problem comes when trying to use the retrieved user_id & token in my component it returns undefined.
How can i get an item from storage and use it later in my code, i want to use the token and userid for a fetch request. Please help me and highlight the best way to do.
import deviceStorage from "../components/services/deviceStorage";
class Jobs extends Component {
constructor() {
super();
this.state = {
jobsData: [],
isLoading: true
};
}
componentDidMount() {
deviceStorage.retrieveToken().then(token => {
this.setState({
token: token
});
});
deviceStorage.retrieveUserId().then(user_id => {
this.setState({
user_id: user_id
});
});
const opts = {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Token " + this.state.token
}
};
fetch("http://example.com/job/" + this.user_id, opts)
.then(res => res.json())
.then(jobs => {
this.setState({
jobsData: jobs,
isLoading: false
});
console.log(jobsData);
})
.catch(error => {
console.error(error);
});
}
render {}
Code for the retrieve and set
import {AsyncStorage} from 'react-native';
const deviceStorage = {
async storeItem(key, item) {
try {
//we want to wait for the Promise returned by AsyncStorage.setItem()
//to be resolved to the actual value before returning the value
var jsonOfItem = await AsyncStorage.setItem(key, JSON.stringify(item));
return jsonOfItem;
} catch (error) {
console.log(error.message);
}
},
async retrieveItem(key) {
try {
const retrievedItem = await AsyncStorage.getItem(key);
const item = JSON.parse(retrievedItem);
// console.log(item);
return item;
} catch (error) {
console.log(error.message);
}
return
}
};
export default deviceStorage;`
There are two ways to get the data stored in async storage:
(1) Promise method. Here your code does not wait for the block to finish and returns promise which is accompanied by .then clause if the promise resolves and .catch clause if there is error.
(2) Async and Await method. This is more preferred, here your code waits for the execution before proceeding one of the example to refer is below:
retrieveData() {
AsyncStorage.getItem("id").then(value => {
if(value == null){
//If value is not set or your async storage is empty
}
else{
//Process your data
}
})
.catch(err => {
// Add some error handling
});
Second Method example:
async retrieveData() {
try {
let value = await AsyncStorage.getItem("id");
if (value !== null) {
//you have your data in value variable
return value;
}
}
catch (error) {
// Error retrieving data
}
}
your retrieve data storage methods should look like this
retrieveData = async () => {
try {
const value = await AsyncStorage.getItem('TASKS');
if (value !== null) {
// We have data!!
return value;
}
} catch (error) {
// Error retrieving data
}
return null;
};
Adding to the previous solutions
//function to retrieve data
async function retrieveItem(key) {
try {
const retrievedItem = await AsyncStorage.getItem(key); //dataType String
const item = JSON.parse(retrievedItem);//dataType object
return item;
} catch (error) {
console.log(error.message);
}
return
}
//function call
retrieveItem(key).then((value) => {
//unlike normal function call, this waits for the promise to complete
return value;// actual value not the promise
})
.catch((error) => {
console.log('Error: ' + error);
});

Categories

Resources