Skip to content

Commit

Permalink
fix: typings with react-hook-form@7.15.0 (#233)
Browse files Browse the repository at this point in the history
* Fix yupResolver typings

* Fix other resolvers typings

* Upgrade `react-hook-form`

* Fix typo

* Infer field values from zod schema

* Infer field values from io-ts decoder

* Infer field values from yup schema

* Infer field values from typanion validator

* Infer field values from superstruct struct

* Fix context type

* Add nested value
  • Loading branch information
julienfouilhe authored Sep 18, 2021
1 parent 8e7e789 commit 10ba224
Show file tree
Hide file tree
Showing 27 changed files with 125 additions and 132 deletions.
2 changes: 1 addition & 1 deletion class-validator/src/__tests__/Form-native-validation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class Schema {
}

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

function TestComponent({ onSubmit }: Props) {
Expand Down
2 changes: 1 addition & 1 deletion class-validator/src/__tests__/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class Schema {
}

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

function TestComponent({ onSubmit }: Props) {
Expand Down
8 changes: 6 additions & 2 deletions class-validator/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ import {
import { ValidatorOptions } from 'class-validator';
import { ClassConstructor } from 'class-transformer';

export type Resolver = <T extends { [_: string]: any }>(
export type Resolver = <
T extends { [_: string]: any },
TFieldValues extends FieldValues,
TContext,
>(
schema: ClassConstructor<T>,
schemaOptions?: ValidatorOptions,
resolverOptions?: { mode?: 'async' | 'sync' },
) => <TFieldValues extends FieldValues, TContext>(
) => (
values: UnpackNestedValue<TFieldValues>,
context: TContext | undefined,
options: ResolverOptions<TFieldValues>,
Expand Down
10 changes: 5 additions & 5 deletions io-ts/src/__tests__/Form-native-validation.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { render, screen, act } from '@testing-library/react';
import user from '@testing-library/user-event';
import { useForm } from 'react-hook-form';
import { SubmitHandler, useForm } from 'react-hook-form';
import * as t from 'io-ts';
import * as tt from 'io-ts-types';
import { ioTsResolver } from '..';
Expand All @@ -15,16 +15,16 @@ const schema = t.type({
});

interface FormData {
username: string;
password: string;
username: tt.NonEmptyString;
password: tt.NonEmptyString;
}

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

function TestComponent({ onSubmit }: Props) {
const { register, handleSubmit } = useForm<FormData>({
const { register, handleSubmit } = useForm({
resolver: ioTsResolver(schema),
shouldUseNativeValidation: true,
});
Expand Down
10 changes: 5 additions & 5 deletions io-ts/src/__tests__/Form.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { render, screen, act } from '@testing-library/react';
import user from '@testing-library/user-event';
import { useForm } from 'react-hook-form';
import { NestedValue, SubmitHandler, useForm } from 'react-hook-form';
import * as t from 'io-ts';
import * as tt from 'io-ts-types';
import { ioTsResolver } from '..';
Expand All @@ -18,12 +18,12 @@ const schema = t.type({
});

interface FormData {
username: string;
password: string;
username: NestedValue<tt.NonEmptyString>;
password: NestedValue<tt.NonEmptyString>;
}

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

function TestComponent({ onSubmit }: Props) {
Expand All @@ -32,7 +32,7 @@ function TestComponent({ onSubmit }: Props) {
formState: { errors },
handleSubmit,
} = useForm<FormData>({
resolver: ioTsResolver(schema),
resolver: ioTsResolver<FormData>(schema),
criteriaMode: 'all',
});

Expand Down
3 changes: 2 additions & 1 deletion io-ts/src/io-ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { pipe } from 'fp-ts/function';
import { toNestError, validateFieldsNatively } from '@hookform/resolvers';
import errorsToRecord from './errorsToRecord';
import { Resolver } from './types';
import { FieldErrors } from 'react-hook-form';

export const ioTsResolver: Resolver = (codec) => (values, _context, options) =>
pipe(
Expand All @@ -25,7 +26,7 @@ export const ioTsResolver: Resolver = (codec) => (values, _context, options) =>

return {
values,
errors: {},
errors: {} as FieldErrors<any>,
};
},
),
Expand Down
12 changes: 8 additions & 4 deletions io-ts/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
/* eslint-disable @typescript-eslint/ban-types */
import * as t from 'io-ts';
import {
FieldError,
FieldValues,
ResolverOptions,
ResolverResult,
UnpackNestedValue,
} from 'react-hook-form';

export type Resolver = <T, TFieldValues, TContext>(
codec: t.Decoder<FieldValues, T>,
export type Resolver = <
TFieldValues,
TInput extends unknown = unknown,
TContext extends object = object,
>(
codec: t.Decoder<TInput, UnpackNestedValue<TFieldValues>>,
) => (
values: UnpackNestedValue<TFieldValues>,
values: TInput,
_context: TContext | undefined,
options: ResolverOptions<TFieldValues>,
) => ResolverResult<TFieldValues>;
Expand Down
5 changes: 2 additions & 3 deletions joi/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import {
FieldValues,
ResolverOptions,
ResolverResult,
UnpackNestedValue,
} from 'react-hook-form';
import type { AsyncValidationOptions, Schema } from 'joi';

export type Resolver = <T extends Schema>(
export type Resolver = <T extends Schema, TFieldValues, TContext>(
schema: T,
schemaOptions?: AsyncValidationOptions,
factoryOptions?: { mode?: 'async' | 'sync' },
) => <TFieldValues extends FieldValues, TContext>(
) => (
values: UnpackNestedValue<TFieldValues>,
context: TContext | undefined,
options: ResolverOptions<TFieldValues>,
Expand Down
9 changes: 6 additions & 3 deletions nope/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type {
FieldValues,
ResolverOptions,
ResolverResult,
UnpackNestedValue,
Expand All @@ -9,10 +8,14 @@ import type { NopeObject } from 'nope-validator/lib/cjs/NopeObject';
type ValidateOptions = Parameters<NopeObject['validate']>[2];
type Context = Parameters<NopeObject['validate']>[1];

export type Resolver = <T extends NopeObject>(
export type Resolver = <
T extends NopeObject,
TFieldValues,
TContext extends Context,
>(
schema: T,
schemaOptions?: ValidateOptions,
) => <TFieldValues extends FieldValues, TContext extends Context>(
) => (
values: UnpackNestedValue<TFieldValues>,
context: TContext | undefined,
options: ResolverOptions<TFieldValues>,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@
"prettier": "^2.3.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-hook-form": "7.12.2",
"react-hook-form": "7.15.0",
"reflect-metadata": "^0.1.13",
"superstruct": "^0.15.2",
"ts-jest": "^27.0.4",
Expand Down
5 changes: 3 additions & 2 deletions src/toNestError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ import {
FieldErrors,
Field,
ResolverOptions,
FieldValues,
} from 'react-hook-form';
import { validateFieldsNatively } from './validateFieldsNatively';

export const toNestError = <TFieldValues>(
export const toNestError = <TFieldValues extends FieldValues>(
errors: Record<string, FieldError>,
options: ResolverOptions<TFieldValues>,
): FieldErrors<TFieldValues> => {
options.shouldUseNativeValidation && validateFieldsNatively(errors, options);

const fieldErrors: FieldErrors<TFieldValues> = {};
const fieldErrors = {} as FieldErrors<TFieldValues>;
for (const path in errors) {
const field = get(options.fields, path) as Field['_f'] | undefined;

Expand Down
2 changes: 1 addition & 1 deletion superstruct/src/__tests__/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function TestComponent({ onSubmit }: Props) {
register,
formState: { errors },
handleSubmit,
} = useForm<FormData>({
} = useForm({
resolver: superstructResolver(schema), // Useful to check TypeScript regressions
});

Expand Down
9 changes: 6 additions & 3 deletions superstruct/src/superstruct.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FieldError } from 'react-hook-form';
import { FieldError, FieldErrors } from 'react-hook-form';
import { toNestError, validateFieldsNatively } from '@hookform/resolvers';

import { StructError, validate } from 'superstruct';
Expand All @@ -21,14 +21,17 @@ export const superstructResolver: Resolver =
if (result[0]) {
return {
values: {},
errors: toNestError(parseErrorSchema(result[0]), options),
errors: toNestError(
parseErrorSchema(result[0]),
options,
) as FieldErrors<any>,
};
}

options.shouldUseNativeValidation && validateFieldsNatively({}, options);

return {
values: result[1],
errors: {},
errors: {} as FieldErrors<any>,
};
};
21 changes: 10 additions & 11 deletions superstruct/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import {
FieldValues,
ResolverOptions,
ResolverResult,
UnpackNestedValue,
} from 'react-hook-form';
/* eslint-disable @typescript-eslint/ban-types */
import { FieldValues, ResolverOptions, ResolverResult } from 'react-hook-form';
import { validate, Struct } from 'superstruct';

type Options = Parameters<typeof validate>[2];

export type Resolver = <T extends Struct<any, any>>(
schema: T,
factoryOtions?: Options,
) => <TFieldValues extends FieldValues, TContext>(
values: UnpackNestedValue<TFieldValues>,
export type Resolver = <
TFieldValues extends FieldValues,
TContext extends object = object,
>(
schema: Struct<TFieldValues, any>,
factoryOptions?: Options,
) => (
values: unknown,
context: TContext | undefined,
options: ResolverOptions<TFieldValues>,
) => ResolverResult<TFieldValues>;
5 changes: 3 additions & 2 deletions typanion/src/__tests__/Form-native-validation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { useForm } from 'react-hook-form';
import * as t from 'typanion';
import { typanionResolver } from '..';

const ERROR_MESSAGE = 'Expected to have a length of at least 1 elements (got 0)';
const ERROR_MESSAGE =
'Expected to have a length of at least 1 elements (got 0)';

const schema = t.isObject({
username: t.applyCascade(t.isString(), [t.hasMinLength(1)]),
Expand All @@ -23,7 +24,7 @@ interface Props {
}

function TestComponent({ onSubmit }: Props) {
const { register, handleSubmit } = useForm<FormData>({
const { register, handleSubmit } = useForm({
resolver: typanionResolver(schema),
shouldUseNativeValidation: true,
});
Expand Down
8 changes: 6 additions & 2 deletions typanion/src/__tests__/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function TestComponent({ onSubmit }: Props) {
register,
formState: { errors },
handleSubmit,
} = useForm<FormData>({
} = useForm({
resolver: typanionResolver(schema), // Useful to check TypeScript regressions
});

Expand All @@ -52,6 +52,10 @@ test("form's validation with Typanion and TypeScript's integration", async () =>
user.click(screen.getByText(/submit/i));
});

expect(screen.getAllByText('Expected to have a length of at least 1 elements (got 0)')).toHaveLength(2);
expect(
screen.getAllByText(
'Expected to have a length of at least 1 elements (got 0)',
),
).toHaveLength(2);
expect(handleSubmit).not.toHaveBeenCalled();
});
7 changes: 5 additions & 2 deletions typanion/src/typanion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,11 @@ export const typanionResolver: Resolver =
options.shouldUseNativeValidation &&
validateFieldsNatively(parsedErrors, options);

return { values, errors: {} };
return { values, errors: {} as FieldErrors<any> };
}

return { values: {}, errors: toNestError(parsedErrors, options) };
return {
values: {},
errors: toNestError(parsedErrors, options) as FieldErrors<any>,
};
};
24 changes: 16 additions & 8 deletions typanion/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
/* eslint-disable @typescript-eslint/ban-types */
import type {
FieldValues,
ResolverOptions,
ResolverResult,
UnpackNestedValue,
} from 'react-hook-form';
import { ValidationState, AnyStrictValidator} from 'typanion'
import { ValidationState, StrictValidator } from 'typanion';

type ValidateOptions = Pick<ValidationState, 'coercions' | 'coercion'>
type ValidateOptions = Pick<ValidationState, 'coercions' | 'coercion'>;

type RHFResolver = <TFieldValues extends FieldValues, TContext>(
values: UnpackNestedValue<TFieldValues>,
type RHFResolver<
TFieldValues extends FieldValues,
TInput = unknown,
TContext extends Object = object,
> = (
values: TInput,
context: TContext | undefined,
options: ResolverOptions<TFieldValues>,
) => ResolverResult<TFieldValues>;

export type Resolver = <UnknownValidator extends AnyStrictValidator>(
validator: UnknownValidator,
export type Resolver = <
TInput extends FieldValues = FieldValues,
TFieldValues extends TInput = TInput,
TContext extends object = object,
>(
validator: StrictValidator<TInput, TFieldValues>,
validatorOptions?: ValidateOptions,
)=> RHFResolver
) => RHFResolver<TFieldValues, TInput, TContext>;
5 changes: 2 additions & 3 deletions vest/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
FieldValues,
ResolverOptions,
ResolverResult,
UnpackNestedValue,
Expand All @@ -8,11 +7,11 @@ import * as Vest from 'vest';

export type ICreateResult = ReturnType<typeof Vest.create>;

export type Resolver = (
export type Resolver = <TFieldValues, TContext>(
schema: ICreateResult,
schemaOptions?: never,
factoryOptions?: { mode?: 'async' | 'sync' },
) => <TFieldValues extends FieldValues, TContext>(
) => (
values: UnpackNestedValue<TFieldValues>,
context: TContext | undefined,
options: ResolverOptions<TFieldValues>,
Expand Down
Loading

0 comments on commit 10ba224

Please sign in to comment.