I am using react Typescript to build a form with Zod validation schema.
What I am trying to achieve?
So, I want to render conditions based on the values of another field. Every country has its own phone code and phone number format. Every time someone selects a phone code, I want the phone number field to have different validation rules. Here is what I have so far. This is basically a replicate of my actual project.
import { Formik, Field, Form, FormikHelpers } from 'formik'
import { toFormikValidationSchema } from 'zod-formik-adapter'
import { z } from 'zod'
import './App.css'
const FormField = z.object({
firstName: z.string().max(6, 'Too long'),
lastName: z.string(),
email: z.string(),
phoneCode: z.string(),
phoneNumber: z.string()
})
const Register = FormField.required()
.refine((input) => {
return input.phoneCode === '0044';
}, 'Phone code must be 0044');
Register.parse({
firstName: 'fewfwf',
lastName: 'Doe',
email: '[email protected]',
phoneCode: '0044',
phoneNumber: '0044'
})
type Values = z.infer<typeof Register>
function App() {
return (
<>
<Formik
initialValues={{
firstName: '',
lastName: '',
email: '',
phoneCode: '',
phoneNumber: ''
}}
onSubmit={(
values: Values,
{ setSubmitting }: FormikHelpers<Values>
) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 500);
}}
validationSchema={toFormikValidationSchema(FormField)}
>
{({ errors, touched }) => (
<Form>
{errors.firstName ? (<div>{errors.firstName}</div>) : null}
<label htmlFor="firstName">First Name</label>
<Field id="firstName" name="firstName" placeholder="John" />
<label htmlFor="lastName">Last Name</label>
<Field id="lastName" name="lastName" placeholder="Doe" />
<label htmlFor="email">Email</label>
<Field
id="email"
name="email"
placeholder="[email protected]"
type="email"
/>
<label htmlFor="phoneCode">PhoneCode</label>
<Field id="phoneCode" name="phoneCode" placeholder="+44" />
{errors.phoneNumber && touched.phoneNumber ? (<div>{errors.phoneNumber}</div>) : null}
<label htmlFor="phoneNumber">PhoneNumber</label>
<Field id="phoneNumber" name="phoneNumber" placeholder="789434556" />
<button type="submit">Submit</button>
</Form>
)}
</Formik>
</>
)
}
export default App
As you can see, I have used .refine() and add some random condition to test. When I run this through the parse method, it validates correctly and works, however, when I submit the form from the frontend, it still executes, even if phoneCode is not equal to ‘0044’. Is this normal behavior for the .refine method? Is there another approach that would be more practical?
3
Answers
I think I managed to solve this issue, thanks to the last post by Sina Ghandri. The idea to useFormikContext worked. I combined this with useState to store the value and then it with function to getPhoneNumberSchema to conditionally render a custom validation for phoneNumber. Here is the code in case it helps anyone else in the future
The
.refine()
method in Zod is used to apply custom refinement checks during the parsing phase. It ensures that the input satisfies the specified condition and throws an error if it does not. However, when you are using Formik and Zod together, you might want to handle these dynamic validation rules differently.In your case, you want to conditionally validate the phoneNumber field based on the selected phoneCode value. To achieve this, you can use the refine method on the individual fields within the validation schema and perform the conditional check inside the refine function. However, keep in mind that the refinement function is evaluated during the parsing phase and does not automatically get applied in the Formik context
In this way, the
.refine()
method is used on the phoneNumber field to perform the dynamic validation based on the phoneCode. Adjust the condition inside the refine function according to your specific validation logic.This way, the validation will be applied during the parsing phase, and you’ll see the error message if the condition is not met.
You can modify your existing code to include this dynamic validation:
Create a Function for Dynamic Phone Number Validation:
Define a function that returns a Zod schema for the phone number based on the selected phone code.
Update the Formik Validation Schema:
Use the dynamic phone number validation function within your Formik validation schema.
Update the Form Component:
Use Formik’s context to access the current value of the phone code and apply the corresponding validation schema to the phone number field.
The getPhoneNumberSchema function returns a different Zod schema for the phone number field depending on the selected phone code.
The Formik validationSchema is dynamically generated using the current form values, allowing the phone number validation to adapt based on the selected phone code.
Make sure to handle different phone codes and their respective validation rules within the getPhoneNumberSchema function.