Forms does not handle Zod array
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
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
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!
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>
This issue is stale because it has been open for 30 days with no activity.