In order to create a transaction the user must first be authenticated(password) in the confirm dialog modal. Once the user is authenticated the modal is closed and the transaction is shown. If a user is not authenticated correctly the modal still closes and a toast message is displayed with the error. I would like to change this logic so that the user must re-enter the password if their attempt was not authenticated.
setting the state inside createTransaction is done in an async way. this.closeModal in newTransactionModal is whats causing the modal to be closed and the state to be reset.
NewTransactionModal.js
this.state.showConfirmDialog ? (
<ConfirmDialog
type="New Transaction"
onPasswordChange={this.changePassword}
onConfirm={this.handleCreateClick}
onCancel={this.handleClose}
errMsg = {this.props.passwordErrMsg}
/>
) : null
NewTransactionModal.js
handleCreateClick = () => {
if (this.formIsValid()) {
let path = '/transaction',
transaction = {
type: this.state.transactionType.id,
amount: this.state.transactionAmount,
internalComment: this.state.comment,
userPassword: this.state.password
},
extraDataForError = {
typeName: this.state.transactionType.name,
advertiserName: this.state.advertiser.name,
fundingType: this.state.advertiser.fundingType,
financialDocumentId: this.state.documentId,
financialDocumentType: this.state.document && this.state.document.documentMetadata.documentType
};
if (this.state.transactionType.creditType) {
path += '/transfer';
transaction.debitAdvertiserId = this.state.advertiserId;
transaction.creditAdvertiserId = this.state.transferAdvertiserId;
transaction.debitFinancialDocumentId = this.state.documentId;
transaction.creditFinancialDocumentId = this.state.documentId;
} else {
transaction.advertiserId = this.state.advertiserId;
transaction.financialDocumentId = this.state.documentId;
}
this.props.createTransaction(path, transaction, extraDataForError);
this.closeModal();
}
};
ListTransaction.js
createTransaction = (path, data, extra) => {
const failureMsg = 'Failed to create transaction';
if (!path || !data) {
this.setState({
toastMessage: failureMsg,
toastType: 'error'
});
return;
}
const getErrTransactions = res => {
return [{ transaction: {...data, ...extra}, validations: res.validations, result: res.result }];
};
this.setState({ toastType: 'pending' }, async () => {
try {
const res = await ApiService.post(path, data, this.abortController.signal);
if (res && res.result !== 'FAILURE') {
this.setState({
toastMessage: 'Transaction created',
toastType: 'success',
selected: []
}, () => this.loadData(1));
} else if (res) {
this.handleError(getErrTransactions(res), failureMsg);
} else {
this.setState({toastType: null})
}
} catch (err) {
if (err) {
if (err.name === 'AbortError') {
return false;
} else if (err.json) {
this.setState({
passwordErrMsg: "Please enter a valid password"
})
const jsonErr = await err.json();
this.handleError(jsonErr.result ? getErrTransactions(jsonErr) : jsonErr, failureMsg);
} else {
this.handleError(err, failureMsg);
}
} else {
this.setState({ toastType: null });
}
}
});
};
ListTransactions.js
canCreate ? (
<NewTransactionModal
show={showNewTransactionModal}
types={allowedTransactionTypes}
createTransaction={this.createTransaction}
passwordErrMsg = {this.state.passwordErrMsg}
handlePasswordAttempt = {this.handlePasswordAttempt}
handleRecent={this.handleRecent}
handleClose={this.hideModal}
/>
) : null
I expect that that the user must still enter their password after an invalid attempt and that the modal does not go away. Also after the user has entered the correct password than the modal is closed.
Related
I am using firebase authentication with phone number, and that requires Recaptcha verification. I had initially used the normal visible recaptcha, and that worked fine, but a need came in to make the recaptcha an invisible one.
I used Firebase's documentations on this and here's my code below:
const generateRecaptcha = () => {
window.recaptchaVerifier = new RecaptchaVerifier(
"signin_btn",
{
size: "invisible",
callback: (response) => {
onSignInSubmit();
},
},
auth
);
};
const onSignInSubmit = async () => {
setLoading(true);
const q = query(doc(db, "users", phone));
const querySnapshot = await getDoc(q);
console.log("Snapshot", querySnapshot.exists());
if (
phone === "" ||
password === "" ||
repeatPassword === "" ||
lastname === "" ||
firstname === ""
) {
setLoading(false);
setErrors("Please fill the needed fields.");
} else if (password !== repeatPassword) {
setErrors("Passwords do not match.");
setLoading(false);
} else if (querySnapshot.exists()) {
setErrors(
"This phone number already exists on the server. Please try another number."
);
setLoading(false);
} else {
setErrors("Complete ReCaptcha to get verification code");
generateRecaptcha();
let appVerifier = window.recaptchaVerifier;
console.log("Did we get here?");
signInWithPhoneNumber(auth, phone, appVerifier)
.then((confirmationResult) => {
console.log("We got here..!!");
window.confirmationResult = confirmationResult;
setLoading(false);
})
.catch((err) => {
console.error(err);
});
}
};
I end up getting this error though
Please I need help on this issue.
So I have a Form in React and, I have a username field and email field. After the user stops typing for 800ms I am checking whether the username/email (based on which field the user is typing) is available (not take by someone else).
I end up having two very similar useEffect functions doing the same thing for username and email, just differ in the URL at which they are sending a request and the variable they are watching.
I am here sharing the snippet of the code. I am using useReducer here.
Initial State :
const initialState = {
username: {
value: '',
hasErrors: false,
errorMessage: '',
isUnique: false,
checkCount: 0,
},
email: {
value: '',
hasErrors: false,
errorMessage: '',
isUnique: false,
checkCount: 0,
},
submitCount: 0,
}
Reducer Function:
//using immer
function myReducer(draft, action) {
switch (action.type) {
case 'usernameAfterDelay':
draft.username.checkCount++;
return;
case 'usernameUniqueResults':
if (action.value) {
draft.username.hasErrors = true;
draft.username.isUnique = false;
draft.username.message = 'That username is already taken.';
} else {
draft.username.isUnique = true;
}
return;
case 'emailAfterDelay':
draft.email.checkCount++;
return;
case 'emailUniqueResults':
if (action.value) {
draft.email.hasErrors = true;
draft.email.isUnique = false;
draft.email.message = 'That email is already being used.';
} else {
draft.email.isUnique = true;
}
return;
default:
return;
}
}
const [state, dispatch] = useImmerReducer(ourReducer, initialState);
My useEffect functions that are very similar
useEffect for applying to debounce to user typing
useEffect(() => {
if (state.username.value) {
const delay = setTimeout(() => {
dispatch({ type: 'usernameAfterDelay' });
}, 800);
return () => clearTimeout(delay);
}
}, [state.username.value]);
useEffect(() => {
if (state.email.value) {
const delay = setTimeout(() => {
dispatch({ type: 'emailAfterDelay' });
}, 800);
return () => clearTimeout(delay);
}
}, [state.email.value]);
useEffect for actually making the api call
useEffect(() => {
if (state.username.checkCount) {
const ourRequest = Axios.CancelToken.source();
async function fetchResults() {
try {
const response = await Axios.post(
'/doesUsernameExist',
{ username: state.username.value },
{ cancelToken: ourRequest.token }
);
dispatch({ type: 'usernameUniqueResults', value: response.data });
} catch (e) {
console.log('There was a problem or the request was cancelled.');
}
}
fetchResults();
return () => ourRequest.cancel();
}
}, [state.username.checkCount]);
useEffect(() => {
if (state.email.checkCount) {
const ourRequest = Axios.CancelToken.source();
async function fetchResults() {
try {
const response = await Axios.post(
'/doesEmailExist',
{ email: state.email.value },
{ cancelToken: ourRequest.token }
);
dispatch({ type: 'emailUniqueResults', value: response.data });
} catch (e) {
console.log('There was a problem or the request was cancelled.');
}
}
fetchResults();
return () => ourRequest.cancel();
}
}, [state.email.checkCount]);
JSX is as follows
<div>
<label htmlFor="username-register" className="text-muted mb-1">
<small>Username</small>
</label>
<input
id="username-register"
onChange={(e) =>
dispatch({
type: 'usernameImmediately',
value: e.target.value,
})
}
value={state.username.value}
/>
<CSSTransition
in={state.username.hasErrors}
timeout={330}
classNames="liveValidateMessage"
unmountOnExit
>
<div className="alert alert-danger small liveValidateMessage">
{state.username.message}
</div>
</CSSTransition>
<div>
<div>
<label htmlFor="email-register" className="text-muted mb-1">
<small>Email</small>
</label>
<input
id="email-register"
onChange={(e) =>
dispatch({
type: 'emailImmediately',
value: e.target.value,
})
}
/>
<CSSTransition
in={state.email.hasErrors}
timeout={330}
classNames="liveValidateMessage"
unmountOnExit
>
<div className="alert alert-danger small liveValidateMessage">
{state.email.message}
</div>
</CSSTransition>
</div>
As you can see there is a lot of repetitive code over here, I want to know is there any better way to handle things.
How about making a custom hook that will store the state and also check if the email is valid? Also you could probably combine your API call into a single function and then combine the useEffect into a single one.
Why are you using all these in useEffect when it's better used in your onchanged events?
let timer = []; //this is a global variable, need to be outside your component so the value does not get reset on every render.
const MyComponent = () => {
const [ isUsernameOk, setIsUsernameOk ] = useState(false)
......
const getResponse = (url, body) => await Axios.post(
url, body,
{ cancelToken: ourRequest.token }
); //u can put everything in one line.
const onChange = () => {
const { name, value } = e.target
dispatch({ type: 'usernameImmediately', value: e.target.value })
clearTimeout(timer[name]);
timer[name] = setTimeout(dispatch({ type: 'usernameAfterDelay' }),899)
// OR
timer[name] == setTimeout(async () => {
const response = await getResponse('/emailCheck', { email: test#email.com })
//do whatever u want with response
//do your dispatch or,
setIsUsernameOk(true)
})
}
return (
...............
{ isUserNameOk && <span> Username is available </span>
)
}
You do not need to use useEffect for whatever you are doing. Unnecessarily using useEffect is ineffective and may unnecessarily causing looping errors ( and also unnecessary re-rendering )
AND
This is a single check? Do you really need to go through all the redux codes to complicate your components? Is local state insufficient?
handleSubmit function called in onClick of a button
handleSubmit = async (name) => {
let registerResponse = await registerAPI(this.state)
const resultResponse = await registerResponse.json()
console.log(registerResponse)
console.log(resultResponse)
if (registerResponse.status === 200) {
const userInfo = {
hasura_id: resultResponse.hasura_id,
name: name
}
let setUserResponse = await setUserAPI(userInfo)
const resultResponseSetUser = await setUserResponse.json()
console.log(setUserResponse)
console.log(resultResponseSetUser)
if (setUserResponse["affected_rows"]) {
this.setRegisterError('')
}
else {
this.setRegisterError(resultResponseSetUser.message)
}
}
else {
this.setRegisterError(resultResponse.message)
}
}
setRegisterError function is supposed to change the state of error and if there's no error it's supposed to navigate to a new screen
setRegisterError = error => {
// If there's an error display that, otherwise send to new screen to tell the user to verify email address and then login
this.setState({error})
if(error === '') {
this.props.navigation.navigate('PostRegister')
}
}
I'm running into an async issue I hope I can get help in. The error I'm getting is
index.js:2178 Warning: Can't call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
in CreateRoomPage (created by Route)
The location of this error is within this function. The two location are found where it says loginPromise.then and the setState which is at the bottom of the function. I'm currently calling this function when a person clicks on .
createUser(e) {
if (this.state.username === "") {
e.preventDefault();
this.setState({
errors: "Username can't be blank"
});
return;
}
const loginPromise = new Promise((resolve, reject) => {
firebase.auth().onAuthStateChanged(user => {
if (user) {
window.user = user;
resolve(user.uid);
} else {
firebase
.auth()
.signInAnonymously()
.then(user => {
resolve(user.uid);
})
.catch(err => {
console.log(err);
});
}
});
});
loginPromise.then(id => {
let db = firebase.database();
let playersRef = db.ref(`Room/${this.state.roomId}/players`);
playersRef.child(`${id}`).set(`${this.state.username}`);
let player = db.ref(`Room/${this.state.roomId}/players/${id}`);
player.onDisconnect().remove();
let allPlayers = db.ref(`Room/${this.state.roomId}/all-players`);
allPlayers.child(`${id}`).set(true);
let allPlayer = db.ref(`Room/${this.state.roomId}/all-players/${id}`);
allPlayer.onDisconnect().remove();
let scoreBoard = db.ref(`Room/${this.state.roomId}/scoreBoard`);
scoreBoard.child(`${this.state.username}`).set(0);
let playerScore = db.ref(
`Room/${this.state.roomId}/scoreBoard/${this.state.username}`
);
playerScore.onDisconnect().remove();
let creator = db.ref(`Room/${this.state.roomId}`);
creator.child("creator").set(`${id}`);
db.ref(`Room/${this.state.roomId}`)
.child("gameStarted")
.set(false);
this.setState({
username: "",
errors: ""
});
});
I've spend nearly 3 hours trying to figure this out. I'm hoping someone can teach me where I'm making this error. I've tried to use a local state where once the componentDidMount it'll change local state to true and back to false once it unmounted like so:
componentDidMount() {
this.setState({ isMounted: true }, () => {
if (this.state.isMounted) {
let db = firebase.database();
let roomRefKey = db.ref("Room").push().key;
this.setState({
roomId: roomRefKey
});
}
});
}
Below is another place where it throws such an error
createUser(e) {
e.preventDefault();
if (
this.state.username.length === 0 &&
this.state.accesscode.length === 0
) {
this.setState({
errors: {
username: "Username can't be blank",
accesscode: "Access Code can't be blank"
}
});
return;
}
if (this.state.username.length === 0) {
this.setState({
errors: { username: "Username can't be blank", accesscode: "" }
});
return;
}
if (this.state.accesscode.length === 0) {
this.setState({
errors: { username: "", accesscode: "Access Code can't be blank" }
});
return;
}
const loginPromise = new Promise((resolve, reject) => {
firebase.auth().onAuthStateChanged(user => {
if (user) {
window.user = user;
resolve(user.uid);
} else {
firebase
.auth()
.signInAnonymously()
.then(user => {
resolve(user.uid);
})
.catch(err => {
console.log(err);
});
}
});
});
loginPromise.then(id => {
let db = firebase.database();
let playersRef = db.ref(`Room/${this.state.accesscode}/players`);
playersRef.child(`${id}`).set(`${this.state.username}`);
let player = db.ref(`Room/${this.state.accesscode}/players/${id}`);
player.onDisconnect().remove();
let allPlayers = db.ref(`Room/${this.state.accesscode}/all-players`);
allPlayers.child(`${id}`).set(true);
let allPlayer = db.ref(`Room/${this.state.accesscode}/all-players/${id}`);
allPlayer.onDisconnect().remove();
let scoreBoard = db.ref(`Room/${this.state.accesscode}/scoreBoard`);
scoreBoard.child(`${this.state.username}`).set(0);
let playerScore = db.ref(
`Room/${this.state.accesscode}/scoreBoard/${this.state.username}`
);
playerScore.onDisconnect().remove();
this.props.history.push({
pathname: `/waiting-room/${this.state.accesscode}`
});
});
}
I'm working in React and I have tried to handle new state in some function.
But that didn't work:
my code then failed to update the value,
there was some late in the update state rather than update immediately
the function called in the callback part passed null as argument rather than e
So after some trials, I have hardcoded all my functions.
So I have hardcoded all my code in the callback area. Now it works fine. But I still wonder how to handle the new state in a more refined way rather than hardcode all function in the callback part.
Here my handleChange function:
handleChange = (e) => {
e.preventDefault();
console.log("state in checkForm: ", this.state);
if (e.target.name === "email") {
let emailValue = e.target.value.trim()
if (
typeof(emailValue) === 'string' &&
emailValue.length >= 1 &&
validator.isEmail(emailValue)
) {
this.setState({
[e.target.name]: Object.assign({},
this.state[e.target.name], {
validation: true
}, {
value: e.target.value
})
},
() => {
let validatePassword = this.state.password.validation;
let validateEmail = this.state.email.validation;
if (validateEmail && validatePassword) {
this.setState({
validateForm: true,
nameSubmit: "onSubmit"
});
console.log("state email: ", this.state)
}
}
);
} else {
this.setState({
[e.target.name]: Object.assign({},
this.state[e.target.name], {
validation: false
})
});
this.setState({
nameSubmit: "offSubmit",
validateForm: false
});
}
}
if (e.target.name === "password") {
let regexCheck = RegExp("^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$#$!%*?&])[A-Za-z\d$#$!%*?&]{8,}");
let password = e.target.value.trim()
if (
typeof(password) === 'string' &&
password.length >= 1 &&
regexCheck.test(password)
) {
this.setState({
[e.target.name]: Object.assign({},
this.state[e.target.name], {
validation: true
}, {
value: e.target.value
})
},
() => {
let validatePassword = this.state.password.validation;
let validateEmail = this.state.email.validation;
if (validateEmail && validatePassword) {
this.setState({
validateForm: true,
nameSubmit: "onSubmit"
});
console.log("state password : ", this.state)
}
}
);
} else {
this.setState({
[e.target.name]: Object.assign({},
this.state[e.target.name], {
validation: false
})
});
this.setState({
nameSubmit: "offSubmit",
validateForm: false
});
}
}
}
As you can see all the code is directly hardcoded in the setState callback part. I wonder how to create a more flexible way to make my callback,
Any hint would be great, Thanks.
so I don't know what you mean, but I understand that you wanna break your main function in some another small function, right? If that does not help you, put the complete code here to better analyze.
Code
// Validations
isValidEmail = (emailValue) => {
return typeof emailValue === 'string' &&
emailValue.length >= 1 &&
validator.isEmail(emailValue);
}
isValidPassword = (password) => {
const pattern = RegExp("^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$#$!%*?&])[A-Za-z\d$#$!%*?&]{8,}");
return typeof password === 'string' &&
password.length >= 1 &&
pattern.test(password);
}
// updates
updateEmail = (name, email) => {
if (isValidEmail(email)) {
this.setState({
[e.target.name]: {
...this.state[e.target.name],
validation: true,
value: e.target.value
}
},
() => {
let validatePassword = this.state.password.validation;
let validateEmail = this.state.email.validation;
if(validateEmail && validatePassword) {
this.setState({validateForm: true,
nameSubmit:"onSubmit"});
console.log("state email: ", this.state)
}
}
);
return;
}
this.setState({
[e.target.name]: {
...this.state[name],
validation: false,
},
nameSubmit:"offSubmit",
validateForm: false
});
}
updatePassword = (name, password) => {
if (this.isValidPassword(password)) {
this.setState({
[e.target.name]: {
...this.state[name],
validaition: true,
value,
}
},
() => {
let validatePassword = this.state.password.validation;
let validateEmail = this.state.email.validation;
if(validateEmail && validatePassword) {
this.setState({validateForm: true, nameSubmit:"onSubmit"});
console.log("state password : ", this.state);
}
});
return;
}
this.setState({
[e.target.name]: {
...this.state[name],
validaition: false,
},
nameSubmit:"offSubmit",
validateForm: false
});
}
// main fuction
handleChange = (e) => {
e.preventDefault();
const { name, value } = e.target;
if (name === "email" ) this.updateEmail(name, value.trim())
if (name === "password") this.updatePassword(name, value.trim())
}
So, first of all, I refactor the validation hardcode to a function which called isValidEmail and isValidPassword.
After that, I extracted in the handleChange content of each if to a new method called updatedEmail and updatePassword passing your current name and value.
I use return inside if to avoid else (It's a best practice I recommend).
Another thing, I use spread instead of Object.assign, much better to see (I recommend too)
And finally I just called the methods passing what it needs (name and value);
I hope it help you ;)