Skip to content

Commit

Permalink
perf: reduce yup resolver's size (#130)
Browse files Browse the repository at this point in the history
  • Loading branch information
jorisre authored Feb 6, 2021
1 parent 3af7bd7 commit de0b62a
Show file tree
Hide file tree
Showing 6 changed files with 35 additions and 92 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 2 additions & 1 deletion yup/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
15 changes: 1 addition & 14 deletions yup/src/__tests__/__snapshots__/yup.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
15 changes: 0 additions & 15 deletions yup/src/__tests__/yup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
2 changes: 1 addition & 1 deletion yup/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type Options<T extends Yup.AnyObjectSchema> = Parameters<T['validate']>[1];
export type Resolver = <T extends Yup.AnyObjectSchema>(
schema: T,
schemaOptions?: Options<T>,
factoryOptions?: { mode: 'async' | 'sync' },
factoryOptions?: { mode?: 'async' | 'sync' },
) => <TFieldValues extends FieldValues, TContext>(
values: UnpackNestedValue<TFieldValues>,
context: TContext | undefined,
Expand Down
88 changes: 29 additions & 59 deletions yup/src/yup.ts
Original file line number Diff line number Diff line change
@@ -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<string, any>, { path, message, type }) => {
// @ts-expect-error
const previousTypes = (previous[path] && previous[path].types) || {};
const key = path || type;
return error.inner.reduce<Record<string, FieldError>>((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,
),
};
}
};

0 comments on commit de0b62a

Please sign in to comment.