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");
}
}
Related
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")
}
I am using React Native and Redux. In initial state of Redux emailExists is set to null:
const INITIAL_STATE = {
// ...
emailExists: null,
};
when registering user. First, I check if user already exists by sending a request to server and if user already exists, I show a toast.
const registerUser = (values, actions) => {
checkEmail(values.userEmail); // takes time to get result of `emailExists`
if (emailExists) { // `emailExists` is now `null` couldn't wait for response
toastRef.current.show("Email already exists!");
return;
}
}
checkEmail code look like this:
function checkEmail(data) {
return (dispatch) => {
return api_request
.post("register/check/email", { email: data })
.then((res) => {
dispatch(emailExists(res.data.exists));
dispatch(authError("Email already exists"));
})
.catch((err) => {
console.log(`err`, err);
});
};
}
aftering dispatching dispatch(emailExists(res.data.exists));, the emailExists will be either true or false, but the problem is that since request takes time to get data from server, at first load of application emailExists is always set to null. Which means below condition will always be false in first load:
if (emailExists) {
toastRef.current.show("Email already exists!");
return;
}
function emailExists(payload){
return {
type: userConstants.EMAIL_EXISTS,
emailExists: payload
}
}
How do I resolve this issue?
THANK YOU
You can modify functions like this to get the expected result.
function checkEmail(data,callback) {
return (dispatch) => {
return api_request
.post("register/check/email", { email: data })
.then((res) => {
// dispatch(emailExists(res.data.exists)); // You don't need this redux approach now because it will take time and will give you the same error
dispatch(authError("Email already exists"));
callback && callback(res.data.exists) // I am assuming here you got the response that email alreay exists and its value is true.
})
.catch((err) => {
console.log(`err`, err);
});
};
}
Now get the callback when the API request is done and you get the response.
const registerUser = (values, actions) => {
const callback = (isEmailExists) => {
//Here you will get the value of the checkemail API (true/false)
//Now do any action in this block of functions
if (isEmailExists) {
toastRef.current.show("Email already exists!");
return;
}
}
checkEmail(values.userEmail,callback);
}
Mantu's answer is one solution. I would prefer using async/await for this use case. dispatch() returns whatever the function returned by the action creator returns. So in this case you are returning a promise, meaning you can await your dispatch(checkEmail(values.userEmail)) call.
You will need to return whether the email exists from the promise, otherwise even if you weait for your checkEmail action to complete, the emailExists will not be up to date when you access it (since using useSelector will have to rerender the component to reflect the updates in the store).
const registerUser = async (values, actions) => {
const emailExists = await checkEmail(values.userEmail);
if (emailExists) {
toastRef.current.show("Email already exists!");
return;
}
}
function checkEmail(data) {
return (dispatch) => {
return api_request
.post("register/check/email", { email: data })
.then((res) => {
dispatch(emailExists(res.data.exists));
dispatch(authError("Email already exists"));
return res.data.exists;
})
.catch((err) => {
console.log(`err`, err);
});
};
}
If you don't want to return the property from the promise, you will need to get the value from the store synchronously via store.getState():
const registerUser = async (values, actions) => {
await checkEmail(values.userEmail);
const emailExists = selectEmailExists(store.getState());
if (emailExists) {
toastRef.current.show("Email already exists!");
return;
}
}
I don't know how can I easy resolve the problem. I have a form that user can fill and send stuff to my database. Form has his own state for it (here for example is just username). I don't know how to call setState (to give message to user about result) after dispatching redux action.
Function postDataBegin is simple and shows spinner only. I think there is a problem, cause this function is synchronous (tell me if not). How can i fix it? What should i learn?
submitForm = e => {
e.preventDefault();
const { username } = this.state;
if (!question) {
this.setState({ messageToUser: "Fill the form" });
} else {
// onSubmit is redux action from props
this.props.onSubmit(username);
//HOW TO CALL THIS FUNCTION AFTER FUNC ABOVE?
this.setState({ messageToUser: "Send" });
}
};
<Form type="submit" onSubmit={this.submitForm}/>
export const postUserQuestion = username => dispatch => {
dispatch(postDataBegin());
return post(postUsernameAPI, username)
.then(res => dispatch(postUsernameAPISucceeded(res)))
.catch(error => dispatch(postUsernameAPIFailure(error)));
};
Your action returns a promise, so you can use then method on it to provide a callback:
submitForm = e => {
e.preventDefault();
const { username } = this.state;
if (!question) {
this.setState({ messageToUser: "Fill the form" });
} else {
// onSubmit is redux action from props
this.props.onSubmit(username).then(() => {
this.setState({ messageToUser: "Send" });
});
}
};
Altho it'd probably be better to store that data on reducer as well, and change it the same time when the form is submitted.
In my form, I'm trying to check email by using reactive-thunk to determine if the email address was already received. Everything is working properly, except for one thing. I request the api and I'm sending the data to the reducer, but the component I have access to the state is empty. Because the state value in the component is working before the reducer.
Is there any help of how to do that?
Submit.js
onSubmit = data => {
const { saveUser, validateEmail, emailValidate } = this.props;
validateEmail(data.email); // action create for api request
console.log(emailValidate); // fetch data in reducer(This data is empty because code run before reducer set state)
if (emailValidate.statusMessage === 'OK') {
throw new SubmissionError({ email: 'Email already in use', _error: 'Login failed!' });
} else {
}
}
const mapDispatchToProps = (dispatch) => {
validateEmail(email) {
dispatch(validateEmail(email));
},
};
};
const mapStateToProps = (state) => ({
emailValidate: state.registrationFormEmailValidate.data,
});
onSubmit = data => {
const { saveUser, validateEmail, emailValidate } = this.props;
validateEmail(data.email); // action create for api request
}
componentDidUpdate(prevProps) {
// Typical usage (don't forget to compare props):
if (this.props.emailValidate.statusMessage !== prevProps.emailValidate.statusMessage) {
console.log(this.props.emailValidate);
if (this.props.emailValidate.statusMessage === 'OK') {
throw new SubmissionError({ email: 'Email already in use', _error: 'Login failed!' });
} else {
}
}
}
If you are using class component, you can use componentDidUpdate()
My Problem
I am using React and Apollo in my frontend application. When I login as a regular user I want to be navigated to a certain path, but before that I need to set my providers state with my selectedCompany value. However, sometimes I get my value from my provider in my components CDM and sometimes I don't, so I have to refresh in order to get the value.
I've tried to solve this, but with no luck. So I am turning to the peeps at SO.
My setup (code)
In my login component I have my login mutation, which looks like this:
login = async (username, password) => {
const { client, qoplaStore, history } = this.props;
try {
const result = await client.mutate({
mutation: LOGIN,
variables: {
credentials: {
username,
password,
}}
});
const authenticated = result.data.login;
const { token, roles } = authenticated;
sessionStorage.setItem('jwtToken', token);
sessionStorage.setItem('lastContactWithBackend', moment().unix());
qoplaStore.setSelectedUser(authenticated);
qoplaStore.setUserSessionTTL(result.data.ttlTimeoutMs);
if (roles.includes(ROLE_SUPER_ADMIN)) {
history.push("/admin/companies");
} else {
// This is where I'm at
this.getAndSetSelectedCompany();
history.push("/admin/shops");
}
} catch (error) {
this.setState({ errorMessage: loginErrorMessage(error) });
}
};
So if my login is successful I check the users roles, and in this case, I will get into the else statement. The function getAndSetSelectedCompany look like this:
getAndSetSelectedCompany = () => {
const { client, selectedValues, qoplaStore } = this.props;
client.query({ query: GET_COMPANIES }).then(company => {
selectedValues.setSelectedCompany(company.data.getCompanies[0]);
});
};
So I am fetching my companies try to set one of them in my providers state with the function setSelectedCompany. selectedValues is what im passing down from my consumer to all my routes in my router file:
<QoplaConsumer>
{(selectedValues) => {
return (
...
)
}}
</QoplaConsumer
And in my provider I have my setSelectedCompany function which looks like this:
setSelectedCompany = company => {
this.persistToStorage(persistContants.SELECTED_COMPANY, company);
this.setState({
selectedCompany: company
})
};
And my selectedValues are coming from my providers state.
And in the component that has the route I'm sending the user to I have this in it's CDM:
async componentDidMount() {
const { client, selectedValues: { selectedCompany, authenticatedUser } } = this.props;
console.log('authenticatedUser', authenticatedUser)
if (selectedCompany.id === null) {
console.log('NULL')
}
Sometimes I get into the if statement, and sometimes I don't. But I rather always come into that if statement. And that is my current problem
All the help I can get is greatly appreciated and if more info is needed. Just let me know.
Thanks for reading.
Your getAndSetSelectedCompany is asynchronous and it also calls another method that does setState which is also async.
One way to do this would be to pass a callback to the getAndSetSelectedCompany that would be passed down and executed when the state is actually set.
changes to your login component
if (roles.includes(ROLE_SUPER_ADMIN)) {
history.push("/admin/companies");
} else {
// This is where I'm at
this.getAndSetSelectedCompany(()=>history.push("/admin/shops"));
}
changes to the two methods that are called
getAndSetSelectedCompany = (callback) => {
const { client, selectedValues, qoplaStore } = this.props;
client.query({ query: GET_COMPANIES }).then(company => {
selectedValues.setSelectedCompany(company.data.getCompanies[0], callback);
});
};
setSelectedCompany = (company, callback) => {
this.persistToStorage(persistContants.SELECTED_COMPANY, company);
this.setState({
selectedCompany: company
}, callback)
};