Part 106: Enhancing Form Validation in Next.js: Ensuring Robust User Input
[App] Server Actions

In our journey to build a fully functional comment form with Next.js, we've reached a pivotal point. While the form successfully posts comments and regenerates the ReviewPage for all users, it currently lacks validation. As it stands, users can submit empty forms, resulting in blank comments displayed on the page. Let's explore how we can address this issue by implementing both client-side and server-side validation, ensuring that our application remains robust and secure.
Adding Client-Side Validation
Client-side validation enhances user experience by providing immediate feedback. Modern browsers support standard HTML form validation attributes, such as required, minlength, and maxlength, which we can utilize to prevent users from submitting invalid data.
Here's how we can implement basic client-side validation:
Require Fields: Add the
requiredattribute to both the "user" and "message" fields. This prevents form submission if these fields are empty.Set Maximum Length: Specify a maximum length for input fields. For example, limit the "user" field to 50 characters and the "message" field to 500 characters.
With these changes, browsers will automatically prompt users to fill in required fields, enhancing the overall user experience.
Code Example
Here's how the form looks with added client-side validation:
// components/CommentForm.jsx
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import { createComment } from '@/lib/comments';
export default function CommentForm({ slug, title }) {
async function action(formData) {
'use server';
if (!formData.get('user')) {
return { isError: true, message: 'Name field is required' };
}
const comment = await createComment({
slug,
user: formData.get('user'),
message: formData.get('message'),
});
console.log('created:', comment);
revalidatePath(`/reviews/${slug}`);
redirect(`/reviews/${slug}`);
}
return (
<form action={action} className="border bg-white flex flex-col gap-2 mt-3 px-3 py-3 rounded">
<p className="pb-1">
Already played <strong>{title}</strong>? Have your say!
</p>
<div className="flex">
<label htmlFor="userField" className="shrink-0 w-32">
Your name
</label>
<input id="userField" name="user" required maxLength={50} className="border px-2 py-1 rounded w-48" />
</div>
<div className="flex">
<label htmlFor="messageField" className="shrink-0 w-32">
Your comment
</label>
<textarea id="messageField" name="message" required maxLength={500} className="border px-2 py-1 rounded w-full" />
</div>
<button type="submit" className="bg-orange-800 rounded px-2 py-1 self-center">Submit</button>
</form>
);
}The Limitations of Client-Side Validation
While client-side validation is beneficial for user experience, it should not be relied upon for security. Users can easily bypass these checks, for instance, by setting the novalidate attribute on the form or by manipulating HTTP requests directly. Therefore, it's essential to implement server-side validation to ensure that only valid data is processed and stored.
Implementing Server-Side Validation
Server-side validation acts as a second line of defense, verifying data integrity before processing it. In our example, we'll check if the "user" field is filled out before proceeding with comment creation. If the validation fails, we'll return an error message.
Handling Server Action Responses
To manage the server action response, we need to incorporate client-side logic, which requires converting CommentForm into a client component. This change allows us to display error messages to users when validation fails.
Code Example
Here's how you can structure your server-side validation:
// components/CommentForm.jsx
// 'use client';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import { createComment } from '@/lib/comments';
export default function CommentForm({ slug, title }) {
async function action(formData) {
'use server';
if (!formData.get('user')) {
return { isError: true, message: 'Name field is required' };
}
const comment = await createComment({
slug,
user: formData.get('user'),
message: formData.get('message'),
});
console.log('created:', comment);
revalidatePath(`/reviews/${slug}`);
redirect(`/reviews/${slug}`);
}
return (
<form action={action}
className="border bg-white flex flex-col gap-2 mt-3 px-3 py-3 rounded">
<p className="pb-1">
Already played <strong>{title}</strong>? Have your say!
</p>
<div className="flex">
<label htmlFor="userField" className="shrink-0 w-32">
Your name
</label>
<input id="userField" name="user" required maxLength={50}
className="border px-2 py-1 rounded w-48"
/>
</div>
<div className="flex">
<label htmlFor="messageField" className="shrink-0 w-32">
Your comment
</label>
<textarea id="messageField" name="message" required maxLength={500}
className="border px-2 py-1 rounded w-full"
/>
</div>
<button type="submit"
className="bg-orange-800 rounded px-2 py-1 self-center
text-slate-50 w-32 hover:bg-orange-700">
Submit
</button>
</form>
);
}Introducing React Hook Form
For more advanced form handling and validation, consider using React Hook Form. This library simplifies form management in React applications, offering features like easy integration with UI libraries, better performance by reducing re-renders, and built-in validation support.
Benefits of React Hook Form
Performance: Minimizes re-renders, improving form performance.
Ease of Use: Simple API that integrates seamlessly with React components.
Validation: Offers built-in validation with support for custom rules.
Integration: Easily works with UI libraries to create complex forms.
Implementing React Hook Form
To get started with React Hook Form, you need to install it via npm:
npm install react-hook-formThen, integrate it into your form component:
// components/CommentForm.jsx
import { useForm } from 'react-hook-form';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import { createComment } from '@/lib/comments';
export default function CommentForm({ slug, title }) {
const { register, handleSubmit, formState: { errors } } = useForm();
async function onSubmit(data) {
'use server';
if (!data.user) {
return { isError: true, message: 'Name field is required' };
}
const comment = await createComment({
slug,
user: data.user,
message: data.message,
});
console.log('created:', comment);
revalidatePath(`/reviews/${slug}`);
redirect(`/reviews/${slug}`);
}
return (
<form onSubmit={handleSubmit(onSubmit)} className="border bg-white flex flex-col gap-2 mt-3 px-3 py-3 rounded">
<p className="pb-1">
Already played <strong>{title}</strong>? Have your say!
</p>
<div className="flex">
<label htmlFor="userField" className="shrink-0 w-32">
Your name
</label>
<input
id="userField"
{...register('user', { required: true, maxLength: 50 })}
className="border px-2 py-1 rounded w-48"
/>
{errors.user && <span className="text-red-500">Name is required</span>}
</div>
<div className="flex">
<label htmlFor="messageField" className="shrink-0 w-32">
Your comment
</label>
<textarea
id="messageField"
{...register('message', { required: true, maxLength: 500 })}
className="border px-2 py-1 rounded w-full"
/>
{errors.message && <span className="text-red-500">Comment is required</span>}
</div>
<button type="submit" className="bg-orange-800 rounded px-2 py-1 self-center">Submit</button>
</form>
);
}The Limitations of Client-Side Validation
While client-side validation is beneficial for user experience, it should not be relied upon for security. Users can easily bypass these checks, for instance, by setting the novalidate attribute on the form or by manipulating HTTP requests directly. Therefore, it's essential to implement server-side validation to ensure that only valid data is processed and stored.
Conclusion
By integrating both client-side and server-side validation, alongside leveraging a library like React Hook Form, you can create a more robust and secure application. Client-side validation enhances user experience with immediate feedback, while server-side validation ensures data integrity and security. As a best practice, always validate and sanitize user input on the server to protect your application from potential exploits and ensure reliable data processing.
Last updated