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!