aiji42 / zod-i18n

Useful for translating zod error messages.
https://zod-i18n.vercel.app
MIT License
669 stars 72 forks source link

Unable to handle dynamic values from Zod Schema (Array) #132

Closed jzamora5 closed 1 year ago

jzamora5 commented 1 year ago

Hi Team.

I am using react-hook-form with a Zod schema in order to validate errors in a React form.

The codebase runs under NextJS and is using next-18next, so I followed this guide to integrate zod-i18n.

https://github.com/aiji42/zod-i18n/tree/main/examples/with-next-i18next

The errors produced by Zod are correct using the i18n config, hovewever I am getting some warnings related to missing keys.

i18next::translator: missingKey en zod

I identified that it refers to the keys used in the Zod schema, and even in the example of the documentation you get the same error, in which you have a schema like.

const schema = z.object({
  nickname: z.string().min(5),  
});

And it complains with i18next::translator: missingKey en zod nickname nickname.

Now, this is easily solvable by addind the corresponding keys to the zod.json. However I am having issues handling values from an array (generated with useFieldArray from react-hook-form), since I have not been able to find a way to correctly interpolate the index for a N length array.

Here is my schema, and I am specifically having problems with ContactMethod which is an array.

z.object({
    [BasicInformationFormNames.RequestSource]: z.string(),   
    [BasicInformationFormNames.ContactName]: z.string().min(1, { message: requiredMsg }).max(30),  
    [BasicInformationFormNames.ContactType]: dropdownSchema,  
    [BasicInformationFormNames.ContactMethodPreferred]: z.string(),  
    [BasicInformationFormNames.ReviewType]: dropdownSchema,  
    [BasicInformationFormNames.Urgency]: dropdownSchema,  
    [BasicInformationFormNames.ContactMethod]: z.array(contactMethodArrayItemSchema).min(1). 
  });

Here is the keys that I added to my zod.json, and most of them are recognized except contactMethod.

{
      "contactName": "contactName",  
      "contactType": "contactType",  
      "reviewType": "reviewType",  
      "urgency": "urgency",  
      "contactMethod": {  
        "{{index}}": { "contactTypeValue": "contactTypeValue" }  
      }  
 }

If instead of {{index}} I use "0", or a proper array, then the corresponding index will be recognized, but since the form generates a N length array then ideally this should recognize any index and not just fixed ones.

As of know this does not break anything in my app and I can work around it, but I would like to know the proper way to handle it. I already tried different interpolation configs, like setting default variables, or trying to use a formatter, but I can't seem to find a solution.

I always get something like

i18next::translator: missingKey en zod zodSchemaKeys.newRequest.contactMethod.0.contactTypeValue contactMethod.0.contactTypeValue

And the same for any number of indexes I have in the array at the time of validating

I appreciate the help.

aiji42 commented 1 year ago

Hello @jzamora5

In order to better understand and investigate the issue you reported, could you please provide a link to a minimal repository that reproduces the problem? This repository should be as simple as possible and include all elements necessary to reproduce the issue.

This will help us to understand the problem more concretely and find an appropriate solution.

Thank you for your cooperation.

jzamora5 commented 1 year ago

Hi @aiji42

Here is the repo of a basic app I created with a fixed input and inputs that can be created dynamically using react-hook-form.

https://github.com/jzamora5/zod-i18n-issue-example

image

image

You will notice that there will be a couple of warnings in the console when trying to submit and adding new contact method inputs.

Those warnings complain about some missing keys, and in the case of the fixed input (personName), you can simply add the key to the zod.json, and the warning will go away.

With the contact method which is dynamic, you can also add the keys, but it only works if you specifically set the number of N elements (indexes) that you have, and if you go beyond the limit index, you will get warnings again. Ideally interpolation should fix this somehow but I did not find a way.

The keys themselves correspond to the zod schema defined for the form, which in this case is:

const contactMethodSchema = z.object({
  method: z.string().min(1),
});

export const schema = z.object({
  personName: z.string().min(1),
  contactMethod: z.array(contactMethodSchema),
});

I hope this makes the issue easier to debug. Thank you

aiji42 commented 1 year ago

@jzamora5

