In my react with typescript project, I’m using the Formik component alongside Yup validation schema to validate an address form. At the moment, it work fine for an existing user, because the initialValues are populated with valid data. Also, it works as well for validating as the user types. Now, the problem is for a new user, where initialValues are empty, or just some of the fields are. Because the form is validating the empty fields when is initialized. I need it to not validate the fields when the form is initialized for a new used, but keep the same behavior of validating the field as the user types in it, and again at submit.
This is a snip of how I implemented it so far:
this is the validation schema component:
return Yup.object().shape({
street: Yup.string()
.required(messages.required)
.test(
'is-empty-or-whitespace',
'This field can t be empty',
(value) => value?.trim() !== ''
),
city: Yup.string()
.required(messages.required)
.test(
'is-empty-or-whitespace',
'This field can t be empty',
(value) => value?.trim() !== ''
),
state: Yup.string()
.required(messages.required)
.test(
'is-empty-or-whitespace',
'This field can t be empty',
(value) => value?.trim() !== ''
),
zip: Yup.string()
.required(messages.required)
.test(
'is-empty-or-whitespace',
'This field can t be empty',
(value) => value?.trim() !== ''
)
this is how Formik is used in my parent component:
<Formik
initialValues={{
street: addressValues.street,
city: addressValues.city,
state: addressValues.state,
country: addressValues.country,
}}
enableReinitialize={true}
onSubmit={() => {
submitHandler();
}}
validationSchema={validationSchema}
valdateOnChange={true}
validateOnBlur={true}
validateOnMount={true}
>
{({ values, errors, handleChange, setFieldValue, setValues }) => (
<Form id="addressForm">
<Col className="someclass">
<Row">
<MyInputFormControl
name="street"
type="text"
value={values.street}
placeholder=""
required={true}
disabled={false}
onChange={handleChange}
isInvalid={!!errors.street}
error={errors.street}
autoFocus
/>
</Row>
<Row">
<MyInputFormControl
name="city"
type="text"
value={values.city}
placeholder=""
required={true}
disabled={false}
onChange={handleChange}
isInvalid={!!errors.city}
error={errors.city}
/>
</Row>
<Row">
<MyInputFormControl
name="state"
type="text"
value={values.state}
placeholder=""
required={true}
disabled={false}
onChange={handleChange}
isInvalid={!!errors.state}
error={errors.state}
/>
</Row>
<Row">
<MyInputFormControl
name="country"
type="text"
value={values.country}
placeholder=""
required={true}
disabled={false}
onChange={handleChange}
isInvalid={!!errors.country}
error={errors.country}
/>
</Row>
</Form>
)}
</Formik>
So far I tried to set validateOnMount={!isNewUser}, pass the isNewUser to the validationSchema and change the test in validation schema like this:
street: Yup.string()
.required(messages.required)
.test(
'is-empty-or-whitespace',
'This field can t be empty',
(value) => {
if (isNewUser) { return true; }
return value?.trim() !== '';
),
But this didn’t work at all.
Is there a way to achive my desired outcome using Formik and Yup?
3
Answers
In Formik, there’s a concept of
touched
. The idea is, you could be showing errors only when there is a validation error AND the field was touched (means the user has already interacted with the field in any meaningful way).Basically, in your code you could be doing something along these lines:
Depending on the input component library you use, sometimes you need to mark fields as touched manually, but in your case it seems unnecessary as you use
handleChange
from Formik. Maybe it’s also worth to addonBlur={handleBlur}
as well though.you are missing touched check on you fields so errors are displayed instantly.
Example :
error={formik.touched.location && Boolean(formik.errors.location)}
The problem you’re experiencing is caused by the validateOnMount prop of the Formik component. When validateOnMount is set to true, Formik validates the form immediately after it’s initialised.
Given your use case, where you don’t want the form validation to be triggered initially for new users, you should set validateOnMount to false. This should fix the problem. The form validation will still be triggered as the user types in and on submit because you’ve set validateOnChange and validateOnBlur to true.
Here is the modified part of your code:
This change ensures that the form will not validate on mount (i.e., when the form is first rendered) but will still validate when the user interacts with the form. If the addressValues object is updated (say, when a new user logs in), the form will reinitialise with the new values, thanks to the enableReinitialize={true} prop, but it won’t validate right away because validateOnMount is false.