Displaying Zod Errors Via Toaster Notifications A Comprehensive Guide

by ADMIN 70 views
Iklan Headers

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 the zod library.
  • We define a signupSchema using z.object(), which means we're expecting an object.
  • name: We use z.string() to ensure the name is a string and .min(2) to require at least 2 characters, with a custom error message.
  • email: We use z.string().email() to validate that the email is in a proper email format, again with a custom error message.
  • password: We use z.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 the password and confirmPassword fields match. If they don't, we add an error message to the confirmPassword field.
  • export type SignupSchema = z.infer<typeof signupSchema>: This line is TypeScript magic! It creates a type SignupSchema 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 use z.string().email() to validate the email format.
  • password: We use z.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 use z.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 our signupSchema and SignupSchema type from our schemas file.
  • We initialize a formData state variable using the SignupSchema type to ensure type safety.
  • handleChange is a function that updates the formData 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 a success property.
  • If result.success is false, it means there are validation errors. We can access the errors through result.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 is true, 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 the handleSubmit 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 from react-hot-toast.
  • Inside the handleSubmit function, if result.success is false, we now iterate through the formatted errors and display each error message using toast.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(