From 7a54ead466a1ae5bed54ed702b90e0f12fd0eff8 Mon Sep 17 00:00:00 2001 From: jorisre Date: Fri, 5 Feb 2021 15:33:52 +0100 Subject: [PATCH] perf: reduce yup resolver's size --- package.json | 4 +- yup/package.json | 3 +- yup/src/__tests__/__snapshots__/yup.ts.snap | 15 +--- yup/src/__tests__/yup.ts | 15 ---- yup/src/types.ts | 2 +- yup/src/yup.ts | 88 +++++++-------------- 6 files changed, 35 insertions(+), 92 deletions(-) diff --git a/package.json b/package.json index 49a7e2c7..988cbcaf 100644 --- a/package.json +++ b/package.json @@ -74,10 +74,10 @@ "build": "npm-run-all --parallel build:*", "build:src": "microbundle build", "build:zod": "microbundle --cwd zod --globals '@hookform/resolvers=hookformResolvers'", - "build:yup": "microbundle --cwd yup", + "build:yup": "microbundle --cwd yup --globals '@hookform/resolvers=hookformResolvers'", "build:joi": "microbundle --cwd joi --globals '@hookform/resolvers=hookformResolvers'", "build:superstruct": "microbundle --cwd superstruct --globals '@hookform/resolvers=hookformResolvers'", - "build:vest": "microbundle --cwd vest", + "build:vest": "microbundle --cwd vest --globals '@hookform/resolvers=hookformResolvers'", "postbuild": "node ./config/node-13-exports.js", "lint": "eslint . --ext .ts,.js --ignore-path .gitignore", "lint:types": "tsc", diff --git a/yup/package.json b/yup/package.json index 8f08eb31..ae226de7 100644 --- a/yup/package.json +++ b/yup/package.json @@ -11,6 +11,7 @@ "types": "dist/index.d.ts", "license": "MIT", "peerDependencies": { - "react-hook-form": ">=6.6.0" + "react-hook-form": ">=6.6.0", + "@hookform/resolvers": ">=2.0.0" } } diff --git a/yup/src/__tests__/__snapshots__/yup.ts.snap b/yup/src/__tests__/__snapshots__/yup.ts.snap index 5e8da723..2b44967a 100644 --- a/yup/src/__tests__/__snapshots__/yup.ts.snap +++ b/yup/src/__tests__/__snapshots__/yup.ts.snap @@ -207,23 +207,10 @@ Object { } `; -exports[`yupResolver should return an error result if inner yup validation error has no path 1`] = ` -Object { - "errors": Object { - "required": Object { - "message": "error1", - "ref": undefined, - "type": "required", - }, - }, - "values": Object {}, -} -`; - exports[`yupResolver should return correct error message with using yup.test 1`] = ` Object { "errors": Object { - "name": Object { + "": Object { "message": "Email or name are required", "ref": undefined, "type": "name", diff --git a/yup/src/__tests__/yup.ts b/yup/src/__tests__/yup.ts index 2ec95eb1..a9d713ee 100644 --- a/yup/src/__tests__/yup.ts +++ b/yup/src/__tests__/yup.ts @@ -101,21 +101,6 @@ describe('yupResolver', () => { expect(result).toMatchSnapshot(); }); - it('should return an error result if inner yup validation error has no path', async () => { - const yupSchema = yup.object({ - name: yup.string().required(), - }); - - jest.spyOn(yupSchema, 'validate').mockRejectedValueOnce({ - inner: [{ message: 'error1', type: 'required' }], - }); - - const result = await yupResolver(yupSchema)({ name: '' }, undefined, { - fields, - }); - expect(result).toMatchSnapshot(); - }); - it('should show a warning log if yup context is used instead only on dev environment', async () => { jest.spyOn(console, 'warn').mockImplementation(jest.fn); process.env.NODE_ENV = 'development'; diff --git a/yup/src/types.ts b/yup/src/types.ts index 47975007..854addd9 100644 --- a/yup/src/types.ts +++ b/yup/src/types.ts @@ -11,7 +11,7 @@ type Options = Parameters[1]; export type Resolver = ( schema: T, schemaOptions?: Options, - factoryOptions?: { mode: 'async' | 'sync' }, + factoryOptions?: { mode?: 'async' | 'sync' }, ) => ( values: UnpackNestedValue, context: TContext | undefined, diff --git a/yup/src/yup.ts b/yup/src/yup.ts index b5ebc560..eeb4f2bb 100644 --- a/yup/src/yup.ts +++ b/yup/src/yup.ts @@ -1,95 +1,65 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment */ import Yup from 'yup'; import { toNestError } from '@hookform/resolvers'; +import { appendErrors, FieldError } from 'react-hook-form'; import { Resolver } from './types'; /** - * From 0.32.0, Yup add TypeScript support and `path` typing is optional that's why we have `@ts-expect-error` - * FYI: `path`: a string, indicating where there error was thrown. `path` is empty at the root level. - * react-hook-form's values are object so path is defined + * Why `path!` ? because it could be `undefined` in some case * https://github.com/jquense/yup#validationerrorerrors-string--arraystring-value-any-path-string */ const parseErrorSchema = ( error: Yup.ValidationError, validateAllFieldCriteria: boolean, ) => { - return Array.isArray(error.inner) && error.inner.length - ? error.inner.reduce( - (previous: Record, { path, message, type }) => { - // @ts-expect-error - const previousTypes = (previous[path] && previous[path].types) || {}; - const key = path || type; + return error.inner.reduce>((previous, error) => { + if (!previous[error.path!]) { + previous[error.path!] = { message: error.message, type: error.type! }; + } + + if (validateAllFieldCriteria) { + previous[error.path!] = appendErrors( + error.path!, + validateAllFieldCriteria, + previous, + error.type!, + error.message, + ) as FieldError; + } - return { - ...previous, - ...(key - ? { - [key]: { - ...(previous[key] || { - message, - type, - }), - ...(validateAllFieldCriteria - ? { - types: { - ...previousTypes, - // @ts-expect-error - [type]: previousTypes[type] - ? // @ts-expect-error - [...[].concat(previousTypes[type]), message] - : message, - }, - } - : {}), - }, - } - : {}), - }; - }, - {}, - ) - : { - // @ts-expect-error - [error.path]: { message: error.message, type: error.type }, - }; + return previous; + }, {}); }; export const yupResolver: Resolver = ( schema, - options = { + schemaOptions = { abortEarly: false, }, - { mode } = { mode: 'async' }, -) => async (values, context, { criteriaMode, fields }) => { + resolverOptions = {}, +) => async (values, context, options) => { try { - if (options.context && process.env.NODE_ENV === 'development') { + if (schemaOptions.context && process.env.NODE_ENV === 'development') { // eslint-disable-next-line no-console console.warn( "You should not used the yup options context. Please, use the 'useForm' context object instead", ); } - const result = - mode === 'async' - ? await schema.validate(values, { - ...options, - context, - }) - : schema.validateSync(values, { - ...options, - context, - }); + const result = await schema[ + resolverOptions.mode === 'sync' ? 'validateSync' : 'validate' + ](values, Object.assign(Object.assign({}, schemaOptions), { context })); return { values: result, errors: {}, }; } catch (e) { - const parsedErrors = parseErrorSchema(e, criteriaMode === 'all'); - return { values: {}, - errors: toNestError(parsedErrors, fields), + errors: toNestError( + parseErrorSchema(e, options.criteriaMode === 'all'), + options.fields, + ), }; } };