DEV Community

Cover image for How to validate two fields that depend on each other with Yup and Zod
Werliton Silva
Werliton Silva

Posted on

How to validate two fields that depend on each other with Yup and Zod

Hey, devs...

Conditional validation is essential when your form logic depends on other fields. In this article, you'll learn how to apply it using Yup and Zod, with complete examples in React.


✅ What is Conditional Validation?

It's when the validation of a field depends on the value of another. Example: if the user checks that they are a student, the minimum required age might be different.


📦 Example with Yup

import * as yup from 'yup';

const schema = yup.object().shape({
  isStudent: yup.boolean(),
  age: yup.number().when('isStudent', {
    is: true,
    then: yup.number().required().min(18),
    otherwise: yup.number().min(12),
  }),
});
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • when('isStudent', {...}): defines the conditional logic based on the isStudent field.
  • then: requires a minimum age of 18 if the user is a student.
  • otherwise: requires a minimum age of 12 if not a student.

Example with Zod

import { z } from 'zod';

const baseSchema = z.object({
  isStudent: z.boolean(),
  age: z.number(),
});

const schema = baseSchema.superRefine((data, ctx) => {
  if (data.isStudent && data.age < 18) {
    ctx.addIssue({
      path: ['age'],
      code: z.ZodIssueCode.too_small,
      minimum: 18,
      type: 'number',
      inclusive: true,
      message: 'Students must be at least 18 years old',
    });
  } else if (!data.isStudent && data.age < 12) {
    ctx.addIssue({
      path: ['age'],
      code: z.ZodIssueCode.too_small,
      minimum: 12,
      type: 'number',
      inclusive: true,
      message: 'Minimum age is 12',
    });
  }
});

Enter fullscreen mode Exit fullscreen mode

Comparison: Yup vs Zod

Feature Yup Zod
Direct conditional syntax ✅ Yes (when) ❌ No (uses superRefine)
TypeScript support Partial (needs yup.InferType) Full and native
Custom error messages Yes Yes
Learning curve Easier for beginners More powerful, but more verbose
Performance Good Excellent

Full Example with React

Form with Formik + Yup

npm install formik yup
Enter fullscreen mode Exit fullscreen mode

Code

import React from 'react';
import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup';

const validationSchema = Yup.object().shape({
  isStudent: Yup.boolean(),
  age: Yup.number().when('isStudent', {
    is: true,
    then: Yup.number().required('Required').min(18, 'Student must be at least 18'),
    otherwise: Yup.number().min(13, 'Minimum age is 12'),
  }),
});

export default function FormYup() {
  return (
    <Formik
      initialValues={{ isStudent: false, age: '' }}
      validationSchema={validationSchema}
      onSubmit={(values) => alert(JSON.stringify(values, null, 2))}
    >
      {({ values }) => (
        <Form>
          <label>
            <Field type="checkbox" name="isStudent" />
            I am a student
          </label>

          <div>
            <label>Age:</label>
            <Field type="number" name="age" />
            <ErrorMessage name="age" component="div" style={{ color: 'red' }} />
          </div>

          <button type="submit">Submit</button>
        </Form>
      )}
    </Formik>
  );
}

Enter fullscreen mode Exit fullscreen mode

Form with React Hook Form + Zod

Instalation

npm install react-hook-form zod @hookform/resolvers
Enter fullscreen mode Exit fullscreen mode

Code

import React from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';

const baseSchema = z.object({
  isStudent: z.boolean(),
  age: z.number({ required_error: 'Please enter your age' }),
});

const schema = baseSchema.superRefine((data, ctx) => {
  if (data.isStudent && data.age < 18) {
    ctx.addIssue({
      path: ['age'],
      code: z.ZodIssueCode.too_small,
      minimum: 18,
      type: 'number',
      inclusive: true,
      message: 'Student must be at least 18',
    });
  } else if (!data.isStudent && data.age < 12) {
    ctx.addIssue({
      path: ['age'],
      code: z.ZodIssueCode.too_small,
      minimum: 12,
      type: 'number',
      inclusive: true,
      message: 'Minimum age is 13',
    });
  }
});

export default function FormZod() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm({
    resolver: zodResolver(schema),
    defaultValues: { isStudent: false, age: 0 },
  });

  const onSubmit = (data) => alert(JSON.stringify(data, null, 2));

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>
        <input type="checkbox" {...register('isStudent')} />
        I am a student
      </label>

      <div>
        <label>Age:</label>
        <input type="number" {...register('age', { valueAsNumber: true })} />
        {errors.age && <p style={{ color: 'red' }}>{errors.age.message}</p>}
      </div>

      <button type="submit">Submit</button>
    </form>
  );
}

Enter fullscreen mode Exit fullscreen mode

Conclusion

  • Use Yup if you want a simpler and more declarative syntax.
  • Use Zod if you're working with TypeScript and want more control and performance.

Both libraries are powerful - the best choice depends on your project and preferences.

Top comments (3)

Collapse
 
dev_king profile image
Dev King

Thanku for share this.

Collapse
 
werliton profile image
Werliton Silva

enjoy

Collapse
 
dev_king profile image
Dev King

that's what I needed