Am i promise chaining correctly? - javascript

onSubmit(e) {
e.preventDefault();
const user = {
fname: this.state.firstname,
lname: this.state.lastname,
email: this.state.email,
username: this.state.username,
password: this.state.password
}
new Promise((resolve,reject) => {
this.props.fetchUser(this.state.username)
.then(res => {
this.setState({failed: this.props.exists})
if(!this.state.failed)
this.props.registerUser(user)
})
.then(res => {
this.setState({registered: this.props.status});
resolve();
})
})
}
This is my attempt at chaining promises. The idea was that registered should update correctly to the state of this.props.status (true/false).
When this.props.registerUser is called in the first promise, it changes this.props.status to true. However, registered is being set to false (which is the value of this.props.status before registerUser is called), rather than true.
I know for sure that this.props.status is changing to true, but the state of registered isn't changing.
I'm new to this stuff.

I'm assuming that fetchUser and registerUser are functions that return promises. In that case, you do not need to wrap the call for fetchUser in a new Promise(...) since it will return a promise when invoked.
The reason that the second then(...) is not being called, is that you never return a promise from the first then(...).
if(!this.state.failed)
this.props.registerUser(user)
should become
if(!this.state.failed)
return this.props.registerUser(user)
With these two modifications, your code should look like so
this.props.fetchUser(this.state.username)
.then(res => {
this.setState({
failed: this.props.exists
});
if (!this.state.failed) {
return this.props.registerUser(user)
}
})
.then(res => {
this.setState({
registered: this.props.status
});
})
Furthermore, you would expect to read the result of fetchUser(...) on the res object rather than the component props.
One final caveat that you should be aware of is that setting the state and reading it immediately after, is not guaranteed to always work as expected. The safe way to do it, is to pass a function as your second argument to setState and that will be invoked when the state is updated by React.
The simplest way to do it in this case is to avoid reading the state altogether and instead using a temporary variable.
const exists = this.props.exists;
this.setState({
failed: exists
});
if (!exists ) {
return this.props.registerUser(user)
}

Related

Nested .then in javascript for firebase operation

I have 2 functions like this:
export function senderMessageInbox(senderUID, recieverUID,
recieverProfileURL, recieverUsername) {
return new Promise(function(resolve, reject){
resolve(
firebase.firestore().collection('messages')
.doc(senderUID)
.collection("inboxMessages")
.doc(recieverUID)
.set({
date: new Date().getTime(),
avatarUrl: recieverProfileURL,
message: "",
userId: recieverUID,
username: recieverUsername
})
)
})
}
export function recieverMessageInbox(senderUID, recieverUID,
senderProfileURL, senderUsername) {
return new Promise(function(resolve, reject){
resolve(
firebase.firestore().collection('messages')
Same logic as above, but passing diff variables
)
})
}
I trying to call these in order but need both to complete separately before navigating to another screen. I have something like this:
function startChat() {
console.log("senderMessageInbox");
senderMessageInbox(senderUID, user.uid, user.profileURL, user.username)
.then(() => {
console.log("recieverMessageInbox");
recieverMessageInbox(user.uid, senderUID, currentUser.profileURL, currentUser.username)
})
.then(() => {
console.log("navigation.navigate");
navigation.navigate('Messages')
})
}
Im logging the steps but, it seems like these are overlapping each other and causing overlaps in the db. Is this the correct way? I need these to finish in that order and then navigate to the screen only once they have been completed.
The problem here is that you have the navigation happening asynchronously with the call to receiverMessageInbox. If you need them to happen sequentially, then you must structure the Promise execution accordingly.
function startChat() {
console.log('senderMessageInbox')
senderMessageInbox(senderUID, user.uid, user.profileURL, user.username)
.then(() => {
console.log('receiverMessageInbox')
receiverMessageInbox(user.uid, senderUID, currentUser.profileURL, currentUser.username)
.then(() => {
console.log('navigation.navigate')
navigation.navigate('Message')
})
})
}
Alternatively, you can use async/await to make this less visually confusing:
async function startChat() {
console.log('senderMessageInbox')
await senderMessageInbox(senderUID, user.uid, user.profileURL, user.username)
console.log('receiverMessageInbox')
await receiverMessageInbox(user.uid, senderUID, currentUser.profileURL, currentUser.username)
console.log('navigation.navigate')
await navigation.navigate('Message')
}
You're leaving out some important code where you say firebase logic here, but I'm going to assume you're doing an async call of some sort there.
In this case, you have your resolve() statements in the wrong place. Your resolve is just returning another Promise, which is what the Firebase call will return.
Instead, call resolve() inside the Firebase call once it has returned and you have your data/have completed your updates.
Secondly, in your then chain, you need to return recieverMessageInbox instead of just calling it. So:
return recieverMessageInbox(user.uid, senderUID, currentUser.profileURL, currentUser.username)
Update, based on your added code
There's really not anything the outer Promise is doing, so you could just write your functions like this:
export function senderMessageInbox(senderUID, recieverUID,
recieverProfileURL, recieverUsername) {
return firebase.firestore().collection('messages')
.doc(senderUID)
.collection("inboxMessages")
.doc(recieverUID)
.set({
date: new Date().getTime(),
avatarUrl: recieverProfileURL,
message: "",
userId: recieverUID,
username: recieverUsername
})
}
And apply the same technique to your other function.
And then, like I said before, make sure that you're returning inside your chained then statements:
senderMessageInbox(senderUID, user.uid, user.profileURL, user.username)
.then(() => {
console.log("recieverMessageInbox");
return recieverMessageInbox(user.uid, senderUID, currentUser.profileURL, currentUser.username)
})

