Displaying Zod Errors Via Toaster Notifications A Comprehensive Guide
Hey guys! As developers, we all know how crucial it is to provide a smooth user experience, especially when it comes to forms. Nothing is more frustrating than filling out a form, hitting submit, and then seeing a cryptic error message or, even worse, no message at all! That's why displaying Zod validation errors using toaster notifications can be a game-changer. In this article, we'll dive into how to catch Zod validation errors during signup, sign-in, and OTP verification, and then display them to users in a clear and friendly way using a toaster service. Let's make those forms less frustrating and more user-friendly!
Why Use Zod and Toaster Notifications?
Before we jump into the implementation, let's quickly chat about why we're choosing Zod for validation and toaster notifications for error display.
Zod for Robust Validation
First off, Zod is a fantastic library for schema declaration and validation in TypeScript and JavaScript. It allows us to define schemas that describe the shape of our data, and then easily validate data against those schemas. This means we can ensure that the data our users are submitting is in the correct format before it even hits our backend. Why is this cool? Well:
- Strong Typing: Zod plays super nicely with TypeScript, giving us excellent type safety throughout our application.
- Clear Schemas: We can define clear, readable schemas that act as a single source of truth for our data structures.
- Validation with Ease: Zod makes validation a breeze, returning detailed error information when things go wrong.
When dealing with forms like signup, sign-in, and OTP verification, having robust validation is crucial. We need to ensure that email addresses are properly formatted, passwords meet complexity requirements, and OTPs are in the correct format. Zod helps us do all of this with ease.
Toaster Notifications for User-Friendly Errors
Now, let's talk about toaster notifications. Instead of displaying raw error objects or generic error messages, toaster notifications provide a non-intrusive way to display feedback to the user. They pop up, usually at the corner of the screen, and disappear after a short time. This is way better than alert boxes or inline error messages that can clutter the UI.
- Non-Intrusive: Toasters don't block the user's flow.
- Clear Feedback: We can customize the messages to be user-friendly and informative.
- Better UX: They make the whole error-handling process feel smoother and more polished.
Imagine a user mistypes their password during sign-in. Instead of a jarring alert, a toaster notification can gently inform them, "Incorrect password. Please try again." See how much friendlier that is?
So, by combining Zod for validation and toaster notifications for error display, we can create a much better user experience. Now, let's get into the how-to!
Setting Up Zod Validation Schemas
Okay, let's start by setting up our Zod validation schemas for signup, sign-in, and OTP verification. This is where we define the rules for the data we expect from our users. We'll create schemas for each of these forms to ensure that the input is valid before we proceed. Let's break it down:
Signup Schema
For the signup form, we typically need fields like name, email, and password. We might also want to include password confirmation. Here’s how we can define a Zod schema for this:
import { z } from "zod";
export const signupSchema = z.object({
name: z.string().min(2, { message: "Name must be at least 2 characters." }),
email: z.string().email({ message: "Invalid email address." }),
password: z
.string()
.min(8, { message: "Password must be at least 8 characters." }),
confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
message: "Passwords do not match",
path: ["confirmPassword"], // path of error
});
export type SignupSchema = z.infer<typeof signupSchema>;
Let's walk through this schema:
- We import
z
from thezod
library. - We define a
signupSchema
usingz.object()
, which means we're expecting an object. name
: We usez.string()
to ensure the name is a string and.min(2)
to require at least 2 characters, with a custom error message.email
: We usez.string().email()
to validate that the email is in a proper email format, again with a custom error message.password
: We usez.string().min(8)
to ensure the password is at least 8 characters long, with a custom error message.confirmPassword
: We have another string field for password confirmation..refine()
: This is a super cool feature where we add a custom validation rule. Here, we're ensuring that thepassword
andconfirmPassword
fields match. If they don't, we add an error message to theconfirmPassword
field.export type SignupSchema = z.infer<typeof signupSchema>
: This line is TypeScript magic! It creates a typeSignupSchema
based on the schema we defined. This means we can use this type to ensure type safety in our components.
Sign-in Schema
For the sign-in form, we typically need email and password. Here’s the Zod schema:
import { z } from "zod";
export const signinSchema = z.object({
email: z.string().email({ message: "Invalid email address." }),
password: z.string().min(1, { message: "Password is required." }),
});
export type SigninSchema = z.infer<typeof signinSchema>;
This schema is simpler:
email
: We usez.string().email()
to validate the email format.password
: We usez.string().min(1)
to ensure that the password is not empty.
OTP Verification Schema
For OTP verification, we need a field for the OTP code. The OTP is often a number or a string of numbers, so we’ll validate it accordingly:
import { z } from "zod";
export const otpSchema = z.object({
otp: z.string().length(6, { message: "OTP must be 6 digits." }),
});
export type OtpSchema = z.infer<typeof otpSchema>;
otp
: We usez.string().length(6)
to ensure that the OTP is exactly 6 characters long. You can adjust the length based on your requirements.
With these schemas in place, we have a solid foundation for validating user input. Next, we'll look at how to integrate these schemas into our forms and catch those Zod validation errors.
Integrating Zod Schemas in Forms
Alright, now that we have our Zod schemas defined, let's see how we can actually use them in our forms. We'll walk through how to integrate these schemas into a React component, but the concepts can be applied to other frameworks or libraries as well. The key is to use the safeParse
method provided by Zod to validate the input and handle any errors that may occur.
Setting Up the Form Component
First, let's set up a basic form component. We'll use React and some simple form elements. For this example, let’s focus on the signup form, but the same principles apply to sign-in and OTP forms. Here's a basic component structure:
import React, { useState } from "react";
import { signupSchema, SignupSchema } from "./schemas";
const SignupForm: React.FC = () => {
const [formData, setFormData] = useState<SignupSchema>({
name: "",
email: "",
password: "",
confirmPassword: "",
});
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// Validation and submission logic here
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="name">Name:</label>
<input type="text" id="name" name="name" onChange={handleChange} />
</div>
<div>
<label htmlFor="email">Email:</label>
<input type="email" id="email" name="email" onChange={handleChange} />
</div>
<div>
<label htmlFor="password">Password:</label>
<input
type="password"
id="password"
name="password"
onChange={handleChange}
/>
</div>
<div>
<label htmlFor="confirmPassword">Confirm Password:</label>
<input
type="password"
id="confirmPassword"
name="confirmPassword"
onChange={handleChange}
/>
</div>
<button type="submit">Sign Up</button>
</form>
);
};
export default SignupForm;
In this component:
- We import
useState
from React and oursignupSchema
andSignupSchema
type from our schemas file. - We initialize a
formData
state variable using theSignupSchema
type to ensure type safety. handleChange
is a function that updates theformData
state whenever an input field changes.handleSubmit
is the function that will handle the form submission. We'll add the validation logic here.- We have a simple form with input fields for name, email, password, and confirm password.
Implementing Zod Validation
Now, let's add the Zod validation logic to the handleSubmit
function. We'll use the safeParse
method to validate the form data against our signupSchema
.
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const result = signupSchema.safeParse(formData);
if (!result.success) {
// Handle validation errors
console.log(result.error.format());
} else {
// Form is valid, submit data
console.log("Form is valid, submitting data", result.data);
// Here is where you would typically make a call to your backend to submit the form data.
}
};
Here's what's happening:
- We call
signupSchema.safeParse(formData)
to validate the form data.safeParse
is crucial because it doesn't throw an error if validation fails. Instead, it returns an object with asuccess
property. - If
result.success
isfalse
, it means there are validation errors. We can access the errors throughresult.error
. - We use
result.error.format()
to get a formatted object of the errors, which is super helpful for displaying error messages. - If
result.success
istrue
, the form is valid, and we can proceed to submit the data (in this case, we're just logging it to the console).
Displaying Validation Errors
For now, we're just logging the formatted errors to the console. This is a good start, but we want to display these errors to the user in a friendly way using toaster notifications. We'll cover that in the next section. But before we move on, let’s recap what we’ve done:
- We set up a basic form component with input fields for our signup form.
- We implemented Zod validation using
safeParse
in thehandleSubmit
function. - We logged the formatted validation errors to the console.
Next, we'll integrate a toaster notification service to display these errors to the user in a much more user-friendly manner.
Displaying Errors with Toaster Notifications
Now comes the fun part: displaying those Zod validation errors to the user in a way that’s both informative and non-intrusive. We'll use a toaster notification service for this. There are many libraries out there that provide toaster notifications, such as react-toastify
, react-hot-toast
, and sonner
. For this example, let's use react-hot-toast
because it’s lightweight and easy to use. If you are using another framework besides React, choose an appropriate library for your stack.
Setting Up react-hot-toast
First, we need to install react-hot-toast
:
npm install react-hot-toast
Or, if you're using Yarn:
yarn add react-hot-toast
Next, we need to import the Toaster
component in our main application component (e.g., App.tsx
or App.jsx
) to ensure the toaster is available throughout our application. This component will render the notifications.
import React from "react";
import { Toaster } from "react-hot-toast";
import SignupForm from "./components/SignupForm";
const App: React.FC = () => {
return (
<div>
<Toaster />
<SignupForm />
</div>
);
};
export default App;
We've imported the Toaster
component and added it to our App
component. Now, we can use the toast
function to display notifications from anywhere in our application.
Displaying Zod Errors
Now, let's go back to our SignupForm
component and integrate the toaster notifications. We'll import the toast
function from react-hot-toast
and use it to display error messages when Zod validation fails.
Here’s how we can modify our handleSubmit
function:
import React, { useState } from "react";
import { signupSchema, SignupSchema } from "./schemas";
import toast from "react-hot-toast";
const SignupForm: React.FC = () => {
const [formData, setFormData] = useState<SignupSchema>({
name: "",
email: "",
password: "",
confirmPassword: "",
});
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const result = signupSchema.safeParse(formData);
if (!result.success) {
// Display validation errors using toaster
const formattedErrors = result.error.format();
for (const key in formattedErrors) {
if (key !== "_errors" && formattedErrors[key]?._errors) {
formattedErrors[key]?._errors.forEach((error) => {
toast.error(error);
});
}
}
} else {
// Form is valid, submit data
toast.success("Signup successful!");
console.log("Form is valid, submitting data", result.data);
// Here is where you would typically make a call to your backend to submit the form data.
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="name">Name:</label>
<input type="text" id="name" name="name" onChange={handleChange} />
</div>
<div>
<label htmlFor="email">Email:</label>
<input type="email" id="email" name="email" onChange={handleChange} />
</div>
<div>
<label htmlFor="password">Password:</label>
<input
type="password"
id="password"
name="password"
onChange={handleChange}
/>
</div>
<div>
<label htmlFor="confirmPassword">Confirm Password:</label>
<input
type="password"
id="confirmPassword"
name="confirmPassword"
onChange={handleChange}
/>
</div>
<button type="submit">Sign Up</button>
</form>
);
};
export default SignupForm;
Here’s what we’ve changed:
- We import
toast
fromreact-hot-toast
. - Inside the
handleSubmit
function, ifresult.success
isfalse
, we now iterate through the formatted errors and display each error message usingtoast.error(error)
. We are skipping the_errors
key and only displaying errors related to the input fields. - If the form is valid, we display a success message using `toast.success(