Search code examples
reactjsreact-hook-formzod

Email validation with zod


I have an email input and i want to validate that the user entered a specific email "[email protected]" and if not to show specific error message "This email is not in our database". I am using zod validation to do that, but how can it be done?

const LoginSchema = z.object({
  email: z
    .string()
    .min(1, { message: "This field has to be filled." })
    .email("This is not a valid email.")
  })
});

Solution

  • I can show how to do this, but I don't think this should be done (more later).

    You can use refine to check if the string is exactly some expected value. For example:

    const LoginSchema = z.object({
      email: z
        .string()
        .min(1, { message: "This field has to be filled." })
        .email("This is not a valid email.")
        .refine((e) => e === "[email protected]", "This email is not in our database")
    });
    

    Then, later if you were going to pull down emails so you can write a validation on the frontend you would use an async refinement with parseAsync like:

    const login2 = z.object({
      email: z
        .string()
        .min(1, { message: "This field has to be filled." })
        .email("This is not a valid email.")
        .refine(async (e) => {
          const emails = await fetchEmails();
          return emails.includes(e);
        }, "This email is not in our database")
    });
    

    Opinion Warning

    I would not recommend doing this for 2 reasons:

    1. The number of emails is likely to be very large if you have any meaningful number of users. Pulling all of those down just to make this check would be a pretty big waste of resources and time.
    2. Security wise, sharing emails of all your users publicly over the API strikes me as a dangerous thing to do. Anyone would hit that API to get real email addresses for all of your users.

    I would recommend not validating this as part of the data validation. This validation should happen on the backend and return a 4XX error response when logging in.

    Edit

    A comment on this post mentioned that you could instead provide an API to validate an email address. This could be safely used from an async refinement and avoids the issues described above.

    That would look like:

    const login2 = z.object({
      email: z
        .string()
        .min(1, { message: "This field has to be filled." })
        .email("This is not a valid email.")
        .refine(async (e) => {
          // Where checkIfEmailIsValid makes a request to the backend
          // to see if the email is valid.
          return await checkIfEmailIsValid(e);
        }, "This email is not in our database")
    });