Issue
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: 'jogn@doe.com',
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="john@acme.com"
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?
Solution
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.
import { Formik, Field, Form, FormikHelpers, useFormikContext } from 'formik';
import { toFormikValidationSchema } from 'zod-formik-adapter';
import { z } from 'zod';
import './App.css';
// Function to get dynamic phone number validation schema based on phone code
const getPhoneNumberSchema = (phoneCode: string) => {
switch (phoneCode) {
case '0044':
return z.string().length(10, 'UK phone number must be 10 digits long');
// Add cases for other phone codes with their respective validation rules
default:
return z.string();
}
};
const FormField = z.object({
firstName: z.string().max(6, 'Too long'),
lastName: z.string(),
email: z.string().email(),
phoneCode: z.string(),
phoneNumber: z.string() // Default validation, will be dynamically updated
});
type Values = z.infer<typeof FormField>;
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={() => {
const { values } = useFormikContext<Values>();
return toFormikValidationSchema(FormField.extend({
phoneNumber: getPhoneNumberSchema(values.phoneCode)
}));
}}
>
{({ errors, touched }) => (
<Form>
{/* ... other fields ... */}
<Field id="phoneCode" name="phoneCode" placeholder="+44" />
<Field id="phoneNumber" name="phoneNumber" placeholder="789434556" />
{errors.phoneNumber && touched.phoneNumber && <div>{errors.phoneNumber}</div>}
<button type="submit">Submit</button>
</Form>
)}
</Formik>
</>
)
}
export default App;
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.
Answered By - Sina Ghadri
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.