Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading