Conditional form validation with Yup - reactjs

I am beginner in Yup and I need to do some form validation with Yup. Here is an example of my schema :
const MyFormSchema = Yup.object().shape({
contactId: Yup.string().required(),
phoneNumber : Yup.string().phoneNumber().required(),
email: Yup.string().email().required()
});
My form is valid in 2 cases : either the user enters the contactID or he enters the phoneNumber and the email.
I tried to implement this using test function but was unable to do it. Any help would be appreciated.

You can build the same logic for rest 2 fields:
Yup.object().shape({
contactId: Yup.string().when(["phoneNumber", "email"], {
is: (phoneNumber, email) => !phoneNumber || !email,
then: Yup.string().required()
}),
phoneNumber: Yup.string().when("contractId", {
is: contactId => !!contactId,
then: Yup.string().required()
}),
email: Yup.string().when("contractId", {
is: contactId => !!contactId,
then: Yup.string()
.email()
.required()
})
});
To check for email, you can use built in function Yup.email(). For phone you need to build custom validator:
const PHONE_VALIDATOR = /...your RegEx here.../
...
phoneNumber : Yup.string().matches(PHONE_VALIDATOR, 'Phone is not valid').required()

Related

How to use Formik with React-Select with isMulti?

I'm building a system where we can create a project and assign it to several users by filling up a form using Formik, Yup and React-Select.
However, I'm really struggling with passing the array of users when submitting my form using Formik, Formik doesn't receive the data from React-Select.
Here are snippets of the code:
const validationSchema = () => {
return Yup.object().shape({
title: Yup.string().required('A title is required'),
description: Yup.string().required('A description is required'),
assigned: Yup.array().required('At least one assigned user is required'),
});
};
const formik = useFormik({
initialValues: {
title: '',
description: '',
assigned: [],
},
validationSchema,
validateOnBlur: false,
validateOnChange: false,
onSubmit: (data) => {
ProjectService.create(data).then(() => {
navigate('/projects');
window.location.reload();
});
},
});
The component containing the options to select:
Assign to:
<SelectList
name="users"
options={convertToReactSelectObject(users)}
onChange={(assignedUsers) =>
formik.setFieldValue('assigned', assignedUsers.value)
}
value={formik.values.assigned}
/>
{formik.errors.assigned ? formik.errors.assigned : null}
Does someone have an idea of what I should fix to make it work?
Thank you!
My mistake was that I was trying to only pass the "value" field of the array to Formik which doesn't work. So I've passed the whole array of assigned users
<SelectList
options={convertToReactSelectObject(users)}
value={formik.values.assigned}
onChange={(assignedUser) =>
formik.setFieldValue('assigned', assignedUser)
}
/>
Then on the backend, the new project will only take the value field of the assigned users.
// Create a Project
const project = new Project({
title: req.body.title,
description: req.body.description,
assigned: req.body.assigned.map((e) => e.value),
});

conditionally required field if another field has a value using array of objects

I want to make one field required, but only if the first field has some value on it.
I know I could do something like this, using when if this wasn't an array of objects, but since it is, the validation is not working.
myFieldArray: yup.array().of(
yup.object().shape({
firstField: yup.string(),
secondField: yup.string().when("firstField", {
is: (firstField) => firstField.length > 0,
then: yup.string().required("this field is required"),
}),
})
)
I tried to use yup.ref too, but "when" don't accept refs, only strings
myFieldArray: yup.array().of(
yup.object().shape({
firstField: yup.string(),
secondField: yup.string().when(yup.ref("firstField"), {
is: (firstField) => firstField.length > 0,
then: yup.string().required("this field is required"),
}),
})
)
This one worked
myArray: Yup.array().of(
Yup.object().shape({
firstField: Yup.string(),
secondField: Yup.string().when("firstField", {
is: firstField => firstField && firstField.length > 0,
then: Yup.string().required("this field is required")
})
})
)
Code sandbox => https://codesandbox.io/s/react-formik-yup-forked-wcfkh?file=/src/App.js

how can I check if the password matches with yup