How do i combine asynchronous js with vuex store?

So i've been trying to make a 'log in' for my vue app. User logs in by clicking on the button and running the following method:
async signIn() {
this.getfireBaseData(await this.authenticate());
},
Here are both of the methods used inside the previous one:
async authenticate() {
auth
.signInWithPopup(provider)
.then((result) =>
this.$store.commit("setUserData", {
email: result.additionalUserInfo.profile.email,
picture: result.additionalUserInfo.profile.picture,
name: result.additionalUserInfo.profile.name,
})
)
.catch((err) => console.log(err.message));
return this.$store.state.userData;
},
async getfireBaseData(x) {
db.collection("rooms")
.get()
.then((snapshot) => {
this.firebaseData = snapshot.docs.map((doc) => ({
id: doc.id,
data: doc.data(),
}));
console.log(x);
if (x) {
this.$router.push(`/rooms/${this.firebaseData[0].id}`);
}
});
},
And the store:
state: {
userData: null,
},
mutations: {
setUserData(state, payload) {
state.userData = payload;
},
}
I'm expecting it to run getfireBaseData as soon as the authenticate() resolves its promise. For some reason - when i'm running the code for the first time - authenticate() leaves me with fullfiled promise with the value of "null" - the value in the store. It seems like authenticate() does modify the store value but does not return it as i run it instantly. Any solves to that issue?
That's because you are returning directly your store data from authenticate(), not the promise chain.
So you are not waiting your auth.signInWithPopup but resolve directly the current value of your store.
try to returning directly auth.signInWithPopup(provider) and return your userData from the resolve callback or via an other "then" after your catch.
But it might not be a good idea to mix async/await with .then.catch.
It's error prone.

State of array is not updated in ComponentDidMount

I have a weird situation, where I have an array as a state:
this.state = { ActivityItem: []} and I am pushing values to it from library that calls an API like this:
getDataFromKit(dateFrom) {
Kit.getSamples(stepCountSample, (err, results) => { //api call
if (err) {
return;
}
const newData = results.map(item => { return { ...item, name: 'itemAmount' } });
this.setState({ d: [...this.state.ActivityItem, ...newData] })
})
Then, I call this method from ComponentDidMount() for array to be loaded
componentDidMount() {
this.getDataFromHealthKit(ONEDAYINTERVAL);
console.log("values are", this.state.ActivityItem)
}
Now, the weirdest part: somehow the array is empty in ComponentDidMount, but when I display elements of Array in return of render() function it displays all the values that were added correctly. How is that possible and how I might fix that?
setState is asynchronous in nature. Therefore, logging the state just immediately after setting it can give this behaviour but if set properly, it will display the required content which is happening in your case. Also componentDidMount is called only once in the beginning so you can check for logs in componentDidUpdate method.
State updates are async in nature. If you want to print the state soon after setting your state in class component, then pass a function to the 2nd argument of setState.
Like this
componentDidMount() {
this.getDataFromHealthKit(ONEDAYINTERVAL);
// remove console.log
}
...
getDataFromKit(dateFrom) {
...
this.setState({ ActivityItem: [...this.state.ActivityItem, ...newData] }), () => {
console.log("values are", this.state.ActivityItem) //<----
}
})
...
}
use prevstate while updating the state value. React setState is an asynchronous update and does batch updating. Using prevState makes sure that the state value is updated before calculating new state value.
getDataFromKit(dateFrom) {
let stepCountSample = {
startDate: dateFrom.toISOString(),
type: "Type1"
};
Kit.getSamples(stepCountSample, (err, results) => {
//api call
if (err) {
return;
}
const newData = results.map(item => {
return { ...item, name: "itemAmount" };
});
this.setState(prevState => {
ActivityItem: [...prevState.ActivityItem, ...newData];
});
});
}
DOCUMENTATION would help understand the concept
Also, console.log would directly not give the updated state, since state updates are batched. You can use a callback method to setState function. The callback will run only after successfull updation of state value

Getting an error after using setState with a promise

Code:
onDragEnd = {
(event) => this.setState({ playerMarkerPositionFuture: event.nativeEvent.coordinate })
.then(() => alert("hello"))
}
When the following code gets executed I receive this error:
undefined is not an object evaluating
('_this.setState({playerMarkerPositionFuture: event.nativeEvent.coordinate}).then')
If I remove the promise everything works as expected so it means that most likely I used the promise in a wrong way:
onDragEnd={
(event) => this.setState({ playerMarkerPositionFuture: event.nativeEvent.coordinate })
}
setState accepts a callback, but it doesn't return a promise. See docs
Calls to setState are asynchronous - don’t rely on this.state to reflect the new value immediately after calling setState. Pass an updater function instead of an object if you need to compute values based on the current state (see below for details).
(event) => {
this.setState({playerMarkerPositionFuture: event.nativeEvent.coordinate }, () => alert("hello"));
}
setState doesn't return a Promise. It is most probably a void function.
Instead of using a promise to call alert after setState is finished, use a callback instead
onDragEnd={(event) =>
this.setState({
playerMarkerPositionFuture: event.nativeEvent.coordinate
}, () => {
alert("hello")
})}
onDragEnd={(event) => this.setState({ playerMarkerPositionFuture: event.nativeEvent.coordinate }).then(() => alert("hello"))}
This is a wrong practise of using setState() method. You can only use a callback using setState().
Right Practise:
onDragEnd={(event) => this.setState({ playerMarkerPositionFuture: event.nativeEvent.coordinate },()=>{
alert("hello")
})
Have a look at this article:
https://medium.com/#voonminghann/when-to-use-callback-function-of-setstate-in-react-37fff67e5a6c
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied.
https://reactjs.org/docs/react-component.html#setstate
so you could use setState(updater, callback) to execute code after the state was altered, to be on the save side
If you want use promise ...another solution could be
setStateAsync(state) {
return new Promise((resolve) => {
this.setState(state, resolve)
});
}
and use that ...
this.setStateAsync(
{playerMarkerPositionFuture: event.nativeEvent.coordinate}
)
.then(() => alert("hello"))
Try this
onDragEnd={(event) => this.setState({ playerMarkerPositionFuture: event.nativeEvent.coordinate },() => alert("hello"))}

React. Why this.state does not updates correctly?

So, I try to run the function by condition: if I got an Error in the catch method.
To do this, I implement the this.state.loginError in component state, that will change on true if we got an Error. So, and after error - the this.state.loginError comese back with true (and this is also I saw in console.log), but after state changes - my function loginError(target) does not want to start anyway.
See my code and logs below, please:
class LoginPage extends Component {
constructor(props) {
super(props);
this.state = {
navigate: false,
loginError: false,
}
}
handleSubmit = (e) => {
e.preventDefault();
axios.post('http://localhost:3016/auth/login', userLogin, {withCredentials: true})
.catch(err => {
this.setState({
loginError: true
});
console.log(this.state.loginError); // gives `true`
});
if (this.state.loginError) {
console.log('Error!') //does not work
loginError(target);
}
};
Because axios.post is asyc function, and at first fires your if condition, and after .catch hook. For fix this try to replace your condition in other place, for example in componentDidUpdate method.
componentDidUpdate() {
if (this.state.loginError) {
console.log('Error!') //does not work
loginError(target);
this.setState({ loginError: false });
}
}
Check this: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_functio
You are basically trying to check the state when the error is still not caught, and hence the state has not changed.
If you move your code to the render method you will see that it will works since it will re-render after the state changes. Or you get the state change withing the componentDidUpdate method.
Why don't you try Promises instead, which is very clear and simple way around.
class LoginPage extends Component {
constructor(props) {
super(props);
this.state = {
navigate: false,
loginError: false,
}
}
handleSubmit = (e) => {
e.preventDefault();
return new Promise(resolve, reject){
axios.post('http://localhost:3016/auth/login', userLogin, {withCredentials: true})
.catch(err => {
reject(err);
})
.then(result => resolve(result));
}
//Else where in Your component use this promise, where ever you call it..
handleSubmit().then(// success code).error(// error code)
};
Because axios.post returns a promise, all code you'll write after it will be executed before the .then() or .catch() statements. If your need is to call loginError() function when the request fails you can call it in .catch statement :
axios.post('http://localhost:3016/auth/login', userLogin, {withCredentials: true})
.catch(err => {
loginError(target);
});
If you need your function to be executed after updating the state you can use the setState callback (second argument) :
axios.post('http://localhost:3016/auth/login', userLogin, {withCredentials: true})
.catch(err => {
this.setState({ loginError: true }, () => { loginError(target); })
});

Categories