Hello! In our last lesson, we established a clean and scalable way to manage form data using a single state object and even abstracted that logic into a custom useForm hook. This was a crucial step in organizing our code.
Now, we'll build directly on that foundation. What happens when a user enters an invalid email or a password that's too short? We need to catch those issues and provide clear feedback. This lesson is all about fulfilling the learning outcome: Implement client-side form validation and display user-friendly error messages.
We'll explore two main approaches:
- A "native" method using built-in HTML5 features combined with React state for a good user experience.
- The professional, industry-standard approach using the
react-hook-formandzodlibraries for robust, scalable validation.
Mastering this is non-negotiable for building professional web applications, as it's fundamental to both user experience and data integrity.

1. Validation Strategies: When to Tell the User?
Before we write any code, we need to decide when to show an error message. The timing significantly impacts user experience.
How to Handle Form Validation in React
The article 'How to Handle Form Validation in React' from OneUptime has a great section that outlines the different strategies for when to trigger validation. Understanding these will help you build forms that feel helpful, not annoying.
Please read the section titled "Validation Strategies". Focus on understanding the difference between onChange, onBlur, and onSubmit validation and the use cases for each.
As the article explains, here's a quick summary:
onChange: Validates on every keystroke. This can be overwhelming for users, as they see errors while they are still typing. It's best reserved for specific cases, like a password strength meter.onSubmit: Validates only when the user clicks "Submit." This is the bare minimum, but it's poor UX. The user fills out the entire form, submits, and only then discovers multiple errors they have to go back and fix.onBlur: Validates when a user "leaves" an input field (clicks or tabs away). This is often the sweet spot. It allows the user to finish typing in a field before seeing an error, providing timely but not intrusive feedback.
For most fields, a combination of onBlur for initial validation and onChange for re-validating an already-invalid field is a common and effective pattern.
2. The "Manual" Approach: HTML5 Attributes & React State
You can implement effective validation without any external libraries by combining built-in HTML5 validation attributes with a little bit of React state to control the user experience.
This approach leverages browser capabilities, which is efficient and a great way to understand the underlying mechanics.
BEST Ways to Handle and Validate React Forms without a Library
The video 'BEST Ways to Handle and Validate React Forms without a Library' by Lama Dev provides an excellent hands-on demonstration of this technique. It shows how to use HTML attributes for rules and CSS pseudo-classes for styling, all orchestrated with a bit of React state.
Watch the following segments to see how a complete validation system is built from scratch: Validation Rules (19:59 - 20:45): Understand the requirements for each field. required Attribute (27:23 - 28:36): See how the simplest validation is added. pattern Attribute with Regex (28:14 - 30:49): Learn how to enforce complex rules, like character sets and length, using regular expressions. type='email' Attribute (30:49 - 31:43): See how the browser can handle email format validation for you. Improving UX with onBlur (33:26 - 38:19): This is the key part! Pay close attention to how he uses a focused state, set by the onBlur event, to prevent showing errors until the user has interacted with and left the field.
Key Concepts from the Video:
- Validation Attributes: HTML inputs have built-in validation properties.
required: The field cannot be empty.minLength/maxLength: Specifies the minimum and maximum number of characters.
type="email"ortype="url": The browser validates the format.pattern: Accepts a regular expression (regex) to test the input against. This is extremely powerful for custom rules (e.g., "password must contain a number").
- CSS Pseudo-classes: CSS can automatically style inputs based on their validation status.
:invalid: Styles the input when its value doesn't conform to its rules. You can use this to add a red border, for example.input:invalid + span: You can use the adjacent sibling selector (+) to show an error message (<span>) that is placed right after an invalid input.
- Controlling Error Visibility with State: The main problem with just using
:invalidis that errors show up immediately, even on an empty required field. The video's solution is brilliant:- Add a
focusedproperty to each input's state, initialized tofalse. - Use the
onBlurevent on the input to set itsfocusedproperty totrue. - In your CSS, only show the error message and red border when the input is both
:invalidand has adata-focused="true"attribute (which you set from your React state). This ensures errors only appear after the user has "touched" the field.
- Add a
Test your understanding!
You need to create a username field. The rules are:
- It is required.
- It must be between 4 and 12 characters long.
- It can only contain lowercase letters and numbers.
How would you write the <input> element with the correct HTML5 attributes to enforce these rules?
Show answer
<input
type="text"
name="username"
required
minLength="4"
maxLength="12"
pattern="[a-z0-9]+"
title="Username must be 4-12 characters and contain only lowercase letters and numbers."
/>
The pattern="[a-z0-9]+" regex ensures only lowercase letters and numbers are allowed. The title attribute is a nice touch for accessibility, as it provides a hint on hover about the expected format.
This manual approach is great, but as forms grow, managing all the error states, touched states, and complex validation logic can become cumbersome. This is why the industry has moved towards dedicated libraries.
3. The Professional Approach: react-hook-form & zod
For any non-trivial application, you'll want a more robust solution. React Hook Form is a library designed to make form handling in React performant and easy. We'll pair it with Zod, a schema validation library, to create a clean, powerful, and maintainable validation system. This combination is a common industry standard.
Why Use a Library?
First, let's understand the problem these libraries solve. As your forms get more complex, the manual approach leads to a lot of boilerplate code.
React Hook Form - Complete Tutorial (with Zod)
Before we dive into the solution, it's critical to understand the problem. This segment from Cosden Solutions perfectly illustrates why building complex forms natively in React becomes difficult to scale and maintain.
Watch the first part of the video (00:00 - 04:32). The presenter shows a 'native' React form and explains how adding more fields, validation logic, and loading states quickly becomes a mess. This is the motivation for using a library like React Hook Form.
Implementing react-hook-form with Zod
This approach separates your validation logic (the "schema") from your component, a key principle in good software design.
- Define a Schema with Zod: You create an object that defines the "shape" of your form data and the validation rules for each field. Zod has a fluent, chainable API that is very readable.
- Connect to React Hook Form: You pass this schema to React Hook Form using a special "resolver".
- Register Inputs: You use the
registerfunction from React Hook Form to connect your inputs. - Display Errors: The library gives you a simple
errorsobject to easily display validation messages next to the correct fields.
This process is best understood by seeing it in action.
React Hook Form - Complete Tutorial (with Zod)
Now, let's see the solution. The same video from Cosden Solutions demonstrates how to refactor the form using React Hook Form and Zod. This is the core of this section.
This is the most important part of the lesson. Please watch these two sections carefully: Integrating Zod (23:15 - 27:53): This is the modern, recommended way. See how a schema is defined using Zod. This object becomes the single source of truth for your validation rules. Notice how it's passed to useForm via the zodResolver. Displaying Error Messages (12:59 - 17:18): This section shows how to use the formState: { errors } object provided by React Hook Form to conditionally render error messages. The errors.fieldName?.message pattern is something you will use constantly.
Let's summarize the code structure.
1. Install dependencies:
npm install react-hook-form @hookform/resolvers zod
2. Create your form component:
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
// 1. Define the schema with Zod
const schema = z.object({
email: z.string().email({ message: 'Invalid email address' }),
password: z.string().min(8, { message: 'Password must be at least 8 characters' }),
});
export function LoginForm() {
// 2. Connect the schema to React Hook Form
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(schema),
});
const onSubmit = (data) => {
// This function only runs if validation passes
console.log('Form data is valid:', data);
};
return (
// The `handleSubmit` function from the library wraps your own submit handler
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label>Email</label>
{/* 3. Register the input */}
<input type="email" {...register('email')} />
{/* 4. Display the error message */}
{errors.email && <p style={{ color: 'red' }}>{errors.email.message}</p>}
</div>
<div>
<label>Password</label>
<input type="password" {...register('password')} />
{errors.password && <p style={{ color: 'red' }}>{errors.password.message}</p>}
</div>
<button type="submit">Log In</button>
</form>
);
}
Notice how much cleaner this is. The component isn't concerned with the validation logic, only with displaying the results. The rules themselves live separately in the Zod schema. This is incredibly scalable and maintainable.
Conclusion
You now have the tools to implement robust, user-friendly form validation in your React applications.
Key Takeaways:
- Validation Timing is Key:
onBluris generally the best strategy for user experience, preventing errors from appearing while the user is still typing. - Native HTML5 validation (
required,pattern, etc.) is a powerful first step, but requires careful state management in React to create a good UX. - Schema-based validation is the modern standard. Libraries like Zod allow you to define all your validation rules in a single, clean, and reusable object.
react-hook-formis the professional's choice for managing form state, submission, and validation. It integrates perfectly with Zod and removes vast amounts of boilerplate code from your components.- The
formState: { errors }object is your gateway to displaying contextual error messages to the user.
In our next lesson, we will shift gears slightly. We've focused heavily on component-level state (like in forms). Next, we'll explore the React Context API, a built-in React feature for managing global state that needs to be accessed by many components throughout your application, such as user authentication status or a theme preference.