Skip to content

Commit

Permalink
feat: automatically infer values from schema (#739)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
jorisre authored Feb 10, 2025
1 parent f9f9187 commit caaff8d
Show file tree
Hide file tree
Showing 83 changed files with 2,920 additions and 537 deletions.
33 changes: 31 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,37 @@
## Install

Install your preferred validation library alongside `@hookform/resolvers`.

npm install @hookform/resolvers

npm install @hookform/resolvers # npm
yarn add @hookform/resolvers # yarn
pnpm install @hookform/resolvers # pnpm
bun install @hookform/resolvers # bun

<details>
<summary>Resolver Comparison</summary>

| resolver | Infer values <br /> from schema | [criteriaMode](https://react-hook-form.com/docs/useform#criteriaMode) |
|---|---|---|
| AJV || `firstError | all` |
| Arktype || `firstError` |
| class-validator || `firstError | all` |
| computed-types || `firstError` |
| Effect || `firstError | all` |
| fluentvalidation-ts || `firstError` |
| io-ts || `firstError` |
| joi || `firstError | all` |
| Nope || `firstError` |
| Standard Schema || `firstError | all` |
| Superstruct || `firstError` |
| typanion || `firstError` |
| typebox || `firstError | all` |
| typeschema || `firstError | all` |
| valibot || `firstError | all` |
| vest || `firstError | all` |
| vine || `firstError | all` |
| yup || `firstError | all` |
| zod || `firstError | all` |
</details>

## Links

Expand Down
20 changes: 20 additions & 0 deletions ajv/src/ajv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,26 @@ const parseErrorSchema = (
return parsedErrors;
};

/**
* Creates a resolver for react-hook-form using Ajv schema validation
* @param {Schema} schema - The Ajv schema to validate against
* @param {Object} schemaOptions - Additional schema validation options
* @param {Object} resolverOptions - Additional resolver configuration
* @param {string} [resolverOptions.mode='async'] - Validation mode
* @returns {Resolver<Schema>} A resolver function compatible with react-hook-form
* @example
* const schema = ajv.compile({
* type: 'object',
* properties: {
* name: { type: 'string' },
* age: { type: 'number' }
* }
* });
*
* useForm({
* resolver: ajvResolver(schema)
* });
*/
export const ajvResolver: Resolver =
(schema, schemaOptions, resolverOptions = {}) =>
async (values, _, options) => {
Expand Down
38 changes: 32 additions & 6 deletions arktype/src/__tests__/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ const schema = type({

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

interface Props {
onSubmit: (data: FormData) => void;
}

function TestComponent({ onSubmit }: Props) {
function TestComponent({
onSubmit,
}: {
onSubmit: (data: typeof schema.infer) => void;
}) {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormData>({
} = useForm({
resolver: arktypeResolver(schema), // Useful to check TypeScript regressions
});

Expand Down Expand Up @@ -54,3 +54,29 @@ test("form's validation with arkType and TypeScript's integration", async () =>
).toBeInTheDocument();
expect(handleSubmit).not.toHaveBeenCalled();
});

export function TestComponentManualType({
onSubmit,
}: {
onSubmit: (data: FormData) => void;
}) {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<typeof schema.infer, undefined, FormData>({
resolver: arktypeResolver(schema), // Useful to check TypeScript regressions
});

return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('username')} />
{errors.username && <span role="alert">{errors.username.message}</span>}

<input {...register('password')} />
{errors.password && <span role="alert">{errors.password.message}</span>}

<button type="submit">submit</button>
</form>
);
}
2 changes: 1 addition & 1 deletion arktype/src/__tests__/__fixtures__/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const invalidData = {
birthYear: 'birthYear',
like: [{ id: 'z' }],
url: 'abc',
};
} as any as typeof schema.infer;

export const fields: Record<InternalFieldName, Field['_f']> = {
username: {
Expand Down
38 changes: 29 additions & 9 deletions arktype/src/arktype.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers';
import { ArkErrors } from 'arktype';
import { FieldError, FieldErrors } from 'react-hook-form';
import type { Resolver } from './types';
import { ArkErrors, Type } from 'arktype';
import { FieldError, FieldErrors, Resolver } from 'react-hook-form';

const parseErrorSchema = (arkErrors: ArkErrors): Record<string, FieldError> => {
function parseErrorSchema(arkErrors: ArkErrors): Record<string, FieldError> {
const errors = [...arkErrors];
const fieldsErrors: Record<string, FieldError> = {};

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

return fieldsErrors;
};

export const arktypeResolver: Resolver =
(schema, _schemaOptions, resolverOptions = {}) =>
(values, _, options) => {
}

/**
* Creates a resolver for react-hook-form using Arktype schema validation
* @param {Schema} schema - The Arktype schema to validate against
* @param {Object} resolverOptions - Additional resolver configuration
* @param {string} [resolverOptions.mode='raw'] - Return the raw input values rather than the parsed values
* @returns {Resolver<Schema['inferOut']>} A resolver function compatible with react-hook-form
* @example
* const schema = type({
* username: 'string>2'
* });
*
* useForm({
* resolver: arktypeResolver(schema)
* });
*/
export function arktypeResolver<Schema extends Type<any, any>>(
schema: Schema,
_schemaOptions?: never,
resolverOptions: {
raw?: boolean;
} = {},
): Resolver<Schema['inferOut']> {
return (values, _, options) => {
const out = schema(values);

if (out instanceof ArkErrors) {
Expand All @@ -40,3 +59,4 @@ export const arktypeResolver: Resolver =
values: resolverOptions.raw ? Object.assign({}, values) : out,
};
};
}
1 change: 0 additions & 1 deletion arktype/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export * from './arktype';
export * from './types';
18 changes: 0 additions & 18 deletions arktype/src/types.ts

This file was deleted.

Loading

0 comments on commit caaff8d

Please sign in to comment.