API call was success but an alert shows as unsuccessful in React - reactjs

I call an API call when a button is clicked. in the console, it shows as the API call was a success.
Then upon the successful call, I call a print handler to print the screen. But the first time the button clicks, it shows as unsuccessful & when I click again it shows as successful.
Following is my code.
const ConfirmBooking = async() =>{
console.log("child",seatsSelected)
const adultTicketCount = seatCount - counter
console.log("adult",adultTicketCount)
const firstName = "Ryan"
const lastName = "Fonseka"
const email = "ryan#343.com"
const mobileNumber = "333333"
const customerDetails = {
firstName:firstName,
lastName:lastName,
email:email,
mobileNumber:mobileNumber
}
let seatDetails = []
let seatCatId = 2761
seatDetails.push({
seatCategoryId: seatCatId,
seatNumbers:seatNumbers,
childCount: counter,
adultCount: adultTicketCount
})
console.log(seatDetails)
let mounted = true
await BookingRequest(seatDetails,customerDetails) // this is the API call
.then(data =>{
if(mounted){
setBooking(data)
}
})
console.log(booking)
const status = booking
console.log(status.success)
if((booking.success) === true){
await printHandleOpen(booking)
} else {
alert("Booking Failed")
}
}

It seems that the problem could be on the line in the API call where you call setBooking(data). This will schedule a state update, but the update will only occur after this function is popped off the call stack, so in the if((booking.success) === true) line, this will only evaluate as expected on the second time through.
edit: adding suggested .then code block
.then(data => {
if(mounted){
setBooking(data)
await printHandleOpen(booking
}
})
.catch(err => {
alert("Booking Failed")
})
and then you can remove that if...else block that fires those methods later in the code.

// Get this out of the function 'ConfirmBooking'
if((booking.success) === true){
await printHandleOpen(booking)
} else {
alert("Booking Failed")
}

Related

What is the best way about send multiple query in useMutation (React-Query)

I developed login function use by react-query in my react app
The logic is as follows
First restAPI for checking duplication Email
If response data is true, send Second restAPI for sign up.
In this case, I try this way
// to declare useMutation object
let loginQuery = useMutation(checkDuple,{
// after check duplication i'm going to sign up
onSuccess : (res) => {
if(res === false && warning.current !== null){
warning.current.innerText = "Sorry, This mail is duplicatied"
}else{
let res = await signUp()
}
}
})
//---------------------------------------------------------------------------------
const checkDuple = async() => {
let duple = await axios.post("http://localhost:8080/join/duple",{
id : id,
})
}
const signUp = async() => {
let res = await axios.post("http://localhost:8080/join/signUp",{
id : id,
pass : pass
})
console.log(res.data)
localStorage.setItem("token", res.data)
navigate("/todo")
}
I think, this isn't the best way, If you know of a better way than this, please advise.
Better to have another async function that does both things.
something like
const checkDupleAndSignUp = async () => {
await checkDuple();
await signUp();
};
And then use that in your useMutation instead.
Other things to consider:
Maybe move the logic to set local storage and navigate to another page in the onSuccess instead.
You can also throw your own error if one or the other request fails and then check which error happened using onError lifecycle of useMutation, and maybe display a message for the user depending on which request failed.
You can handle both of them in a single function and in mutation just add token in localStorage and navigate
like this:
const checkDupleAndSignUp = async () => {
return checkDuple().then(async res => {
if (res === false) {
return {
isSuccess: false,
message: 'Sorry, This mail is duplicatied',
};
}
const { data } = await signUp();
return {
isSuccess: true,
data,
};
});
};
and in mutation :
let loginQuery = useMutation(checkDupleAndSignUp, {
onSuccess: res => {
if (res.isSuccess) {
console.log(res.data);
localStorage.setItem('token', res.data);
navigate('/todo');
} else if (warning.current !== null) {
warning.current.innerText = res.message;
}
},
});

Getting a return value from axios function in reactJS

I am new to reactJS , my problem is that I want to assign my varaiable a value that has been returned from a axios function.When I do this, I get undefined value for u1.
function getUsername(userid){
var user = ''
axios.post('/api/getuserdata' , {_id:userid}).then(res=>{
console.log(res.data)
const userdata = res.data[0]
user = userdata.username
}).catch(err=>{
console.log(err)
})
return user
}
const u1 = getUsername(id)
The reason your code doesn't work is that the POST request handled by axios is async, your getUsername function won't wait for that request to resolve and return user immediately after it's called.
The simplest solution is that make your getUsername async and call it in another async function, or if you want to use the result returned from POST request for some other stuff, you can store it as state in React, it would be better.
You can use something like this:
const getUsername = async (userid) => {
try {
const response = await axios.post("/api/getuserdata", { _id: userid });
const userdata = response?.data?.[0]?.username || "";
return userdata;
} catch (error) {
console.log(`error`, error);
}
};
// in another function
const doSomethingWithUsername = async (id) => {
const username = await getUsername(id);
// other stuff..
}
async function getUsername(userid) {
try {
const result = await axios.post('/api/getuserdata' , {_id:userid});
const user = result.data;
}
catch (error) {
console.log(error)
}
}

Nested axios API call causes trouble on updating state React

I'm trying to build a simple app that fetches data from an API and shows them. I have a scenario in which I have to fetch the IDs of some items, and then for every ID make an API call to get the details. I want to set the array of fetched details as a state, and I can actually do it, but the view does not update and I don't understand why... I guess I'm doing a mess with asynchronous calls, as always...
updateSearchResults() is a state setter passed as a prop from the upper level component, and the holder of the state is that same upper level component.
async function handleSubmit(event) {
event.preventDefault();
if(name) {
let res = await axios.get(`https://www.foo.bar/search`);
if(res.data.items !== null) {
const filteredItems = filterItems(res.data.items);
updateSearchResults(filteredItems); //this works as expected
}
} else {
let res = await axios.get(`https://www.foo.bar/anothersearch`);
if(res.data.items !== null) {
let items= [];
res.data.items.forEach(async item => {
const resDetails = await axios.get(`https://www.foo.bar/getdetails`);
items.push(resDetails.data.items[0]);
})
console.log(items); //this prints the expected result
updateSearchResults(items); //this updates the state but not the view
}
}
}
...
const items= await Promise.all(res.data.items.map(async item => {
const resDetails = await axios.get(`https://www.foo.bar/getdetails`);
return resDetails.data.items[0];
}));
console.log(items); //this prints the expected result
updateSearchResults(items);
You can modify your code to something like this:
async function handleSubmit(event) {
event.preventDefault();
if(name) {
let res = await axios.get(`https://www.foo.bar/search`);
if(res.data.items !== null) {
const filteredItems = filterItems(res.data.items);
updateSearchResults(filteredItems); //this works as expected
}
} else {
let res = await axios.get(`https://www.foo.bar/anothersearch`);
if(res.data.items !== null) {
let items= [];
for await (const item of res.data.items) {
const resDetails = await axios.get(`https://www.foo.bar/getdetails`);
items.push(resDetails.data.items[0]);
}
console.log(items); //this prints the expected result
updateSearchResults(items); //this updates the state but not the view
}
}
}

Wait For Dispatch Action to be completed outside the component

I have a screen that user will choose a type of Quiz from, then quiz questions should be generated, currentGameInformation should be updated in the store and then new screen should be visible.
Since dispatch actions are async, sometimes the currentGameInformation is not updated, so my app crashes when it gets to next page. I want it to wait until it goes to next page so the information is available.
On Press of a button, a function is called in my component called startTheGame()
//inside the screen component
startTheGame = async (id) => {
let navigation = this.props.navigation;
await StartTheGame(MASTER_TIME_PERIOD, {time_period_id: id}).then(function(){
console.log("Navigating");
navigation.replace('Quiz');
});
};
//This function is located outside the component,
//It is a library that handles all the Quiz functionalities
export async function StartTheGame(type, details) {
let state = store.getState();
let username = state.currentUser.username;
if(username === undefined){
//AWS gets the current user working fine and waiting to be completed
let user = await GetCurrentUserAWS();
username = user.username;
}
//set game status to loading
let currentGameInfo = {};
let currentDayPoints = await GetCurrentDayPointsForUserDB(username);
//Redux Thunk function (is sent, but not waiting to get done)
SetCurrentDayPoints(currentDayPoints);
//redux thunk function (is set but not waiting for it to be done)
SetGameStatus(SET_GAME_START_LOADING, QUIZ_GAME_START_STATUS_LOADING);
//at this point, current day points are either updated/not and same with game status
let questions = await GenerateQuestions(type, details).catch(err => {
SetGameStatus(SET_GAME_START_ERROR, QUIZ_GAME_START_STATUS_ERROR); //same not waiting to be completed
});
currentGameInfo = {
questions: questions,
points: 0,
questionIndexesAnsweredCorrectly: [],
questionIndexesAnsweredIncorrectly: [],
shouldRestartBeEnabled: false,
currIndex:0,
questionsAnsweredInRow:0,
gameType:type
};
SetGameStatusSuccess(currentGameInfo); //same not waiting
return currentGameInfo; }
My goal is to return only after SetGameStatusSuccess has been completed
export function SetGameStatusSuccess(currentGameInfo){
return (dispatch, getState) => {
dispatch({type: SET_GAME_START_SUCCESS, payload:{
gameStatus:QUIZ_GAME_START_STATUS_STARTED,
currentGameInformation:currentGameInfo
}});
}; }
export function SetGameStatus(gameStatus, quizStatus){
return (dispatch, getState) => {
dispatch({type: gameStatus, payload:{gameStatus:quizStatus}});
};}
I am wondering if there is a way to do this without the need of mapDispatchToProps function ?
You need to await your SetGameStatus function call. Since your StartTheGame function is marked as async, all you need to do is:
let currentDayPoints = await GetCurrentDayPointsForUserDB(username);
SetCurrentDayPoints(currentDayPoints);
//add 'await' here
await SetGameStatus(SET_GAME_START_LOADING, QUIZ_GAME_START_STATUS_LOADING);
and same for here:
let questions = await GenerateQuestions(type, details).catch(asybc (err) => {
await SetGameStatus(SET_GAME_START_ERROR, QUIZ_GAME_START_STATUS_ERROR);
});

How to get component to wait for async action

I have this form code below:
submitForm = (e) => {
e.preventDefault();
const { user } = this.state;
const { dispatch } = this.props;
if (user.firstName && user.lastName && user.userName && user.password) {
this.props.dispatch(userActions.register(user));
} else {
this.setState({
error: "Form Incomplete"
})
}
if(this.state.error === "") {
this.props.history.push("/login");
}
}
The problem is this.props.dispatch is an async call. It gets fired when a user successfully fills out the form field.
The problem is it can fail if the username already exists and it will populate the error state. If this occurs my app keeps going and hits this.props.history and redirects the user even if the form has an error.
How do I basically say "Wait until this.props.dispatch is complete and then check to see if there are any errors. If not then redirect"?
You can specify submitForm as an async function like this:
submitForm = async (e) => {
and then add the await keyword before this.props.dispatch
await this.props.dispatch(userActions.register(user));
But since you are using redux, and I am assuming something like redux-promise-middleware, then you should let that handle the success/failure of your async calls.
You mentioned that onInit the form continuously redirects because there is no error set. Can we change the initial error value to false and if error is ever true then re-direct the user? And then, the only time you would ever set error to true would be when an actual error came from your call.
I imagine that you are sending this data to a backend of some sort. Just add a bool to the server response to let your front end know what to do next.
submitForm = (e) => {
e.preventDefault();
const { user } = this.state;
const { dispatch } = this.props;
if (!( user.password && user.lastName &&user.userName && user.firstName )) {
this.props.dispatch(userActions.register(user));
} else {
this.setState({
error: "Form Incomplete"
})
}
if(isValid) {
this.props.history.push("/login");
}
}

Resources