Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UInput with type='file', how to validate with Zod? #2462

Open
pieterjanmaes opened this issue Oct 25, 2024 · 6 comments
Open

UInput with type='file', how to validate with Zod? #2462

pieterjanmaes opened this issue Oct 25, 2024 · 6 comments
Labels
question Further information is requested

Comments

@pieterjanmaes
Copy link

pieterjanmaes commented Oct 25, 2024

For what version of Nuxt UI are you asking this question?

v2.x

Description

How can i use Zod to validate an UInput file field?

Let's say you have the following code:
(I found the example over here.)

<UForm :schema="schema" :state="state" @submit="onSubmit">

  <UFormGroup name="picture" label="Picture">
    <UInput v-model="state.picture" type="file" @change="onChangeFile"/>
  </UFormGroup>

  <UButton type="submit">
    Submit
  </UButton>

</UForm>
<script setup lang="ts">
  import { z } from 'zod';

  const state = reactive({
    picture: undefined,
  })

  const schema = z.object({
    picture: z.custom<FileList>()
      .transform((val) => {
        if (val instanceof File) return val;
        if (val instanceof FileList) return val[0];
        return null;
      })
      .superRefine((file, ctx) => {
        if (!(file instanceof File)) {
          ctx.addIssue({
            code: z.ZodIssueCode.custom,
            fatal: true,
            message: 'Not a file',
          });
          return z.NEVER;
        }
        if (file.size > 5 * 1024 * 1024) {
          ctx.addIssue({
            code: z.ZodIssueCode.custom,
            message: 'Max file size allowed is 5MB',
          });
        }
        if (
          !['image/jpeg', 'image/png', 'image/webp', 'image/jpg'].includes(
            file.type
          )
        ) {
          ctx.addIssue({
            code: z.ZodIssueCode.custom,
            message: 'File must be an image (jpeg, jpg, png, webp)',
          });
        }
      })
  });

  type Schema = z.infer<typeof schema>;

  async function onSubmit (event: FormSubmitEvent<Schema>) {
    console.log(event.data);
  }
</script>

As aspected the following onChange function logs a FileList

function onChangeFile(event: Event) {
  console.log(event)
}

But when I log val in the transform function of Zod

.transform((val) => {
  console.log(val);
  if (val instanceof File) return val;
  if (val instanceof FileList) return val[0];
  return null;
})

I get a string like this:

C:\fakepath\Screenshot 2024-10-25 at 10.26.36.png

Can someone please help me out, i'm looking for a few days now for a solution.

@pieterjanmaes pieterjanmaes added the question Further information is requested label Oct 25, 2024
@pieterjanmaes pieterjanmaes changed the title UInput with type='file', how to validate with Zod UInput with type='file', how to validate with Zod? Oct 25, 2024
@noook
Copy link
Collaborator

noook commented Oct 25, 2024

Try to use Zod's z.instanceof. z.custom seems to be okay for object literals or string template/literals.

I think in your situation you'd need to do something like this:

z.object({
    picture: z.union([z.instanceof(File), z.instanceof(FileList)])
      .transform((val) => {
        if (val instanceof File) return val;
        if (val instanceof FileList) return val[0];
        return null;
      })
})

@pieterjanmaes
Copy link
Author

Hi @noook, thanks for your reply.

When I try it, my app won't build and i get an 500 error:

FileList is not defined

When I use File alone and I select an image, i get:

Input not instance of File

z.object({
    picture: z.instanceof(File)
      .transform((val) => {
        if (val instanceof File) return val;
        if (val instanceof FileList) return val[0];
        return null;
      })
})
  • I'm using:
    node: 22
    Nuxt: 3.13.2
    @nuxt/ui: 2.18.7
    zod: 3.23.8

@pieterjanmaes
Copy link
Author

And when i make the component 'clientOnly' i get Invalid input as error

@noook
Copy link
Collaborator

noook commented Oct 26, 2024

Can you try to make a reproducible example so that I can play with it ?

@pieterjanmaes
Copy link
Author

@noook
Copy link
Collaborator

noook commented Oct 26, 2024

I tried with this example:

https://stackblitz.com/edit/nuxt-starter-adyxmi?file=components%2FMyForm.client.vue

Basically what I did:

  1. Move logic into .client.vue file. This allows mentioning the FileList class. I think you can bypass this by doing import.meta.client ? z.instanceof(FileList) : z.any(). This shouldn't try to reach for an undefined class server-side
  2. Remove instanceof tests, your type should always be a FileList
  3. Remove binding on the input, it is not bound in the component if the type is "file". Manually assign with the @change event

However, I can't figure out why the transform is not working, it still outputs me a FileList on the Zod's parse output.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants