Last active
May 2, 2024 14:50
-
-
Save issam-seghir/81ae88a73a5715ef66f69ffab6a0401c to your computer and use it in GitHub Desktop.
validate a file input with zod
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const MAX_FILE_SIZE = 500000; | |
const ACCEPTED_IMAGE_TYPES = ["image/jpeg", "image/jpg", "image/png", "image/webp"]; | |
const RegistrationSchema = z.object({ | |
profileImage: z | |
.any() | |
.refine((files) => files?.length == 1, "Image is required.") | |
.refine((files) => files?.[0]?.size <= MAX_FILE_SIZE, `Max file size is 5MB.`) | |
.refine( | |
(files) => ACCEPTED_IMAGE_TYPES.includes(files?.[0]?.type), | |
".jpg, .jpeg, .png and .webp files are accepted." | |
), | |
}); | |
// best alternative to.any() for files | |
z.custom<File>((v) => v instanceof File) | |
// or | |
z.instanceof(File), | |
const ACCEPTED_FILE_TYPES = ['application/json'] | |
z.custom<File>(val => val instanceof File, 'Please upload a file') | |
.refine( | |
file => ACCEPTED_FILE_TYPES.includes(file.type), | |
{ message: 'Please choose .json format files only' } | |
), | |
// --------------- Validate arry of files | |
const formSchema= z.object({ | |
image: z | |
.array(z.custom<File>()) | |
.refine( | |
(files) => { | |
// Check if all items in the array are instances of the File object | |
return files.every((file) => file instanceof File); | |
}, | |
{ | |
// If the refinement fails, throw an error with this message | |
message: 'Expected a file', | |
}, | |
) | |
.refine( | |
(files) => files.every((file) => file.size <= MAX_FILE_SIZE), | |
`File size should be less than 2mb.`, | |
) | |
.refine( | |
(files) => files.every((file) => ACCEPTED_IMAGE_TYPES.includes(file.type)), | |
'Only these types are allowed .jpg, .jpeg, .png and .webp', | |
), | |
}) | |
// -------------- another approche ------------------------------- | |
z.custom<FileList>().superRefine((files, ctx) => { | |
if (files.length === 0) { | |
ctx.addIssue({ | |
code: z.ZodIssueCode.custom, | |
message: 'File must be provided', | |
}) | |
return false | |
} | |
if ( | |
!['image/webp', 'image/png', 'image/svg', 'image/jpg', 'image/jpeg'].includes( | |
files[0].type | |
) | |
) { | |
ctx.addIssue({ | |
code: z.ZodIssueCode.custom, | |
message: 'File must be a valid image type', | |
}) | |
return false | |
} | |
if (files[0].size > 1024 * 1024 * 5) { | |
ctx.addIssue({ | |
code: z.ZodIssueCode.custom, | |
message: 'File must be less than 5MB', | |
}) | |
return false | |
} | |
return true | |
}), | |
// another approche | |
const ACCEPTED_IMAGE_TYPES = ["image/png", "image/jpg", "image/jpeg"]; | |
const MAX_IMAGE_SIZE = 4; //In MegaBytes | |
const sizeInMB = (sizeInBytes: number, decimalsNum = 2) => { | |
const result = sizeInBytes / (1024 * 1024); | |
return +result.toFixed(decimalsNum); | |
}; | |
const UserGeneralInfoSchema = z.object({ | |
profileImage: z | |
.custom<FileList>() | |
.refine((files) => { | |
return Array.from(files ?? []).length !== 0; | |
}, "Image is required") | |
.refine((files) => { | |
return Array.from(files ?? []).every( | |
(file) => sizeInMB(file.size) <= MAX_IMAGE_SIZE | |
); | |
}, `The maximum image size is ${MAX_IMAGE_SIZE}MB`) | |
.refine((files) => { | |
return Array.from(files ?? []).every((file) => | |
ACCEPTED_IMAGE_TYPES.includes(file.type) | |
); | |
}, "File type is not supported"), | |
}); | |
// for server is FILE doesnt exist : | |
const formData = await request.formData() | |
const alt = z.string().min(3).parse(formData.get('alt')) | |
const file = z.instanceof(Blob).parse(formData.get('file')) as File | |
console.log(file.name) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment