Notes 01 - React Hook Form + Zod

Just some code snippets & links for my future self. Not a tutorial

Live demo link ( if the link is broken, you can always check the code below )

TL;DR

  1. we need zodResolver to integrate react-hook-form with Zod. If you are using other validation library, check the documentation here.

  2. refine comes in handy when you want to use custom validation logic. It takes 2 arguments: validation function and options. See zod documentation

  3. we can set a custom error message in {message: ‘custom_message‘}

  4. register method allow you to register an input and apply validation rules.

  5. the field errors errors is inside formState which is an object contains info about your entire form state.

Example:

A form with username, email, password and confirm password.

The goal here is to check if

  • username is valid ( using regex to check if the username contains any spaces )

  • email address is valid

  • the confirm passwords match with passwords ( using refine )

    If there are any validation errors, React Hook Form will prevent form submission.


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

// define zod schema
const schema = z
  .object({
    username: z.string()
    .trim()
    .min(5,{message:"must contain at least 5 characters"})
    .regex(/^\S+$/, 'No spaces allowed'),
    email: z.string().email({ message: 'Please enter a valid email address' }),
    password: z.string().min(8),
    confirmPassword: z.string().min(8),
  })
  .refine(values => values.password === values.confirmPassword, {
    message: 'password not match',
    path: ['confirmPassword'],
  });

function MyForm() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm({
    resolver: zodResolver(schema),
    defaultValues: {
      username: '',
      email: '',
      password: '',
      confirmPassword: '',
    },
  });
  console.log(errors,'errors')

  return (
    <form
      onSubmit={handleSubmit(data => {
        console.log(data, 'what is data on submit');
        // some api calls here
      })}
    >
      <label >User Name:</label>
      <input type="text" {...register('username')} />
      {errors.username && <p>{errors.username.message}</p>}
      <label>Email:</label>
      <input {...register('email')} />
      {errors.email && <p>{errors.email.message}</p>}
      <label>Password:</label>
      <input type='password' {...register('password')} />
      {errors.password && <p>{errors.password.message}</p>}  
      <label>Confirm Password:</label>
      <input type='password' {...register('confirmPassword')} />
      {errors.confirmPassword && <p>{errors.confirmPassword.message}</p>}  
      <button type='submit'>Submit</button>
    </form>
  );
}

export default MyForm;

CSS code


.App {
  color: #81e391;
}

form {
  max-width: 700px;
  margin: 0 auto;
  padding: 20px;
  border: 1px solid #ccc;
  border-radius: 5px;
}
label {
  display: block;
  margin-bottom: 5px;
  font-weight: bold;
}
input {
  outline:none;
  width:100%;
  padding:10px 0;
  border-radius: 3px;
  border: 1px solid #ccc;
  margin-bottom:10px;
}
button {
  display: block;
  margin: 10px 0;
  padding: 10px 20px;
  background-color: #4caf50;
  color: white;
  border: none;
  border-radius: 3px;
  cursor: pointer;
}
p{
  color:rgb(233, 107, 107);
  margin:0;
}