The repository you provided has been helpful for debugging. Thank you.

As of know this does not break anything in my app and I can work around it

As you've mentioned, this warning can be ignored without causing any issues. The warning appears because of the handlePath feature. This feature allows for translating error messages that include the key name when the schema is an object. Even without a proper handlePath setup, it will automatically fallback, so there's no issue with operation (which is why it's fine to ignore the warning).

Currently, this feature cannot be opted out. I initially thought there was no need to make it opt-outable since it automatically falls back to the original error message. However, I overlooked the point that the warning appears when debug mode is enabled. Therefore, I want to release a change that allows for opting out of handlePath as soon as possible. This should eliminate the warning.

On the other hand, there's currently no best practice solution for managing the corresponding data for array data keys. I would like to think about this separately.

For the time being, you can define it like this, but it's clear that this is not the best method:

{
  "contactName": "contactName",  
  "contactType": "contactType",  
  "reviewType": "reviewType",  
  "urgency": "urgency",  
  "contactMethod": [
    { "contactTypeValue": "contactTypeValue" },
    { "contactTypeValue": "contactTypeValue" },
    { "contactTypeValue": "contactTypeValue" },
    // Repeat the definition as many times as necessary
  ]
}
aiji42 commented 1 year ago

I have just released v2.13.0. I actually updated zod-i18n-map in your sample repository and confirmed that the warning no longer appears by opting out of the handlePath as shown below.

z.setErrorMap(makeZodI18nMap({ t, handlePath: false }));
jzamora5 commented 1 year ago

@aiji42 I will test it out. Thank you. One other question though. I was using the handlePath keyPrefix option to organize those zod schema keys in an object inside the zod.json. So instead of having the keys at the root of the json I would define something like

z.setErrorMap(makeZodI18nMap({ t, handlePath: { keyPrefix: 'zodSchemaKeys.newRequest' } })); And then inside the json I could organize them in

zodSchema: {
   newRequest: {
      MY KEYS:
      ...
   }
}

By opting out of the handlePath I suppose this would not work now, so I am wondering if there is still a way to organize them so that the json file is a little more cohesive.

aiji42 commented 1 year ago

If you opt out of handlePath, the key mapping will no longer be referenced and you will not need to define it. So you don't have to think about organizing them.

If you don't opt out, it would be better to use keyPrefix as you are doing, or split ns(namespace) and separate json files that define it.

z.setErrorMap(zodI18nMap({ ns: ["zod", "form"] }));
// form.json
{
  "userName": "User Name",
  ...
}
jzamora5 commented 1 year ago

@aiji42 Thank you. Opting out of the handlePath seems to work for me (to not have warnings). Do you mind explaining to me what is the main purpose of using the key mapping in the first place? I would like to understand if there is a scenario in which I would actually need it. I understand what It does, just not exactly when to use it.

aiji42 commented 1 year ago

For example, you can include the field name in the error message as shown in this captured image.

スクリーンショット 2023-05-30 9 16 28

If handlePath is opted out (or if there is no translation data for the field key), the error here will be String must contain at least 5 characters.

zod.json

"string": {
        "inclusive": "String must contain at least {{minimum}} character(s)",
        "inclusive_one": "String must contain at least {{minimum}} character",
        "inclusive_other": "String must contain at least {{minimum}} characters",
        "inclusive_with_path": "{{path}} must contain at least {{minimum}} character(s)",
        "inclusive_with_path_one": "{{path}} must contain at least {{minimum}} character",
        "inclusive_with_path_other": "{{path}} must contain at least {{minimum}} characters",

common.json

{
  "username": "User name",
  "username_placeholder": "John Doe",
  "email": "Email",
  "favoriteNumber": "Favorite number",
  "submit": "Submit"
}

If the error is displayed directly below the input, it is clear which input is being described. However, if they are displayed in separate places (e.g., several at once under a submit button), the error message must contain the name of the field or the user will be confused.

jzamora5 commented 1 year ago

@aiji42 Got it, thank you. I wonder if there is a way in the future when using handlePath there is a way to pass a variable for interpolation, such as index, so that dynamic values still can be supported somehow. Without setting a fixed number of elements in the json file.