I want to use oneOf but it doesnt work. I want to check If the password and password-Repeat matches. In nodejs it works, but in react native it doesnt work. All errors are shown except password-Repeat. Why?
const registerSchema = yup.object().shape({
fullName: yup.string()
.required('Your name is required.')
.min(4)
.max(40),
email: yup.string()
.required()
.min(6)
.max(255)
.email(),
password: yup.string()
.required()
.min(7)
.max(255),
passwordRepeat: yup.string()
.oneOf([yup.ref('password'), null], 'Password does not match')
});
all my TextInput has values and onChangeText and onBlur and all fields work fine except password repeat
Try this way
Yup.object().shape({
password: Yup.string().required("Required"),
passwordRepeat: Yup.string()
.required("Required")
.when('password', (password, schema) => {
return schema.test({
test: (passwordRepeat) => password === passwordRepeat,
message: 'Password does not match',
});
}),
});

validate if username already exists while typing using Formik and Yup

I have a task from my company where I have to show whether the username is taken or not, I am using formik and yup validation for the checks so for the time being I have added a custom test functionality to yup validation which shows whether the username is taken or not on the click of the submit button, however, my original task was to show the error message dynamically while the user enters the username, it should tell him if the the username is taken or not.
I understood that I might have to manipulate the default handleChange of formik, but I’m unable to do so.
Any help is highly appreciated!!!
validationSchema: Yup.object({
name: Yup.string()
.min(2, "Mininum 2 characters")
.max(30, "Maximum 30 characters")
.required("Your name is required"),
email: Yup.string()
.email("Invalid email format")
.test("email", "This email has already been registered", function (email) {
return checkAvailabilityEmail(email);
})
.required("Your email is required"),
username: Yup.string()
.min(1, "Mininum 1 characters")
.max(15, "Maximum 15 characters")
.test("username", "This username has already been taken", function (username) {
return checkAvailabilityUsername(username);
})
.required("You must enter a username"),
In your validation schema, just add the test function which is found in Yup. Yup also supports async/await as well!
Anyways, to answer your question, here's my example:
validationSchema: Yup.object({
email: Yup.string()
.min(8, 'Must be at least 8 characters')
.max(20, 'Must be less than 20 characters')
.required('Email is required')
.test('Unique Email', 'Email already in use', // <- key, message
function (value) {
return new Promise((resolve, reject) => {
axios.get(`http://localhost:8003/api/u/user/${value}/available`)
.then((res) => {
resolve(true)
})
.catch((error) => {
if (error.response.data.content === "The email has already been taken.") {
resolve(false);
}
})
})
}
),
}),
The value of the email is passed and called to the database which returns a response object. If the response object is an error and the error content is matched, it will be resolved to false and display the message, 'Email already in use'.
EDIT: If you do not want to validate on every keystroke, you can pass in some props, in particular, validateOnChange={false} or ValidateOnBlur={false}. This can help improve performance issues and reduce API calls to your backend!
<Formik
initialValues={{
firstName: '',
lastName: '',
email: '',
}}
validationSchema={validationSchema}
validateOnChange={false} // disable on every keystroke
...
>
To display your error messages accordingly:
{(errors.email && touched.email) && <span style={{ color: 'red', fontSize: '0.8rem', marginLeft: '1.5rem' }}>{errors.email}</span>}

Yup validation for loginID with dynamic data type(either email or phone number)

I am trying to implement login functionality using Formik and Yup. This is my current schema
let loginSchema = yup.object().shape({loginId:
yup
.string()
.email("That doesn't look like a valid email")
.required("This field is required."),password: yup.string().required("This field is required."),});
But my loginId can either be a phone number or email. So, how do i add the validation based on the type of field that has been entered in the form. If its an email, trigger validation for email or if its phone number, i want to validate it against a regex.
Input field that accepts both email and phone:
const LoginSchema = yup.object().shape({
email_or_phone: yup.string()
.required('Email / Phone is required')
.test('email_or_phone', 'Email / Phone is invalid', (value) => {
return validateEmail(value) || validatePhone(parseInt(value ?? '0'));
}),
password: yup.string().required()
});
const validateEmail = (email: string | undefined) => {
return yup.string().email().isValidSync(email)
};
const validatePhone = (phone: number | undefined) => {
return yup.number().integer().positive().test(
(phone) => {
return (phone && phone.toString().length >= 8 && phone.toString().length <= 14) ? true : false;
}
).isValidSync(phone);
};
Solved email and PhoneNumber Validation
const loginValidationSchema = Yup.object({
email: Yup.string().when("isEmail", {
is: '1',
then: Yup.string()
.email("Please enter valid email")
.required("email cannot be empty"),
otherwise: Yup.string()
.required("phonenumber cannot be empty")
.min(6, 'phonenumber must be at least 6 char'),
}),
password: Yup.string()
.required("Password cannot be empty")
.min(6, 'Password must be at least 6 char'),
});
<Formik
validationSchema={loginValidationSchema}
initialValues={{ isEmail: 0, email: '', password: '' }}
onSubmit={}
>
{({ handleChange, handleBlur, handleSubmit, values, errors,
touched }) => (
<View>
<TextInput
placeholder="ramchandran2897#gmail.com"
onChangeText={(event)=>{
handleChange("email")(event)
if(Number(values.phonenumberOrEmail)){
handleChange("isEmail")('0')
}else{
handleChange("isEmail")('1')
}
}}
onBlur={handleBlur('email')}
value={values.email}
keyBoardType="email-address"
autoFocus={false}
returnKeyType='next'
returnKeyLabel='>'
onSubmitEditing={() => passwordRef.current?.focus()}
error={errors.email}
touched={touched.email}
/>
<TextInput
placeholder="****"
placeholderTextColor={Colors.darkLight}
onChangeText={handleChange('password')}
onBlur={handleBlur('password')}
value={values.password}
secureTextEntry={hidePassword}
isPassword={true}
hidePassword={hidePassword}
setHidePassword={setHidePassword}
min={6}
autoCapitalize='none'
keyboardAppearance='dark'
onSubmitEditing={handleSubmit}
ref={passwordRef}
error={errors.password}
touched={touched.password}
/>
<TextInput
onChangeText={handleChange('isEmail')}
keyBoardType='none'
/>
</View>
)}
</Formik>
If you want to dynamically validate your fields, use when for the validationSchema.
An example from the documentation.
let schema = object({
isBig: boolean(),
count: number()
.when('isBig', {
is: true, // alternatively: (val) => val == true
then: yup.number().min(5),
otherwise: yup.number().min(0),
})
.when('$other', (other, schema) => (other === 4 ? schema.max(6) : schema)),
});
So in your case you have to craft your is statement to decide whether it is an email of a phone number, then you can attach validation accordingly.
I didn't work with formik but face the same situation as you face here. I was working with Form Hook. Maybe somehow it will help you or someone.
First of all, add a hidden input after your main input in which the user will add phone or email. On change of that main input, you have to check that if this string contains '#' or not. If it contains # then make a state and update state with true like this.
const onChangeEmailOrPhone = (event) => {
let email = event.target.value.includes('#');
if (email) {
setIsEmail(true);
} else {
setIsEmail(false);
}
};
and set this state value to the hidden input just like this
<TextField
type="hidden"
name="isEmailValue"
inputRef={register}
value={isEmail}
/>
Following should be your validation schema to check weather isEmailValue is true or not, If true then you have to validate for email otherwise for the phone just like this.
let loginSchema = yup.object().shape({
emailOrPhone: yup.string().when('isEmailValue', {
is: 'true',
then: yup
.string()
.email('Please enter valid email')
.required('This field is required'),
otherwise: yup
.string()
.matches(phoneRegex, 'Please enter valid phone number')
.required('This field is required'),
})});
Following is phoneRegex for your help
const phoneRegex = /^(\+?\d{0,4})?\s?-?\s?(\(?\d{3}\)?)\s?-?\s?(\(?\d{3}\)?)\s?-?\s?(\(?\d{4}\)?)?$/;

Resources