I have a form with an email that creates an account on the backend
and the user can change the email in this step. This code do this by putting the function in onBlur, but if I change the email in the input and don't leave the field, onBlur doesn't happen. I can click submit direct, sending my old email for account creation.
This is the code:
const SendForm = ({ submit }) => {
const onLabelSubmit = () => async (event, newValue, name) => {
handleLabelSubmit(newValue, name);
};
const submitForm = () => {
// validations
submit();
};
const handleSubmitAccount = (e) => {
e.preventDefault();
dispatch(submitAccount(field.name, field.email))
.then(() => {
submitForm();
});
};
return (
<form onSubmit={handleSubmitAccount}>
<Field
id="email"
name="email"
label="label"
onBlur={onLabelSubmit(label.email)}
/>
<Button type="submit">
Submit Form
</Button>
</form>
);
};
Is there any way to do what onBlur does, but when I click the submit button?
I need improving the experience and avoiding mistakes.
Thanks!
Add state to the component with useState.
Instead of onBlur use onChange and add value property to your input field, it will fire on every user input so you will always have the latest given email & username.
const SendForm = ({ submit }) => {
const [fields, setFields] = useState({ name: '', email: '' })
const onFieldChange = () => async (newValue, name) => {
setFields({ ...fields, [name]: newValue });
};
const submitForm = () => {
// validations
submit();
};
const handleSubmitAccount = (e) => {
e.preventDefault();
dispatch(submitAccount(field.name, field.email))
.then(() => {
submitForm();
});
};
return (
<form onSubmit={handleSubmitAccount}>
<Field
id="email"
name="email"
label="label"
value={fields.email}
onChange={onFieldChange(label.email)}
/>
<Button type="submit">
Submit Form
</Button>
</form>
);
};
Related
I'm developing a custom Form validation for my React project with Typescript.
I'm facing an issue with the useState that is not updating immediately the state containing the errors when I submit a form.
Let me provide you an example.
const initialFormState = {
email: '',
password: '',
}
const SignUpForm = () => {
const [formValues, setFormValues] = useState(initialFormState);
const [validationErrors, setValidationErrors] = useState<string>([]);
const handleChange = () => {
// handle the change implementation updating the formValues ...
}
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
doValidationOnSubmit();
if (validationErrors.length > 0) {
console.log('validation errors!!');
return;
}
doLogin();
};
const doValidationOnSubmit = () => {
Object.entries(formValues).forEach(([inputName, value]) => {
if (formValues[inputName] === '') {
setValidationErrors((oldValidationErrors) => [...oldValidationErrors, `${inputName} is not valid`]);
}
});
}
const doLogin = () => {
// do login logic
}
return (
<>
<form onSubmit={handleSubmit}>
<input type="email" name="email" onChange={handleChange} />
<input type="password" name="email" onChange={handleChange} />
<button type="submit">Login</button>
</form>
</>
);
}
export default SignUpForm;
When I'm checking for the errors in the handleSubmit, there are no errors, even if errors should be present there:
if (validationErrors.length > 0) {
console.log('validation errors!!');
return;
}
In general, I'm wondering what is the best practice in order to avoid these kinds of issues with the react state not updating immediately the state?
I already tried with useEffect, listening on the validationErrors changes but nothing changes actually, the behavior is pretty the same.
I'm sure I'm missing something.
useState is asynchronous, so state changes (setValidationErrors) are not applied immediately. Therefore, you cannot get the latest state of validationErrors in the next line.
We can do validation and set state separately. In that case, you can leverage the latest value (not the latest state) to check values validity.
const initialFormState = {
email: '',
password: '',
}
const SignUpForm = () => {
const [formValues, setFormValues] = useState(initialFormState);
const [validationErrors, setValidationErrors] = useState<string>([]);
const handleChange = () => {
// handle the change implementation updating the formValues ...
}
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
//get all invalid values
const invalidValues = returnInvalidValues();
//update state
setValidationErrors(prev => [...prev, ...invalidValues])
if (invalidValues.length > 0) {
console.log('validation errors!!');
return;
}
doLogin();
};
const returnInvalidValues = () => {
return Object.entries(formValues).filter(([inputName, value]) => formValues[inputName] === ''). map(invalidValue => `${inputName} is not valid`);
}
const doLogin = () => {
// do login logic
}
return (
<>
<form onSubmit={handleSubmit}>
<input type="email" name="email" onChange={handleChange} />
<input type="password" name="email" onChange={handleChange} />
<button type="submit">Login</button>
</form>
</>
);
}
export default SignUpForm;
You also can try useEffect
const initialFormState = {
email: '',
password: '',
}
const SignUpForm = () => {
const [formValues, setFormValues] = useState(initialFormState);
const [validationErrors, setValidationErrors] = useState<string>([]);
const handleChange = () => {
// handle the change implementation updating the formValues ...
}
//introduce useEffect here
useEffect(() => {
if (validationErrors.length > 0) {
console.log('validation errors!!');
return;
}
doLogin();
}, [validationErrors]);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
doValidationOnSubmit();
};
const doValidationOnSubmit = () => {
Object.entries(formValues).forEach(([inputName, value]) => {
if (formValues[inputName] === '') {
setValidationErrors((oldValidationErrors) => [...oldValidationErrors, `${inputName} is not valid`]);
}
});
}
const doLogin = () => {
// do login logic
}
return (
<>
<form onSubmit={handleSubmit}>
<input type="email" name="email" onChange={handleChange} />
<input type="password" name="email" onChange={handleChange} />
<button type="submit">Login</button>
</form>
</>
);
}
export default SignUpForm;
If it does not work for your case, you can delay using the latest state with setTimeout. With this approach, it will put the task to get the latest state to the end of the call stack queue (you can check this document)
const initialFormState = {
email: '',
password: '',
}
const SignUpForm = () => {
const [formValues, setFormValues] = useState(initialFormState);
const [validationErrors, setValidationErrors] = useState<string>([]);
const handleChange = () => {
// handle the change implementation updating the formValues ...
}
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
doValidationOnSubmit();
setTimeout(() => {
if (validationErrors.length > 0) {
console.log('validation errors!!');
return;
}
doLogin();
})
};
const doValidationOnSubmit = () => {
Object.entries(formValues).forEach(([inputName, value]) => {
if (formValues[inputName] === '') {
setValidationErrors((oldValidationErrors) => [...oldValidationErrors, `${inputName} is not valid`]);
}
});
}
const doLogin = () => {
// do login logic
}
return (
<>
<form onSubmit={handleSubmit}>
<input type="email" name="email" onChange={handleChange} />
<input type="password" name="email" onChange={handleChange} />
<button type="submit">Login</button>
</form>
</>
);
}
export default SignUpForm;
Im trying to console.log the input value inside Input but every time i type something the page reloads. Console show no errors.
const [enteredText, setEnteredText] = useState("");
const textHandler = (event) => {
setEnteredText(event.target.value);
};
const submitHandler = (event) => {
event.preventDefault();
const textData = {
text: enteredText,
};
console.log(textData);
};
return (
<Post onSubmit={submitHandler}>
<TopPost>
<ProfilePicture></ProfilePicture>
<Input
placeholder="What's happening?"
required
onChange={textHandler}
/>
</TopPost>
<BottomPost>
<Button type="submit">Post</Button>
</BottomPost>
</Post>
I am using email.js to send emails client side, and validator to validate the email and phone number. Everything works fine, except... I am trying to empty the input fields after a successful submission.
Here is what I have so far:
State management:
const formRef = useRef()
const [emailError, setEmailError] = useState('')
const [phoneError, setPhoneError] = useState('')
const [inputValues, setInputValues] = useState({email: "", phone: ""})
const handleOnChange = event => {
const { name, value } = event.target;
setInputValues({ ...inputValues, [name]: value });
validateEmail(inputValues.email)
validatePhone(inputValues.phone)
};
Validation and Submit handler:
const validateEmail = (email) => {
if (validator.isEmail(email)) {
setEmailError('Valid Email :)')
return true
} else {
setEmailError('Enter valid Email!')
return false
}
}
const validatePhone = (phone) => {
if (validator.isMobilePhone(phone)) {
setPhoneError('Valid Phone :)')
return true
} else {
setPhoneError('Enter valid Phone!')
return false
}
}
const handleSubmit = (e) => {
e.preventDefault()
const isValidEmail = validateEmail(e.target.email.value)
const isValidPhone = validatePhone(e.target.phone.value)
if(isValidEmail && isValidPhone){
console.log("if both inputs are true, on to submit")
setSentMessage(false)
//shouldnt this line empty out the current fields?
setInputValues({email: "", phone: ""})
} else {
console.log("one of the inputs is false, wont submit")
}
}
Form:
<form ref={formRef} onSubmit={handleSubmit} className={classes.contactPageInputs}>
<input placeholder='email' type="text" id="userEmail" name="email" onChange={(e) => handleOnChange(e)}></input>
<span style={{fontWeight: 'bold', color: 'red' }}>{emailError}</span>
<input placeholder='phone' id="userPhone" name="phone" onChange={(e) => handleOnChange(e)}></input> <br />
<span style={{fontWeight: 'bold', color: 'red' }}>{phoneError}</span>
<button className={classes.submitButton}>submit</button>
</form>
QUESTION:
How can I reset the input fields after submission?
Setting the form refs value to null worked.
Here is what I added to the handleSubmit function, after sending the email:
formRef.current[0].value = null
formRef.current[1].value = null
UPDATE
This si the better way. I added value={inputValues.user_email}, value={inputValues.user_phone}, value={inputValues.user_message} to each respective input field.
I did a thorough search before positing this question, most of them were using <Formik/> where they had sent onSubmit handler as a prop so that it can be tested whether it is called or not when the form is submitted.
In my case, I'm using useFormik hook for my form and I can't get to reach the onSubmit code when the form is submitted. The type property of button is submit so the onClick function is undefined when I displayed the element in console log.
Below is excerpt from my code
Component
export const WithMaterialUI = () => {
const formik = useFormik({
initialValues: {
email: "foobar#example.com"
},
validationSchema: validationSchema,
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2));
}
});
return (
<div>
<form onSubmit={formik.handleSubmit} data-testid="form">
<TextField
data-testid="email"
fullWidth
id="email"
name="email"
label="Email"
value={formik.values.email}
onChange={formik.handleChange}
error={formik.touched.email && Boolean(formik.errors.email)}
helperText={formik.touched.email && formik.errors.email}
/>
<Button color="primary" variant="contained" fullWidth type="submit">
Submit
</Button>
</form>
</div>
);
};
Test case
const mockSubmit = jest.fn();
describe("Test for formik onSubmit", () => {
it("should handle form submission", async () => {
const { getByTestId } = render(<WithMaterialUI />);
const email = getByTestId("email");
const form = getByTestId("form");
act(() => {
fireEvent.change(email, { target: { value: "abc#gmail.com" } });
});
act(() => {
fireEvent.submit(form);
});
await waitFor(() => {
expect(mockSubmit).toHaveBeenCalled();
});
});
});
I created a working example using CodeSandbox. Could anyone please help?
const TextForm: React.FunctionComponent<Props> = (props) => {
const formError = yup.object({
name: yup.string().required("Required"),
});
const formValidation = (fieldName) => {
return {
invalid: !!form.errors[fieldName] && form.touched[fieldName],
invalidText: form.errors[fieldName],
onBlur: form.handleBlur,
};
};
const form = useFormik({
initialValues: {
name,
id,
},
formValidation
formError,
validateOnChange: true,
validateOnMount: true,
initialTouched: {},
});
return(
<React.Fragment>
<form>
<TextInput
id="text-input-2"
{...validation("name")}
type="text"
name = "name"
onChange={(event) => {
setName(event.target.value);
form.handleChange(event);
}}
/>
<TextInput
id="text-input-2"
{...validation("id")}
type="text"
name = "id"
onChange={(event) => {
setId(event.target.value);
}}
/>
<Button>Clear</Button>
<Button>Submit</Button>
</form>
</React.Fragment>
)
}
Validations in my form are working fine. But if user does not enter any field, it comes with required warning. I am trying to clear/reset the form on Clear button click ,but could not find any possible solution working. Can anyone help me with this.
A quick search of the Formik docs.
The Formik onSubmit and onReset are passed formikBag as their second argument, of which contains the resetForm action. The resetForm callback can be destructured from this formikBag object and used within your callback.
onSubmit
onReset
const form = useFormik({
initialValues: {
name,
id,
},
formValidation
formError,
validateOnChange: true,
validateOnMount: true,
initialTouched: {},
onSubmit: (values, { resetForm }) => {
// submission logic
resetForm();
},
onReset: (values, { resetForm }) => resetForm(),
});
You also just need to ensure your form buttons have the correct button types so the form takes the correct action when clicked.