Implementing an Account Registration Screen and Validation Using Next.js and React Hook Form
Introduction
Up to the previous article, we used a card component on the account registration screen to place the title, message, social buttons, and so on.
I think we stopped after temporarily placing the text “Input Form” at the end, and now we’ll create the actual input form there.
Here we’ll mainly make use of two libraries that provide the features needed for input forms: React Hook Form and Zod. We’ll also briefly explain React Hook Form and Zod themselves.
Goal for This Article
The goal of this article is to build an account registration screen where the user enters their name, email address, and password. When they press the Enter key or click the “Register Account” button, we’ll be able to check the entered values in the console log.
We’ll also implement a mechanism that checks whether the email address is in a valid format when it’s entered, and if the input is incorrect, an error will be displayed—thus implementing validation functionality as well.
Ultimately, the input values will be saved to the DB, but there are still various things we need to implement before that. For now, we’ll stop at the point where we can retrieve the input values.
What Is React Hook Form?
It’s a library for efficiently handling form state management and validation in React. It makes it easy to work with form inputs and validation, and it’s also excellent in terms of performance. Its main features are:
-
Performance optimization: It’s designed to minimize re-renders caused by changes in form input and validation state, so it runs smoothly even with large forms.
-
Concise API: It provides an API that allows you to handle form state management and validation concisely, reducing configuration and the amount of code needed.
-
Validation support: It supports native HTML5 validation and integration with external libraries (e.g., Zod), allowing you to set flexible validation rules.
-
Form data management: It makes it easy to handle operations related to form data, such as retrieving input values, resetting, and displaying error messages.
We’ll introduce the detailed usage as we implement it.
What Is Zod?
Zod is a type-safe schema validation library for TypeScript. It’s used to validate JavaScript and TypeScript data structures and ensure type consistency. Its main features are:
- Type safety: Zod is tightly integrated with TypeScript and can detect type errors at compile time.
- Intuitive API: The API for defining schemas is intuitive and easy to use, allowing you to create validation rules simply.
- Structured error messages: Validation errors are structured, making it clear which part is incorrect.
You can use it like this, for example:
- name: at least 1 character
- age: numeric and positive
These are the conditions we set, and we can check whether the entered data is correct or not.
In this example, the input matches the conditions, so “Validation succeeded” is returned.
import { z } from 'zod';
const userSchema = z.object({
name: z.string().min(1),
age: z.number().int().positive(),
});
const result = userSchema.safeParse({ name: "Alice", age: 30 });
if (result.success) {
console.log("Validation succeeded:", result.data);
} else {
console.log("Validation error:", result.error.errors);
}
Implementing the Schema with Zod
Let’s jump into the implementation.
First, install the zod library.
Normally, you would install it with the following command:
$ npm i zod
However, when we install the shadcn form component that we’ll use later, zod will be installed together as a related library, so we’ll run that instead:
$ npx shadcn@latest add form
If you check package.json, you can see that several libraries have been added.
{
"name": "tutorial-nextjs-14-auth",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint --fix && eslint --fix '**/*.{js,ts,tsx}' && prettier --write '**/*.{ts,tsx,scss}'"
},
"dependencies": {
+ "@hookform/resolvers": "^3.9.0",
"@radix-ui/react-icons": "^1.3.0",
+ "@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-slot": "^1.1.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"lucide-react": "^0.438.0",
"next": "14.0.4",
"react": "^18",
"react-dom": "^18",
+ "react-hook-form": "^7.53.0",
"react-icons": "^5.3.0",
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7",
+ "zod": "^3.23.8"
},
}
Because the shadcn form component is designed to be used together with React Hook Form and Zod, they are installed together like this.
So now we have Zod installed.
Next, we’ll define the schema.
This time we’ll be entering a name, email address, and password, so we’ll define a schema for those.
Create an index.ts file under /schemas and write the following code:
import * as z from 'zod'
export const RegisterSchema = z.object({
email: z.string().email({
message: 'Please enter your email address',
}),
password: z.string().min(6, {
message: 'Password must be at least 6 characters',
}),
name: z.string().min(1, {
message: 'Please enter your name',
}),
})
We’ve set the input conditions for name, email address, and password. At the same time, we defined the messages that will be displayed on the screen when the input doesn’t meet those conditions.
This time we only set minimal validation, but you can configure many other options, such as not only the minimum number of characters but also the maximum allowed length.
For details on what kinds of checks are possible, this will be helpful:
From here on is additional information.
In my environment, I got ESLint errors in the form component.
The message says that ControllerProps, FieldPath, and FieldValues cannot be found. However, they do actually exist. Since these are type definitions, I avoided the error in my environment by applying the following settings. If you see ESLint messages, try changing the settings like this:
- import {
- Controller,
- ControllerProps,
- FieldPath,
- FieldValues,
- FormProvider,
- useFormContext,
- } from "react-hook-form";
+ import type { FieldPath, FieldValues, ControllerProps } from 'react-hook-form'
+ import { Controller, FormProvider, useFormContext } from 'react-hook-form'
Implementing the Input Component
In addition to the form component, we also need an input component for the input fields, so let’s install that.
npx shadcn@latest add input
Open register-form.tsx and paste in the following code:
'use client'
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import * as z from 'zod'
import { RegisterSchema } from '@/schema'
import { Button } from '../ui/button'
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '../ui/form'
import { Input } from '../ui/input'
import { CardWrapper } from './card-wrapper'
export const RegisterForm = () => {
const form = useForm<z.infer<typeof RegisterSchema>>({
resolver: zodResolver(RegisterSchema),
defaultValues: {
email: '',
password: '',
name: '',
},
})
const onSubmit = (values: z.infer<typeof RegisterSchema>) => {
console.log(values)
};
return (
<CardWrapper
headerLabel="Fill in each field to create an account"
buttonLabel="If you already have an account, click here"
buttonHref="/auth/login"
showSocial
>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<div className="space-y-4">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input
{...field}
placeholder="Next JS"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email Address</FormLabel>
<FormControl>
<Input
{...field}
placeholder="nextjs@example.com"
type="email"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormDescription>
Password must be at least 6 characters
</FormDescription>
<FormControl>
<Input {...field} placeholder="******" type="password" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<Button type="submit" className="w-full">
Create Account
</Button>
</form>
</Form>
</CardWrapper>
)
}
Let’s break this down and add some notes on the implementation.
const onSubmit = (values: z.infer<typeof RegisterSchema>) => {
console.log(values)
};
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
For the overall form wrapper, we have the Form component and the form tag.
By passing the content defined with useForm to the Form component, it becomes available to the subsequent FormField components.
In the form tag, we register the event that fires when the user finalizes the input by pressing the Enter key or clicking the “Create Account” button.
This time, we implemented it so that onSubmit outputs the values passed as arguments.
We plan to later modify this so that it connects to the database.
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email Address</FormLabel>
<FormControl>
<Input
{...field}
placeholder="nextjs@example.com"
type="email"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
Next, some notes on each input section.
This is the part where the user enters their email address when registering an account.
By passing the form content to FormField, the settings defined in /schemas/index.ts are applied.
Checking the Behavior
Finally, let’s actually run it and check whether it behaves as expected.
Open the account registration screen (http://localhost:3000/auth/register).
First, press the Enter key without entering anything.
You’ll see messages indicating that nothing has been entered.
Then, enter a name, email address, and password, and press Enter.
The console log on the right will display, and you can confirm the values that were entered.
Since we were able to receive the input values in onSubmit, the next step is to use these to register the data in the database.
Conclusion
This time, we used React Hook Form and Zod to create input forms for the name and email address.
We also implemented validation processing to check whether the input is correct.
Next time, we’ll implement the functionality to register the user’s input into the database.
Here Is the Next Article
Questions about this article 📝
If you have any questions or feedback about the content, please feel free to contact us.Go to inquiry form
Related Articles
Chat App (with Image/PDF Sending and Video Call Features)
2024/07/15Practical Component Design Guide with React × Tailwind CSS × Emotion: The Optimal Approach to Design Systems, State Management, and Reusability
2024/11/22Management Dashboard Features (Graph Display, Data Import)
2024/06/02Tutorial for Implementing Authentication with Next.js and Auth.js
2024/09/13Thorough Comparison of the Best ORMs for the Next.js App Router: How to Choose Between Prisma / Drizzle / Kysely / TypeORM [Part 1]
2025/03/13Test Strategy in the Next.js App Router Era: Development Confidence Backed by Jest, RTL, and Playwright
2025/04/22Done in 10 minutes. Easy deployment procedure for a Next.js app using the official AWS Amplify template
2024/11/05Building an Integrated Next.js × AWS CDK Environment: From Local Development with Docker to Production Deployment
2024/05/11


