Skip to content

Commit caaff8d

Browse files
authored
feat: automatically infer values from schema (#739)
* fix(zodResolver): infer from schema * chore: update letfhook config * fix(vineResolver): infer from schema * fix(valibotResolver): infer from schema * refactor(zod): remove arrow functions * feat(typebox): automatically infer values from schema * feat(typanion): automatically infer values from schema * refactor: remove useless files * feat(superstruct): automatically infer values from schema * feat(io-ts): automatically infer values from schema * refactor: remove arrow functions * feat(effect): automatically infer values from schema * feat(computed-types): automatically infer values from schema * feat(class-validator): automatically infer values from schema * feat(arktype): automatically infer values from schema * feat(standard-schema): automatically infer values from schema * doc: add resolver comparison * doc: add jsdoc
1 parent f9f9187 commit caaff8d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+2920
-537
lines changed

README.md

+31-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,37 @@
1919
## Install
2020

2121
Install your preferred validation library alongside `@hookform/resolvers`.
22-
23-
npm install @hookform/resolvers
22+
23+
npm install @hookform/resolvers # npm
24+
yarn add @hookform/resolvers # yarn
25+
pnpm install @hookform/resolvers # pnpm
26+
bun install @hookform/resolvers # bun
27+
28+
<details>
29+
<summary>Resolver Comparison</summary>
30+
31+
| resolver | Infer values <br /> from schema | [criteriaMode](https://react-hook-form.com/docs/useform#criteriaMode) |
32+
|---|---|---|
33+
| AJV || `firstError | all` |
34+
| Arktype || `firstError` |
35+
| class-validator || `firstError | all` |
36+
| computed-types || `firstError` |
37+
| Effect || `firstError | all` |
38+
| fluentvalidation-ts || `firstError` |
39+
| io-ts || `firstError` |
40+
| joi || `firstError | all` |
41+
| Nope || `firstError` |
42+
| Standard Schema || `firstError | all` |
43+
| Superstruct || `firstError` |
44+
| typanion || `firstError` |
45+
| typebox || `firstError | all` |
46+
| typeschema || `firstError | all` |
47+
| valibot || `firstError | all` |
48+
| vest || `firstError | all` |
49+
| vine || `firstError | all` |
50+
| yup || `firstError | all` |
51+
| zod || `firstError | all` |
52+
</details>
2453

2554
## Links
2655

ajv/src/ajv.ts

+20
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,26 @@ const parseErrorSchema = (
5858
return parsedErrors;
5959
};
6060

61+
/**
62+
* Creates a resolver for react-hook-form using Ajv schema validation
63+
* @param {Schema} schema - The Ajv schema to validate against
64+
* @param {Object} schemaOptions - Additional schema validation options
65+
* @param {Object} resolverOptions - Additional resolver configuration
66+
* @param {string} [resolverOptions.mode='async'] - Validation mode
67+
* @returns {Resolver<Schema>} A resolver function compatible with react-hook-form
68+
* @example
69+
* const schema = ajv.compile({
70+
* type: 'object',
71+
* properties: {
72+
* name: { type: 'string' },
73+
* age: { type: 'number' }
74+
* }
75+
* });
76+
*
77+
* useForm({
78+
* resolver: ajvResolver(schema)
79+
* });
80+
*/
6181
export const ajvResolver: Resolver =
6282
(schema, schemaOptions, resolverOptions = {}) =>
6383
async (values, _, options) => {

arktype/src/__tests__/Form.tsx

+32-6
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,16 @@ const schema = type({
1212

1313
type FormData = typeof schema.infer & { unusedProperty: string };
1414

15-
interface Props {
16-
onSubmit: (data: FormData) => void;
17-
}
18-
19-
function TestComponent({ onSubmit }: Props) {
15+
function TestComponent({
16+
onSubmit,
17+
}: {
18+
onSubmit: (data: typeof schema.infer) => void;
19+
}) {
2020
const {
2121
register,
2222
handleSubmit,
2323
formState: { errors },
24-
} = useForm<FormData>({
24+
} = useForm({
2525
resolver: arktypeResolver(schema), // Useful to check TypeScript regressions
2626
});
2727

@@ -54,3 +54,29 @@ test("form's validation with arkType and TypeScript's integration", async () =>
5454
).toBeInTheDocument();
5555
expect(handleSubmit).not.toHaveBeenCalled();
5656
});
57+
58+
export function TestComponentManualType({
59+
onSubmit,
60+
}: {
61+
onSubmit: (data: FormData) => void;
62+
}) {
63+
const {
64+
register,
65+
handleSubmit,
66+
formState: { errors },
67+
} = useForm<typeof schema.infer, undefined, FormData>({
68+
resolver: arktypeResolver(schema), // Useful to check TypeScript regressions
69+
});
70+
71+
return (
72+
<form onSubmit={handleSubmit(onSubmit)}>
73+
<input {...register('username')} />
74+
{errors.username && <span role="alert">{errors.username.message}</span>}
75+
76+
<input {...register('password')} />
77+
{errors.password && <span role="alert">{errors.password.message}</span>}
78+
79+
<button type="submit">submit</button>
80+
</form>
81+
);
82+
}

arktype/src/__tests__/__fixtures__/data.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export const invalidData = {
4343
birthYear: 'birthYear',
4444
like: [{ id: 'z' }],
4545
url: 'abc',
46-
};
46+
} as any as typeof schema.infer;
4747

4848
export const fields: Record<InternalFieldName, Field['_f']> = {
4949
username: {

arktype/src/arktype.ts

+29-9
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers';
2-
import { ArkErrors } from 'arktype';
3-
import { FieldError, FieldErrors } from 'react-hook-form';
4-
import type { Resolver } from './types';
2+
import { ArkErrors, Type } from 'arktype';
3+
import { FieldError, FieldErrors, Resolver } from 'react-hook-form';
54

6-
const parseErrorSchema = (arkErrors: ArkErrors): Record<string, FieldError> => {
5+
function parseErrorSchema(arkErrors: ArkErrors): Record<string, FieldError> {
76
const errors = [...arkErrors];
87
const fieldsErrors: Record<string, FieldError> = {};
98

@@ -19,11 +18,31 @@ const parseErrorSchema = (arkErrors: ArkErrors): Record<string, FieldError> => {
1918
}
2019

2120
return fieldsErrors;
22-
};
23-
24-
export const arktypeResolver: Resolver =
25-
(schema, _schemaOptions, resolverOptions = {}) =>
26-
(values, _, options) => {
21+
}
22+
23+
/**
24+
* Creates a resolver for react-hook-form using Arktype schema validation
25+
* @param {Schema} schema - The Arktype schema to validate against
26+
* @param {Object} resolverOptions - Additional resolver configuration
27+
* @param {string} [resolverOptions.mode='raw'] - Return the raw input values rather than the parsed values
28+
* @returns {Resolver<Schema['inferOut']>} A resolver function compatible with react-hook-form
29+
* @example
30+
* const schema = type({
31+
* username: 'string>2'
32+
* });
33+
*
34+
* useForm({
35+
* resolver: arktypeResolver(schema)
36+
* });
37+
*/
38+
export function arktypeResolver<Schema extends Type<any, any>>(
39+
schema: Schema,
40+
_schemaOptions?: never,
41+
resolverOptions: {
42+
raw?: boolean;
43+
} = {},
44+
): Resolver<Schema['inferOut']> {
45+
return (values, _, options) => {
2746
const out = schema(values);
2847

2948
if (out instanceof ArkErrors) {
@@ -40,3 +59,4 @@ export const arktypeResolver: Resolver =
4059
values: resolverOptions.raw ? Object.assign({}, values) : out,
4160
};
4261
};
62+
}

arktype/src/index.ts

-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
export * from './arktype';
2-
export * from './types';

arktype/src/types.ts

-18
This file was deleted.

0 commit comments

Comments
 (0)