ui icon indicating copy to clipboard operation
ui copied to clipboard

Forms does not handle Zod array

Open Barbapapazes opened this issue 1 year ago • 3 comments

Environment


  • Operating System: Linux
  • Node Version: v18.20.3
  • Nuxt Version: 3.12.2
  • CLI Version: 3.12.0
  • Nitro Version: 2.9.6
  • Package Manager: [email protected]
  • Builder: -
  • User Config: devtools, modules
  • Runtime Modules: @nuxt/[email protected]
  • Build Modules: -

Version

"@nuxt/ui": "^2.17.0"

Reproduction

https://stackblitz.com/edit/github-gcnkca?file=app.vue

You can start the project, click on the file input, select a file, click on the submit button and check the console.

Description

If you upload a file larger than 200kb or that is not image/png and then submit the form, no error will be displayed. However, if you take a look at the console, you'll see an error related to the file input.

[
  {
    "path": "email",
    "message": "Required",
    "id": "nK7dDJpdOWE_57"
  },
  {
    "path": "password",
    "message": "Required",
    "id": "nK7dDJpdOWE_58"
  },
  {
    "path": "files.2",
    "message": "Max 200kb"
  }
]

Why is this happening?

The problem comes from this line: https://github.com/nuxt/ui/blob/dev/src/runtime/components/forms/Form.vue#L107

You are looking for this:

['files'].includes('files.0')

which is false because the array validate every index.

I expect this to be true in order to show an error message to the user.

Additional context

No response

Logs

No response

Barbapapazes avatar Jun 21 '24 12:06 Barbapapazes

We rely on zod's path attribute to map each errors to a FormGroup using it's name attribute. When validating arrays, zod appends the index to the path of the element, in your case, files.0, which does not match the FormGroup's name.

This is needed in some cases, for instance using objects where you can map a FormGroup to a nested attributes, so we can't really remove it without introducing another bug.

A solution here is to tweak your schema to validate the entire array instead, here's an example: https://stackblitz.com/edit/github-gcnkca-cda6oa?file=app.vue

romhml avatar Jun 21 '24 17:06 romhml

Or we might able to add a prop to the FormGroup with a pattern instead of a string to match errors, I'll explore this solution!

romhml avatar Jun 21 '24 17:06 romhml

Thank you for that tip on Zod appending the index to the path, @romhml !

For anyone else looking to use UForm and Zod on an array of objects in your reactive state, see below. Note the :name="" attribute on the UFormGroup.

const menu = reactive({
  selections: [{ name: '', price: null }],
});
const menuSchema = z.object({
  selections: z
    .array(
      z.object({
        name: z.string().min(5, {
          message: 'Selection name must be at least 5 characters long.',
        }),
        price: z.number({
          message: 'Selection price is required.',
        }),
      })
    )
    .nonempty({
      message: 'Selections are required.',
    }),
});
<div
  v-for="(selection, index) in menu.selections"
  :key="index"
  class="flex items-center gap-2"
>
  <UFormGroup :name="`selections.${index}.name`" class="flex-1">
    <UInput
      v-model="selection.name"
      placeholder="Selection name"
      class="flex-1"
    />
  </UFormGroup>
</div>

brentreilly avatar Jul 28 '24 00:07 brentreilly

This issue is stale because it has been open for 30 days with no activity.

github-actions[bot] avatar Oct 16 '24 02:10 github-actions[bot]