Confirm Password using schema Zod with React Hook Form

Confirm Password using schema Zod with React Hook Form

·

3 min read

initialize the Next.js project using this command below.

pnpm create next-app@latest my-app --typescript --tailwind --eslint

and fill up with the prompt with default answer like so.

✔ Would you like to use `src/` directory? … Yes
✔ Would you like to use App Router? (recommended) … Yes
✔ Would you like to customize the default import alias (@/*)? … No

Install the Library that we need for our form and zod schema validation.

pnpm i react-hook-form zod @hookform/resolvers

Schema

For this tutorial we will be creating a simple register form which user will have fill up email, password and confirm their password. A zod schema would look like this

// src/app/register-form/schema.ts
import z from 'zod'

export const registerSchema = z
  .object({
    email: z.string().email().min(1, { message: 'Email is Required' }),
    password: z.string().min(1, { message: 'Password is Required' }),
    confirmPassword: z
      .string()
      .min(1, { message: 'Please confirm your password' }),
  })

To add validation for the confirm password, there are multiple ways we can do that. In this tutorial I will use .superRefine from zod to have more freedom than normal .refine in defining our validation. The modified schema will look like this.

// src/app/register-form/schema.ts
import z from 'zod'

export const registerSchema = z
  .object({
    email: z.string().email().min(1, { message: 'Email is Required' }),
    password: z.string().min(1, { message: 'Password is Required' }),
    confirmPassword: z
      .string()
      .min(1, { message: 'Please confirm your password' }),
  })
  .superRefine((val, ctx) => {
    if (val.password !== val.confirmPassword) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: 'Password is not the same as confirm password',
        path: ['confirmPassword'],
      })
    }
  })

export interface RegisterSchema extends z.infer<typeof registerSchema> {}

Take note, since we are using typescript, we need to export the schema type as well by using z.infer of our schema.

Register form component

We will use the schema with the react-hook-form and create a simple register form with simple styling and when we finish filling up the form, it will alert the data.

// src/app/register-form/form.tsx
'use client'

import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { registerSchema, RegisterSchema } from '@/app/register-form/schema'

export const RegisterForm = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<RegisterSchema>({
    resolver: zodResolver(registerSchema),
  })

  const onSubmit = handleSubmit((data) => {
    console.log(data)
    alert(JSON.stringify(data))
  })

  return (
    <form onSubmit={onSubmit} className='space-y-3'>
      <div className='flex flex-col gap-1'>
        <label htmlFor='email'>Email</label>
        <input
          id='email'
          {...register('email')}
          className='bg-transparent border py-2 px-4'
        />
        {errors.email?.message && <p>{errors.email?.message}</p>}
      </div>

      <div className='flex flex-col gap-1'>
        <label htmlFor='email'>Password</label>
        <input
          id='password'
          type='password'
          {...register('password')}
          className='bg-transparent border py-2 px-4'
        />
        {errors.password?.message && <p>{errors.password?.message}</p>}
      </div>

      <div className='flex flex-col gap-1'>
        <label htmlFor='confirmPassword'>Confirm Password</label>
        <input
          id='confirmPassword'
          type='password'
          {...register('confirmPassword')}
          className='bg-transparent border py-2 px-4'
        />
        {errors.confirmPassword?.message && (
          <p>{errors.confirmPassword?.message}</p>
        )}
      </div>

      <input type='submit' className='px-4 py-2 rounded bg-gray-500' />
    </form>
  )
}

now to test this we just need to replace our page component like so.

// src/app/page.tsx
import { RegisterForm } from '@/app/register-form/form'

export default function Home() {
  return (
    <main className='flex min-h-screen flex-col items-center justify-between p-24'>
      <RegisterForm />
    </main>
  )
}

Now we can test this code in our http://localhost:3000 using pnpm dev in our terminal.

Additional information

Zod is powerful schema validation library that can help you type-safe your code, if you refactor your schema like changing from email to username, all your code will have an error because, you have to update them from email to username in the files that use the schema.

.superRefine is a powerful functions to do a custom validation as well as doing so much things like we can call an APIs for checking email uniquely or not for the validation.

Cheers!