Yup for Form Validation

Yup is a powerful JavaScript schema builder for value parsing and validation, commonly used with form libraries like Formik and React Hook Form. Here’s a comprehensive guide to using Yup effectively:

Basic Validation Schemas

1. Primitive Validations

import * as yup from 'yup';

// String validation
const nameSchema = yup.string().required('Name is required');

// Number validation
const ageSchema = yup.number()
  .required('Age is required')
  .positive('Must be positive')
  .integer('Must be integer');

// Boolean validation
const acceptSchema = yup.boolean()
  .oneOf([true], 'Must accept terms');

2. Object Schema (Common for Forms)

const userSchema = yup.object().shape({
  firstName: yup.string().required('First name is required'),
  lastName: yup.string().required('Last name is required'),
  age: yup.number()
    .required('Age is required')
    .min(18, 'Must be at least 18')
    .max(100, 'Must be less than 100'),
  email: yup.string()
    .email('Invalid email')
    .required('Email is required')
});

Advanced Validation Techniques

1. Conditional Validation

const conditionalSchema = yup.object().shape({
  isMember: yup.boolean(),
  membershipId: yup.string().when('isMember', {
    is: true,
    then: yup.string().required('Membership ID is required')
  })
});

2. Cross-field Validation

const passwordSchema = yup.object().shape({
  password: yup.string()
    .required('Password is required')
    .min(8, 'Password must be at least 8 characters'),
  confirmPassword: yup.string()
    .oneOf([yup.ref('password'), null], 'Passwords must match')
});

3. Array Validation

const arraySchema = yup.object().shape({
  tags: yup.array()
    .of(yup.string().min(2, 'Tag too short'))
    .min(1, 'At least one tag required')
    .max(5, 'Maximum 5 tags allowed')
});

4. Nested Object Validation

const addressSchema = yup.object().shape({
  address: yup.object().shape({
    street: yup.string().required('Street is required'),
    city: yup.string().required('City is required'),
    zipCode: yup.string()
      .matches(/^\d{5}$/, 'Must be 5 digit ZIP code')
  })
});

Integration with Form Libraries

1. With Formik

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

const validationSchema = yup.object().shape({
  email: yup.string().email().required(),
  password: yup.string().min(8).required()
});

function LoginForm() {
  return (
    <Formik
      initialValues={{ email: '', password: '' }}
      validationSchema={validationSchema}
      onSubmit={values => console.log(values)}
    >
      <Form>
        <Field name="email" type="email" />
        <ErrorMessage name="email" component="div" />

        <Field name="password" type="password" />
        <ErrorMessage name="password" component="div" />

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

2. With React Hook Form

import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';

const schema = yup.object().shape({
  username: yup.string().required(),
  age: yup.number().positive().integer().required()
});

function ProfileForm() {
  const { register, handleSubmit, formState: { errors } } = useForm({
    resolver: yupResolver(schema)
  });

  return (
    <form onSubmit={handleSubmit(data => console.log(data))}>
      <input {...register("username")} />
      <p>{errors.username?.message}</p>

      <input type="number" {...register("age")} />
      <p>{errors.age?.message}</p>

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

Custom Validation

1. Custom Validation Functions

const customSchema = yup.object().shape({
  username: yup.string()
    .test(
      'username-available',
      'Username is taken',
      async value => {
        const available = await checkUsernameAvailability(value);
        return available;
      }
    )
});

2. Custom Error Messages

const messageSchema = yup.object().shape({
  coupon: yup.string()
    .required('Please enter a coupon code')
    .test(
      'valid-coupon',
      'Invalid coupon code', 
      value => isValidCoupon(value)
    )
});

Transformation and Parsing

1. Type Transformation

const transformSchema = yup.object().shape({
  price: yup.string()
    .transform(value => value.replace(/\$/g, ''))
    .matches(/^\d+$/, 'Must be a number')
});

2. Default Values

const defaultSchema = yup.object().shape({
  active: yup.boolean().default(true),
  createdAt: yup.date().default(() => new Date())
});

Best Practices

  1. Reuse Schemas: Create reusable validation schemas for common fields
   const emailSchema = yup.string().email().required();
  1. Compose Schemas: Combine smaller schemas into larger ones
   const userSchema = yup.object().shape({
     personalInfo: personalInfoSchema,
     contactInfo: contactInfoSchema
   });
  1. Localization: Customize error messages for different languages
   yup.setLocale({
     string: {
       email: 'Invalid email format',
       min: 'Must be at least ${min} characters'
     }
   });
  1. Async Validation: Handle server-side validation efficiently
   .test('unique-email', 'Email already exists', async email => {
     const exists = await checkEmailExists(email);
     return !exists;
   })
  1. Performance: For large forms, consider splitting validation schemas

Yup provides a declarative way to handle complex validation logic with excellent TypeScript support. Its chainable API makes it easy to create readable and maintainable validation rules for your forms.

Leave a Reply

Your email address will not be published. Required fields are marked *