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);
});
Related
I have 3 generator function first is "loginUserStart" where the actual request comes then the second one is "LoginUserAsync" which is called in the "loginUserStart" and third is api call function
so I am trying to pass the parameter from my signin component to the loginUserStart function but whenever I console.log(arguments) it is showing nothing
Code:-
Sign-in component
const login = async () => {
arr.userEmail = "sample_email";
arr.userPassword = "sample_password";
console.log(arr);
signinUserStart(arr);
};
const logSubmit = () => {
login();
};
const mapDispatchToProps = (dispatch) => ({
signinUserStart: (data) => dispatch(signinUserStart(data))
});
Action file code
export const signinUserStart = (data) => ({
type: UserActionTypes.Set_SigninUser_Start,
payload: data
})
saga File code
API generator function code
export async function fetchUser(info) {
console.log(info);
const email = 'Admin#gmail.com'; //sample_email
// const passwords = info.userPassword;
const password = 'Admin#123'; //sample_password
try {
const user = await axios.post("http://localhost:5050/sign", {
data: {
email: email,
password: password,
},
});
console.log(user);
return user;
} catch (error) {
console.log(error);
return error;
}
}
LoginUserAsync function
export function* LoginUserAsync(data) {
console.log("in saga");
console.log(data);
try {
let userInfo = yield call(fetchUser, data)
console.log(userInfo);
yield put(setUserId('62b1c5ee515317d42239066a')); //sample_token
yield put(setCurrentUserName(userInfo.data.userName));
} catch (err) {
console.log(err);
}
}
loginUserStart function
export function* loginUserStart(action) {
console.log(action.payload);//not logging anything for showing in console
yield takeLatest(UserActionTypes.Set_SigninUser_Start, LoginUserAsync(action));
}
I can't be sure without seeing more code, but assuming that loginUserStart is either root saga or started from root saga it means there is no action for it to receive.
The main issue I think is this line
yield takeLatest(UserActionTypes.Set_SigninUser_Start, LoginUserAsync(action));
In the second parameter you are calling the generator function which is wrong, instead you should be passing the saga itself (as reference).
So it should look like this:
yield takeLatest(UserActionTypes.Set_SigninUser_Start, LoginUserAsync);
This way, the Redux Saga library will then call LoginUserAsync when Set_SigninUser_Start is dispatched with first param correctly set to the action object.
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")
}
When I am deleting the category with an API call, Redux doesn't wait for all actions to complete. Sample action below:
export const deleteCategory=(id)=>{
return dispatch=>{
const message='Category Deleted Successfully!'
dispatch(deleteCategoryStart())
axios.delete(`http://127.0.0.1:8000/admindashboard/categories/${id}/`)
.then(res=>{
dispatch(deleteCategorySuccess(message))
}).catch(err=>{
dispatch(deleteCategoryFailed(err))
})
}
}
My code to handle the delete:
handleDelete= (event) =>{
event.preventDefault();
this.props.deleteCategory(this.props.match.params.categoryID);
this.props.history.push('/categories')
}
I am trying to delete the category and redirect the user to the "all categories" page. Instead, it's redirecting the user before all action dispatches are completed. The new categories page/component calls additional actions, getting all categories.
Action sequence:
JavaScript explicitly won't wait for your external process (API call) to complete. You can use async/await or change your code similar to below
export const deleteCategory = (id) => {
return dispatch => {
dispatch(deleteCategoryStart());
return axios.delete(`http://127.0.0.1:8000/admindashboard/categories/${id}/`);
}
}
handleDelete = (event) => {
event.preventDefault();
this.props.deleteCategory(this.props.match.params.categoryID).then(res => {
const message='Category Deleted Successfully!';
this.props.dispatch(deleteCategorySuccess(message));
this.props.history.push('/categories'); // Push to the page here
}).catch(err => {
this.props.dispatch(deleteCategoryFailed(err));
});
}
[React JS]How to wait 'return' until 'dispatch' finish in action creator?
I don't know how handle this ;(
i made some code. this is a part of action creator. But it return before dispatch finish. i want to dispatch finish before 'return'. help me please
export const Hello = param=> dispatch => {
return postApi('/hello', param)
.then(async res => {
await dispatch(goHello(res));
return true;
})
By default, there really is no need to call return after dispatching an action. But if you'd like, you could use the getState() method to check that your action was processed before returning. getState() is the second argument of the redux-thunk function you are returning in your action-creator.
export const Hello = (param) => async (dispatch, getState) => {
postApi("/hello", param)
.then((res) => {
await dispatch(goHello(res))
})
.catch((err) => {
console.log(err.response.data)
})
//check if reducer was updated
const value = getState().keyOfReducerInStore <-- returns the state of your reducer, you can follow this up with whatever value you want to check
if(value){
return true
}
}
You can use await on postApi. You can only use await directly inside of an async function. Link.
export const Hello = (param) => async (dispatch, getState) => {
const res = await postApi("/hello", param); // <- code pauses here
const res2 = await anotherPostApi("/helloagain", param); // <- will wait for above
dispatch(goHello(res));
// You can also wrap the await postApi in a try catch instead of using .catch
const value = getState().keyOfReducerInStore <-- returns the state of your reducer, you can follow this up with whatever value you want to check
if(value){
return true
}
}
Just remember. Promises can be awaited as long as they are directly inside of an async function. If you console.log a variable it'll show you if it is a Promise.
I have a thunk using Axios that's posting to an Express route using Sequelize.
The route is posting correctly (ie. data is getting added to the db) but the action inside of the React component isn't behaving as expected. Using async/await, I expect the action to wait until it completes the db post before continuing but that's not the case here. I'm getting undefined from the action.
The thunk hits the express route where I'm dispatching the action to update my redux store and returning the response:
const addedNewList = (newList) => ({type: ADD_NEW_LIST, newList})
export const addNewList = (name, userId) => async dispatch => {
try {
const { data } = await axios.post('/api/list/add', { name, userId })
dispatch(addedNewList(data))
return data
} catch (err) {
console.error(err)
}
}
Using debugger, I can confirm that return data is in fact returning the response from the server that I need. I can also confirm that the redux store is getting updated correctly.
But here, when I try and access that response data as result, I get undefined:
handleSubmit = async () => {
const result = await this.props.addNewList(this.state.name, this.props.userId)
// ** result is 'undefined' **
this.handleClose()
// pass off the results
}
If I add a setTimeout after I evoke the addNewList action, it works as expected. This suggests to me that maybe it's not returning a promise? But my understanding was that if you returned the response from the server in the thunk, it would do that.
For completeness, here is my route which I've also confirmed with debugger that data is being passed as expected:
const userAuth = function(req, res, next) {
if (req.isAuthenticated()) {
return next()
}
res.status(401).send('Unauthorized user')
}
router.post('/add', userAuth, async (req, res, next) => {
const { name, userId } = req.body
try {
const list = await List.create({ name, userId })
res.json(list)
} catch(err) { next(err) }
})
Why is the action returning undefined in the handleSubmit method?
Try returning the dispatch of addedNewList(data) instead:
export const addNewList = (name, userId) => async dispatch => {
try {
const { data } = await axios.post('/api/list/add', { name, userId })
return Promise.resolve(dispatch(addedNewList(data)));
} catch (err) {
console.error(err)
}
}
That being said, you could consider restructuring the component to instead utilize mapStateToProps to use values/result from the updated Redux store rather than explicitly awaiting the response and manually passing the value?
The response from Alexander got me on the right track so I'm sharing my solution in case it helps someone (as he suggested).
While I could have continued to try and solve this by wrapping the dispatch in a Promise, the better solution was to rethink how the component was structured.
In my situation, I wanted to get the ID for the newly created row in the database so that I could pass it into history.push.
handleSubmit = async () => {
const result = await this.props.addNewList(this.state.name, this.props.userId)
this.handleClose()
history.push(`/list/${result.id}`)
}
With result coming back undefined, the url was not updating correctly.
The better solution was to access the new data from the redux store where it was updated. This way I could be certain the history wouldn't get updated until the data was ready.
So my updated component now looked something like this where the history wouldn't update until a newId was available:
handleSubmit = () => {
this.props.addNewList(this.state.name, this.props.userId)
this.handleClose()
}
render(){
const { newId } = this.props
if (newId) {
history.push(`/list/${newId}`)
}
return (
....
)
}
}
const mapStateToProps = (state) => {
return {
newId: state.list.newId
}
}
Instead of putting this into render, I could probably also use a component lifecylcle method like componentWillReceiveProps or similar.