React Native course ยท Module 5: Forms

Validation with Yup and Zod

2 min read ยท
In this lesson4

Validation schemas allow you to define rules in one place.

Yup

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

// Validation schema
const schema = yup.object({
  name: yup
    .string()
    .required('Name is required')
    .min(2, 'Min. 2 characters')
    .max(50, 'Max. 50 characters'),

  email: yup
    .string()
    .required('Email is required')
    .email('Invalid email format'),

  password: yup
    .string()
    .required('Password is required')
    .min(8, 'Min. 8 characters')
    .matches(/[a-z]/, 'Lowercase letter required')
    .matches(/[A-Z]/, 'Uppercase letter required')
    .matches(/[0-9]/, 'Digit required'),

  confirmPassword: yup
    .string()
    .required('Confirm password')
    .oneOf([yup.ref('password')], 'Passwords must match'),

  age: yup
    .number()
    .transform((value) => (isNaN(value) ? undefined : value))
    .required('Age is required')
    .min(18, 'You must be at least 18')
    .max(120, 'Invalid age'),

  terms: yup
    .boolean()
    .oneOf([true], 'You must accept the terms'),
});

type FormData = yup.InferType<typeof schema>;

function YupForm() {
  const {
    control,
    handleSubmit,
    formState: { errors },
  } = useForm<FormData>({
    resolver: yupResolver(schema),
    defaultValues: {
      name: '',
      email: '',
      password: '',
      confirmPassword: '',
      age: undefined,
      terms: false,
    },
  });

  return (
    <View>
      {/* Form fields with Controller */}
    </View>
  );
}

Zod (alternative)

npx expo install zod @hookform/resolvers
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';

const schema = z.object({
  name: z
    .string()
    .min(1, 'Name is required')
    .min(2, 'Min. 2 characters'),

  email: z
    .string()
    .min(1, 'Email is required')
    .email('Invalid email'),

  password: z
    .string()
    .min(8, 'Min. 8 characters')
    .regex(/[a-z]/, 'Lowercase letter required')
    .regex(/[A-Z]/, 'Uppercase letter required')
    .regex(/[0-9]/, 'Digit required'),

  confirmPassword: z.string(),

  website: z
    .string()
    .url('Invalid URL')
    .optional()
    .or(z.literal('')),
}).refine((data) => data.password === data.confirmPassword, {
  message: 'Passwords must match',
  path: ['confirmPassword'],
});

type FormData = z.infer<typeof schema>;

function ZodForm() {
  const { control, handleSubmit } = useForm<FormData>({
    resolver: zodResolver(schema),
  });

  // ...
}

Conditional Validation

const schema = yup.object({
  hasCompany: yup.boolean(),

  companyName: yup.string().when('hasCompany', {
    is: true,
    then: (schema) => schema.required('Company name required'),
    otherwise: (schema) => schema.notRequired(),
  }),

  taxId: yup.string().when('hasCompany', {
    is: true,
    then: (schema) =>
      schema
        .required('Tax ID required')
        .matches(/^\d{10}$/, 'Tax ID must have 10 digits'),
  }),
});

Asynchronous Validation

const schema = yup.object({
  username: yup
    .string()
    .required()
    .test('unique', 'Username taken', async (value) => {
      if (!value) return true;
      const response = await fetch(`/api/check-username?name=${value}`);
      const { available } = await response.json();
      return available;
    }),
});

Validation schemas are a readable and reusable way to define rules!

Code for this lesson: App.tsx
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { z } from 'zod';

// TODO: create a validation schema using z.object({ ... }):
//   - email: z.string().email('Invalid email')
//   - password: z.string().min(8, 'Min. 8 characters')
//   - confirmPassword: z.string()
// Add .refine(...) checking whether password === confirmPassword
//   with message: 'Passwords must match' and path: ['confirmPassword']

// TODO: derive the type from the schema: type FormData = z.infer<typeof schema>

export default function App() {
  return (
    <View style={styles.container}>
      <Text style={styles.text}>
        Complete the Zod validation schema above.
      </Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    justifyContent: 'center',
    backgroundColor: '#1a5276',
  },
  text: {
    fontSize: 16,
    color: '#aed6f1',
    textAlign: 'center',
  },
});

Docs entries used in this lesson

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. How do you connect Yup with react-hook-form?

  2. 2. Which library has better TypeScript support?

Hands-on tasks in the game

  • Click in order

    Click the elements of an email schema in Yup:

  • Code editor

    Complete the schema with email, password and confirmation validation.

Useful articles