最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Zod's superrefine doesn't update errors dynamically - Stack Overflow

programmeradmin2浏览0评论

I have a form that uses both react-hook-form and Zod for validation. the schema looks something like this:

 const schema = z
.object({
  email: z.string().email().optional(),
  userName: z.string().optional(),
  contactType: z.string(),
})
.superRefine((data, ctx) => {
    if (data.contactType === 'email' && !data.email) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        path: ['email'],
        message: 'email is required',
      });
    }else if {//more fields here}

  });

What usually happens with react-hook-form and Zod, is the following:

  1. You can type what you want, even if it's a mistake, and validation is turned off
  2. The moment you submit the form, validation occurs.
  3. If there are any wrong fields, the error object is filled
  4. the error handling mode changes to "onChange", meaning you're now gonna be notified of every error change, even if you hadn't hit submit

This is a great behavior and I wanna keep it, but the problem is, my error is defined in the "superRefine" method, and when this happens, it no longer updates dynamically (step 4 doesn't work). I only see the error message change when I press submit again.

What I'm trying to achieve here, is to have a field required only if the contactType matches, so if contactType is email, require email, if contactType is userName, require userName. if I submit when email is empty (for example), it tells me "email is required". but once I change contactType to userName, I want to see "userName" is required straight away.

EDIT: I've added "unregister" to the field that I remove, and it correctly shows my errors, but now something else happens when I do the following:

  1. Write something in input, when type set to "email" (for example, write "123")
  2. Change type to username Now, if I look at the form state, email is removed, but the data inside is my correct type, with the old string. (for example, username: "123")

I assume this is because I have one input, with a dynamic register

{...register('user.${idx}.${fieldName}')} (where 'fieldName' is derived from 'watch')

I assume register happens before watch, and then quickly sets the new data to what I have in the input, before deleting the input.

I can create a few inputs that are rendered conditionally, each for the specific 'contactType' but I wonder if there's a better way

I have a form that uses both react-hook-form and Zod for validation. the schema looks something like this:

 const schema = z
.object({
  email: z.string().email().optional(),
  userName: z.string().optional(),
  contactType: z.string(),
})
.superRefine((data, ctx) => {
    if (data.contactType === 'email' && !data.email) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        path: ['email'],
        message: 'email is required',
      });
    }else if {//more fields here}

  });

What usually happens with react-hook-form and Zod, is the following:

  1. You can type what you want, even if it's a mistake, and validation is turned off
  2. The moment you submit the form, validation occurs.
  3. If there are any wrong fields, the error object is filled
  4. the error handling mode changes to "onChange", meaning you're now gonna be notified of every error change, even if you hadn't hit submit

This is a great behavior and I wanna keep it, but the problem is, my error is defined in the "superRefine" method, and when this happens, it no longer updates dynamically (step 4 doesn't work). I only see the error message change when I press submit again.

What I'm trying to achieve here, is to have a field required only if the contactType matches, so if contactType is email, require email, if contactType is userName, require userName. if I submit when email is empty (for example), it tells me "email is required". but once I change contactType to userName, I want to see "userName" is required straight away.

EDIT: I've added "unregister" to the field that I remove, and it correctly shows my errors, but now something else happens when I do the following:

  1. Write something in input, when type set to "email" (for example, write "123")
  2. Change type to username Now, if I look at the form state, email is removed, but the data inside is my correct type, with the old string. (for example, username: "123")

I assume this is because I have one input, with a dynamic register

{...register('user.${idx}.${fieldName}')} (where 'fieldName' is derived from 'watch')

I assume register happens before watch, and then quickly sets the new data to what I have in the input, before deleting the input.

I can create a few inputs that are rendered conditionally, each for the specific 'contactType' but I wonder if there's a better way

Share Improve this question edited Feb 7, 2024 at 17:33 Ido Kadosh asked Feb 6, 2024 at 16:27 Ido KadoshIdo Kadosh 1452 silver badges12 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 5

Discriminated Union:

const contactTypeEmailSchema = z.object({
  email: z.string().email(),
  contactType: z.literal('email'),
})

const contactTypeUserNameSchema = z.object({
  userName: z.string(),
  contactType: z.literal('userName'),
})

const schema = z.discriminatedUnion('contactType', [
    contactTypeEmailSchema,
    contactTypeUserNameSchema
])


No superrefine/custom logic needed, just clean schemas

 const schema = z
  .object({
    email: z.string().email().optional(),
    userName: z.string().optional(),
    contactType: z.string(),
  })
  .refine((data) =>
    data.contactType === 'email' && !data.email) ?
      z.string().nonempty() : z.unknown()
  });
发布评论

评论列表(0)

  1. 暂无评论