In this post, I will walk you through the process of building a registration form using Next.js and React Hook Form, complete with validation and dynamic language switching using i18next.
1. Creating the Registration Form
I will begin by building a simple form that accepts the following user details:
- First Name
- Last Name
- Password
- Phone Number
I will use React Hook Form for easy form management and validation, and i18next for localization.
Here’s how you can set up the form (say page.tsx):
"use client";
import { useState } from "react";
import { useForm, SubmitHandler } from "react-hook-form"; // Importing useForm
import { useTranslation } from "next-i18next";
import InputField from "../../../components/InputField"; // Input component
import Button from "../../../components/Button"; // Button component
interface IFormInput {
email: string;
password: string;
confirmPassword: string;
phone: string;
firstName: string; // Adding firstName field
lastName: string; // Adding lastName field
}
const RegisterForm = () => {
const { t } = useTranslation("common"); // Load translations from the "common" namespace
const { register, handleSubmit, formState: { errors }, watch } = useForm<IFormInput>(); // Using useForm
const onSubmit: SubmitHandler<IFormInput> = async (data) => {
console.log("Form submitted with data:", data); // Log submitted data
// Example of sending data to backend (using fetch)
try {
const response = await fetch("localhost:3000/api/auth/register", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
if (response.ok) {
console.log("Registration successful!");
} else {
const errorData = await response.json();
console.error("Registration failed:", errorData.message);
}
} catch (error) {
console.error("Network error:", error);
}
};
return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<InputField
label={t("first_name")}
type="text"
{...register("firstName", {
required: t("firstName_required"),
minLength: { value: 3, message: t("firstName_min_length") }
})}
error={errors.firstName?.message}
/>
<InputField
label={t("last_name")}
type="text"
{...register("lastName", {
required: t("lastName_required"),
minLength: { value: 3, message: t("lastName_min_length") }
})}
error={errors.lastName?.message}
/>
<InputField
label={t("email")}
type="email"
{...register("email", { required: t("email_required"), pattern: /^\S+@\S+\.\S+$/ })}
error={errors.email?.message}
/>
<InputField
label={t("password")}
type="password"
{...register("password", { required: t("password_required"), minLength: 6 })}
error={errors.password?.message}
/>
<InputField
label={t("confirm_password")}
type="password"
{...register("confirmPassword", {
required: t("confirm_password_required"),
validate: (value) => value === watch("password") || t("passwords_do_not_match"),
})}
error={errors.confirmPassword?.message}
/>
<InputField
label={t("phone_number")}
type="tel"
{...register("phone", { required: t("phone_required"), pattern: /^[0-9]{9}$/ })}
error={errors.phone?.message}
/>
<Button type="submit">
{t("register")}
</Button>
</form>
);
};
export default RegisterForm;
../../../components/Button.tsx
interface ButtonProps {
type: "submit" | "button";
children: React.ReactNode;
disabled?: boolean;
onClick?: () => void;
}
const Button = ({ type, children, disabled, onClick }: ButtonProps) => (
<button
type={type}
onClick={onClick}
disabled={disabled}
>
{children}
</button>
);
export default Button;
../../../components/InputField.tsx
interface InputFieldProps {
label: string;
type: string;
error?: string;
[key: string]: any;
}
const InputField = ({ label, type, error, ...props }: InputFieldProps) => (
<div>
<label>{label}</label>
<input
type={type}
{...props}
/>
{error && <p>{error}</p>}
</div>
);
export default InputField;
2. Handling Form Validation with React Hook Form
I also use React Hook Form for form validation. This library provides simple and flexible form validation. I validate the fields by using the register function from React Hook Form and set validation rules like required, minLength, and pattern.
Here is how I validate each field:
- Email: It must be a valid email and required.
- Password: It must be at least 6 characters long and required.
- Confirm Password: It must match the password field and is required.
- Phone Number: It must be a valid phone number, requiring only digits and a specific length.
3. Localization with i18next
In this implementation, I am using i18next for localization. This allows me to switch between languages dynamically. For example, I can easily switch the form between English and Polish by using a language toggle button.
3.1 Project Directory Structure for Localization
Here’s the directory structure for the localization files:
3.2 Configuration in next-i18next.config.js
I configure i18next in next-i18next.config.js:
// next-i18next.config.js
module.exports = {
i18n: {
locales: ['en', 'pl'], // Supported languages
defaultLocale: 'en', // Default language
},
react: { useSuspense: false }, // Disable Suspense for i18next (optional)
};
3.3 Translation Files
Here are the sample translation files for English and Polish.
en/common.json:
{
"email": "Email",
"password": "Password",
"confirm_password": "Confirm Password",
"phone_number": "Phone Number",
"first_name": "First Name",
"last_name": "Last Name",
"email_required": "Email is required",
"password_required": "Password is required",
"firstName_required": "First name is required",
"firstName_min_length": "First name must be at least 3 characters",
"lastName_required": "Last name is required",
"lastName_min_length": "Last name must be at least 3 characters",
"register": "Register",
"registering": "Registering..."
}
pl/common.json:
{
"email": "Email",
"password": "Hasło",
"confirm_password": "Potwierdź Hasło",
"phone_number": "Numer telefonu",
"first_name": "Imię",
"last_name": "Nazwisko",
"email_required": "Email jest wymagany",
"password_required": "Hasło jest wymagane",
"firstName_required": "Imię jest wymagane",
"firstName_min_length": "Imię musi mieć co najmniej 3 znaki",
"lastName_required": "Nazwisko jest wymagane",
"lastName_min_length": "Nazwisko musi mieć co najmniej 3 znaki",
"register": "Zarejestruj się",
"registering": "Rejestracja..."
}
3.4 Switching Languages Dynamically
To enable dynamic language switching, I added buttons in the RegisterForm to toggle between English and Polish. Here’s how you can do it:
// Inside the RegisterForm component
const toggleLanguage = (lang: string) => {
i18next.changeLanguage(lang); // Change the language
};
return (
<div>
<button onClick={() => toggleLanguage("en")} className="px-2 py-1">
en
</button>
/
<button onClick={() => toggleLanguage("pl")} className="px-2 py-1">
pl
</button>
</div>
);
This function will switch between English and Polish translations when the user clicks the buttons.
4. Common Issues with CORS and Routing
When working with Next.js in combination with an API on a different port, you may encounter CORS (Cross-Origin Resource Sharing) issues. This happens when your frontend (running on localhost:4000) tries to make requests to your backend (running on localhost:3000).
4.1 Solving CORS Issues
To solve the CORS issue, ensure that your backend is correctly configured to accept requests from the frontend’s origin. Here’s how you can set up CORS in your NestJS backend:
import * as cors from 'cors';
app.enableCors({
origin: 'localhost:4000', // Add your frontend URL here
methods: 'GET,POST',
allowedHeaders: 'Content-Type,Authorization',
});
This configuration will allow your backend to accept requests from localhost:4000.
4.2 Routing
If you’re using Next.js and NestJS, make sure the routes are correctly mapped. The frontend should make requests to the correct API endpoint (e.g., /api/auth/register), and you need to ensure that your proxy or API routing is correctly set up in Next.js to forward the request to your backend.
Summary
This is a simple registration form built with Next.js, React Hook Form, and i18next. I created form fields with validation and allowed dynamic language switching. If you want to improve or extend the form, feel free to adjust the validation rules or add new fields.
I hope this post was helpful! Feel free to let me know what you think on Mastodon.

Dodaj komentarz