UNPKG

@hookform/resolvers

Version:

React Hook Form validation resolvers: Yup, Joi, Superstruct, Zod, Vest, Class Validator, io-ts, Nope, computed-types, TypeBox, arktype, Typanion, Effect-TS and VineJS

142 lines (126 loc) 4.15 kB
import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers'; import { FieldError, FieldErrors, FieldValues, Resolver, ResolverError, ResolverSuccess, appendErrors, } from 'react-hook-form'; import { ZodError, z } from 'zod'; const isZodError = (error: any): error is ZodError => Array.isArray(error?.errors); function parseErrorSchema( zodErrors: z.ZodIssue[], validateAllFieldCriteria: boolean, ) { const errors: Record<string, FieldError> = {}; for (; zodErrors.length; ) { const error = zodErrors[0]; const { code, message, path } = error; const _path = path.join('.'); if (!errors[_path]) { if ('unionErrors' in error) { const unionError = error.unionErrors[0].errors[0]; errors[_path] = { message: unionError.message, type: unionError.code, }; } else { errors[_path] = { message, type: code }; } } if ('unionErrors' in error) { error.unionErrors.forEach((unionError) => unionError.errors.forEach((e) => zodErrors.push(e)), ); } if (validateAllFieldCriteria) { const types = errors[_path].types; const messages = types && types[error.code]; errors[_path] = appendErrors( _path, validateAllFieldCriteria, errors, code, messages ? ([] as string[]).concat(messages as string[], error.message) : error.message, ) as FieldError; } zodErrors.shift(); } return errors; } export function zodResolver<Input extends FieldValues, Context, Output>( schema: z.ZodSchema<Output, any, Input>, schemaOptions?: Partial<z.ParseParams>, resolverOptions?: { mode?: 'async' | 'sync'; raw?: false; }, ): Resolver<Input, Context, Output>; export function zodResolver<Input extends FieldValues, Context, Output>( schema: z.ZodSchema<Output, any, Input>, schemaOptions: Partial<z.ParseParams> | undefined, resolverOptions: { mode?: 'async' | 'sync'; raw: true; }, ): Resolver<Input, Context, Input>; /** * Creates a resolver function for react-hook-form that validates form data using a Zod schema * @param {z.ZodSchema<Input>} schema - The Zod schema used to validate the form data * @param {Partial<z.ParseParams>} [schemaOptions] - Optional configuration options for Zod parsing * @param {Object} [resolverOptions] - Optional resolver-specific configuration * @param {('async'|'sync')} [resolverOptions.mode='async'] - Validation mode. Use 'sync' for synchronous validation * @param {boolean} [resolverOptions.raw=false] - If true, returns the raw form values instead of the parsed data * @returns {Resolver<z.output<typeof schema>>} A resolver function compatible with react-hook-form * @throws {Error} Throws if validation fails with a non-Zod error * @example * const schema = z.object({ * name: z.string().min(2), * age: z.number().min(18) * }); * * useForm({ * resolver: zodResolver(schema) * }); */ export function zodResolver<Input extends FieldValues, Context, Output>( schema: z.ZodSchema<Output, any, Input>, schemaOptions?: Partial<z.ParseParams>, resolverOptions: { mode?: 'async' | 'sync'; raw?: boolean; } = {}, ): Resolver<Input, Context, Output | Input> { return async (values: Input, _, options) => { try { const data = await schema[ resolverOptions.mode === 'sync' ? 'parse' : 'parseAsync' ](values, schemaOptions); options.shouldUseNativeValidation && validateFieldsNatively({}, options); return { errors: {} as FieldErrors, values: resolverOptions.raw ? Object.assign({}, values) : data, } satisfies ResolverSuccess<Output | Input>; } catch (error) { if (isZodError(error)) { return { values: {}, errors: toNestErrors( parseErrorSchema( error.errors, !options.shouldUseNativeValidation && options.criteriaMode === 'all', ), options, ), } satisfies ResolverError<Input>; } throw error; } }; }