I have written the following code, it runs smoothly but I have encountered a question:
submitFormToBackend = async () => {
if (this.paymentMethod === 'apple-pay') {
this.setState({ showLoadingIndicator: true }); // <-- below await setTimeout can confirm this line run before it
}
let requester = new ApplePayRequester({...this.form});
let applePay = new ApplePay();
await setTimeout(async () => {
let cardTokenResponse = await applePay.getCardToken();
if (cardTokenResponse.isSuccess()) {
requester.setCardToken(cardTokenResponse.message);
let response = await requester.pushToBackend();
this.setState({ showLoadingIndicator: false }); //<-- below setTimeout can confirm this line run before them
if (response.isSuccess()) {
setTimeout(() => { this.navigator.backToPreviousScreen(); }, 800);
} else {
setTimeout(() => { Alert.alert('your purchase has error. Try again'); }, 800);
}
} else {
this.setState({ showLoadingIndicator: false });
setTimeout(() => { Alert.alert('cannot get your card token.'); }, 800);
}
}, 800);
};
My render() in that component:
render() {
return (
<View style={styles.form}>
<LoadingIndicator visible={this.state.showLoadingShader} />
<InputBox />
<InputBox />
<SubmitButton />
</View>
);
}
As you see there are a lot of setTimeout() functions, it seems like functions will crash together if I don't use setTimeout() to restrict the functions run one by one.
However, it's not a good practice as there is no default millisecond for success running (the millisecond can set to 700ms or 1500ms or etc.). Therefore I would like to ask is there any solution to confirm previous function has run before next function start, other than using setTimeout()?
UPDATE
Procedures:
Step 1 - Press submit button
Step 2 - Pop up a confirmation modal
Step 3 - User confirm, dismiss confirmation modal, set showLoadingIndicator to true to show loading indicator
Step 4 - Call ApplePay and pop up ApplePay UI
Step 5 - User confirm, set showLoadingIndicator to false to dismiss loading indicator and navigate previous screen
Problems encountered when not using setTimeout():
Step 4 - ApplePay UI cannot pop up after setting showLoadingIndicator to true, below is the code that encountered problem:
let cardTokenResponse = await applePay.getCardToken();
Step 5 - Alert will be pop up before setting showLoadingIndicator to false, which stops the setting, below is the code that encountered problem:
this.setState({ showLoadingIndicator: false });
if (response.isSuccess()) {
} else {
setTimeout(() => { Alert.alert('your purchase has error. Try again'); }, 800);
}
A second optional parameter of setState function is a callback function that runs synchronously with the state change.
So you can just rely on the following:
this.setState({
//change state variables here
}, () => {
//do the next work here...
});
The callback function always run post the state is changed.
In your code, this would work:
this.setState({ showLoadingIndicator: false }, () => {
if (response.isSuccess()) {
this.navigator.backToPreviousScreen();
} else {
Alert.alert('your purchase has error. Try again');
}
});
Related
I am trying to run some firebase code when the tab is closed or refreshed for my react app, and it is working fine so far for firebase, however when I close the tab the code is not executed. I assume this is because firebase is asynchronous, and thus the tab closes before the firebase code is done executing. Is there a way for me to get around this and assure my firebase code finishes execution before the tab closes?
leaveLobby(e) {
e.preventDefault();
var firestore = firebase.firestore();
var docRef = firestore.doc("Games/Game " + this.state.Lobbycode);
docRef.get()
.then((docSnapshot) => {
if (docSnapshot.data().PlayerAmnt === 1) {
firestore.doc("Games/Active Games").update({
"Active Games" : firebase.firestore.FieldValue.arrayRemove(this.state.Lobbycode)
})
firestore.doc("Games/Game " + this.state.Lobbycode).delete();
} else {
docRef.update({
players : firebase.firestore.FieldValue.arrayRemove(this.state.name),
PlayerAmnt : firebase.firestore.FieldValue.increment(-1)
})
}
this.props.setInLobby(false, "", this.state.name);
})
return
}
componentDidMount() {
window.onbeforeunload = this.leaveLobby;
}
You can hook into the close event
window.addEventListener("beforeunload", (ev) =>
{
ev.preventDefault();
return ev.returnValue = 'Are you sure you want to close?';
});
componentDidMount: function() {
window.addEventListener('onbeforeunload', this.handleWindowClose);
},
componentWillUnmount: function() {
window.removeEventListener('onbeforeunload', this.handleWindowClose);
}
Just make sure you also store the handler in a local field during initialization in the constructor
this.handler = (ev) =>
Then use the following as needed
addEventListener("beforeunload", this.handler)
removeEventListener("beforeunload", this.handler)
I have the following code:
$scope.deleteJob = function(job) {
SandboxService.deleteJob(job.id).then(res => {
if (res.status == 200) {
ngToast.success();
$scope.refreshApps();
}
else {
ngToast.danger();
}
});
};
And the following unit test:
it('should show success toast on delete and refresh apps', () => {
spyOn(sandboxService, 'deleteJob').and.returnValue(Promise.resolve({status: 500}));
spyOn(ngToast, 'success');
spyOn(scope, 'refreshApps');
let mockJob = {
'id': 1
};
scope.deleteJob(mockJob);
sandboxService.deleteJob().then(() => {
expect(ngToast.success).toHaveBeenCalled();
expect(scope.refreshApps).toHaveBeenCalled();
});
});
Basically when deleting a job, if the delete was a success with a returned status of 200 then show a success toast and refresh, else show a danger toast.
I expect the test to fail, as it returns a status of 500, but it passes. This implies that ngToast.success() and scope.refreshApps() have been called.
I added some logs to the code, and it does return status: 500 and go to the else block.
What am I missing here?
The problem is related to the asynchronous nature of deleteJob. Your it test ends even before expect is performed. Therefore you need some sort of synchronization. This could basically be done with fakeAsync and tick from #angular/core/testing.
it('should show success toast on delete and refresh apps', fakeAsync(() => {
...
sandboxService.deleteJob();
tick();
expect(ngToast.success).toHaveBeenCalled();
expect(scope.refreshApps).toHaveBeenCalled();
}));
The problem however is that you're overwriting the original behaviour of deleteJob with the spy below, hence ngToast.success and scope.refreshApps won't be called and the test will fail.
spyOn(sandboxService, 'deleteJob').and.returnValue(Promise.resolve({status: 500}));
#uminder's answer pointed out that the test was finishing before the expect functions were even called due the asynchronous nature - verified by adding some logs inside the test.
The solution was to add an argument to the test that is to be called when the test has finished: https://jasmine.github.io/2.0/introduction.html#section-Asynchronous_Support
it('should show success toast on delete and refresh apps', (done) => {
spyOn(sandboxService, 'deleteJob').and.returnValue(Promise.resolve({status: 200}));
spyOn(ngToast, 'success');
spyOn(scope, 'refreshApps');
let mockJob = {
'id': 1
};
scope.deleteJob(mockJob);
sandboxService.deleteJob().then(() => {
expect(ngToast.success).toHaveBeenCalled();
expect(scope.refreshApps).toHaveBeenCalled();
done();
});
});
The default flow of a switch will toggle the value as soon as it is pressed.
I want to execute the following logic
Press switch
Don't change the value yet
Open alert box
If ok, send post request to toggle database value
On successful response, allow the switch value to change
I can get the successful response and update the switch, but as soon as I press the switch the value toggles twice and I want to pause this behaviour until there is a successful response. Tried async await, tried wrapping the switch in a touchablewithout feedback... now need some help
<Switch
value={this._getSwitchValue(id)}
onValueChange={() => this._toggleSwitch(id)}
/>
/**
* Toggle the Switch - Send post request to database
*/
_toggleSwitch (id) {
Alert.alert('Notice!', 'Do you really want to change this setting?',
[
{ text: "OK", onPress: () => this.props.toggleSettingsSwitch(id, this.props.token) },
{ text: "Cancel", onPress: () => console.log("cancel pressed")}
],
{ cancelable: true }
)}
Change the Switch value inside the function that makes the request.
toggleSettingsSwitch = async (value) => {
const response = await this.changeSettingsRequest(); // making a call
// here add a check, if request was successful change switch value
// if (response.ok) {
// this.setState({ switchValue: value });
//}
};
_toggleSwitch = (value) => {
Alert.alert(
'Notice!',
'Do you really want to change this setting?',
[
{
text: 'OK',
onPress: () => {
this.toggleSettingsSwitch(value);
},
},
{ text: 'Cancel', onPress: () => console.log('cancel pressed') },
],
{ cancelable: true }
);
};
<Switch
onValueChange={this._toggleSwitch}
value={this.state.switchValue}/>
snack example
I know this is an old question, but for people still looking for an answer to this, try: react-native-switch-pro
Another alternative is to create your own async-supporting switch-like component, here's an example on snack: Custom Async Switch
You can just have another variable loading and ignore calls, if it is true
_toggleSwitch = async (value) => {
if (this.loading) {
return;
}
this.loading = true;
try {
...
} finally {
this.loading = false;
}
};
You can even put it in React state and make it part of UI interaction (hiding Switch if it's in loading state and presenting some kind of animation)
I would just optimistically update UI state and inform user in case of an error, that way UI would feel more responsive.
I am having a strange (to me) issue with my ReactJS code.
I just started learning ReactJS so I might be doing something very stupid, but this is what's happening.
Link to codepen: https://codepen.io/Scyleung/pen/XyVovg
I am have this component like so
class LeaseData extends React.Component {
constructor(props) {
super(props);
this.state = {
id: null,
ready: false,
data: null,
error: null,
};
this.update = this.update.bind(this);
//this.getNewJson(1);
}
update(newID) {
this.setState({
id: newID,
ready: false,
data: null,
error:null,
});
console.log("From Main: " + newID + " " + this.state.id);
this.getNewJson(newID);
}
getNewJson(id) {
let url = "https://hiring-task-api.herokuapp.com/v1/leases/"+id;
fetch(url)
.then(res => res.json())
.then(
(result) => {
console.log(result);
this.setState({
ready: true,
data: result
});
},
(error) => {
console.log("Error: " + error.message);
this.setState({
ready: true,
error: error
});
}
)
}
render() {
if (this.state.error) {
console.log("ERROR")
return (
<div>
<Form callback = {this.update}/>
Error: {this.state.error.message}
</div>);
} else if (!this.state.ready) {
console.log("Waiting")
return (<Form callback = {this.update}/>);
} else {
console.log("Displaying")
return (
<div>
<Form callback = {this.update}/>
{this.state.data.rent}</div>
);
}
}
}
I have a Form that calls update() when the user submit the form.
First issue, inside update() the console.log() always say this.state.id is null, even after submitting the form multiple times.
Second issue, update() should call getNewJson() that should set this.state.error, if an error happens in fetch. As much in render() it should console.log("ERROR"), which it does, so that's good. However immediately afterward it would go to console.log("Waiting") again. So maybe related to the first issue, since the state got reset.
Third issue, why am I getting an error on the fetch like 80% of the time?
The strangest thing is, if I uncomment the this.getNewJson(1) in the constructor, all the problems disappears!!
The main issue I assume is that onSubmit does what you want but then reloads the page cancelling the request. What you need to do to prevent this is to call e.preventDefault() after calling the callback function.
Changing your onSubmit to something like this should fix the main issue
onSubmit = {(e) => {
this.props.callback(this.state.value);
e.preventDefault();
}}
https://codepen.io/anon/pen/QJaeyY
Secondly, to get the error from fetch, you should use .catch() instead of the second callback function in then() as this is the proposed syntax and should work with most browsers.
I have two ajax post request which need to be executed as synchronous calls.
Scenario is that the form is creating two differnt type of objects, these objects need to be sent to the different API as post request and I have to show animation on the UI until both API returns result to UI.
Any Help!
Edit to add Code
doApproveSingle() {
//animation part
this.loading = true
//call 1
this._http.post('api/single', this.approvedChanges, contentHeaders).subscribe(resp => {
}, error => {
})
//call 2
this._http.post('api/new', this.approveNew, contentHeaders).subscribe(resp => {
}, error => {
})
}
there are two post requests I need to close that animation after both calls are completed, need help in that part.
doApproveSingle() {
//animation part
this.loading = true
//call 1
let obs1 = this._http.post('api/single', this.approvedChanges, contentHeaders)
.map(resp => { ... });
//call 2
let obs2 = this._http.post('api/new', this.approveNew, contentHeaders)
.map(resp => { ... });
Observable.zip([obs1, obs2]).subscribe(val => this.loading = false);
}
If there is nothing to do when the individual HTTP calls complete, the .map(...) parts can be omitted.