Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement typed Input/Output interface for resolvers #753

Merged
merged 30 commits into from
Mar 31, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
cf569f3
feat: implement typed Input/Output interface for resolvers
jorisre Feb 26, 2025
14d8095
docs: write some docs
jorisre Feb 27, 2025
b82fd2f
WIP
jorisre Mar 3, 2025
1a4e0d2
doc: update
jorisre Mar 3, 2025
c0a1dbe
test: update
jorisre Mar 6, 2025
d3aa26b
try to handle option
jorisre Mar 6, 2025
06f3b08
fix: applied @controversial's suggested modifications
jorisre Mar 12, 2025
d32ab27
feat: yup type inference
jorisre Mar 14, 2025
9841f67
refactor: zod tests
jorisre Mar 14, 2025
f2f0b0b
chore: move @standard-schema/utils deps to subpackage
jorisre Mar 14, 2025
35e029d
chore: use RHF RC version
jorisre Mar 14, 2025
9ccbee3
chore: bump RHF version
jorisre Mar 14, 2025
a6b75c3
feat(vine): infer types from resolver
jorisre Mar 17, 2025
a091583
feat(valibot): type inference
jorisre Mar 17, 2025
cfcafe2
feat(typanion): types inference
jorisre Mar 19, 2025
0c137d3
feat(superstruct): type inference
jorisre Mar 19, 2025
4e40368
feat(standardSchema): type inference
jorisre Mar 19, 2025
882dac9
feat(io-ts): type inference
jorisre Mar 19, 2025
157d110
feat(effect-ts): type inference
jorisre Mar 19, 2025
c0fdb95
feat(computed-types): type inference
jorisre Mar 19, 2025
c6da66a
feat(arktype): type inference
jorisre Mar 20, 2025
bf30971
chore: clean
jorisre Mar 20, 2025
b49bc81
feat(typebox): type inference
jorisre Mar 20, 2025
a45a85e
typeschema
jorisre Mar 24, 2025
d441f34
fix(typebox): improve type interface (#757)
kotarella1110 Mar 27, 2025
8424fa4
chore: bump RHF
jorisre Mar 27, 2025
c4793f8
ci: temp fix compressed size
jorisre Mar 27, 2025
236bdde
chore: update bun.lock
jorisre Mar 27, 2025
c7378e5
chore: use RHF 7.55.0
jorisre Mar 31, 2025
f403f32
chore: reg bun.lock
jorisre Mar 31, 2025
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
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,41 @@ Install your preferred validation library alongside `@hookform/resolvers`.
| zod | ✅ | `firstError | all` |
</details>

## TypeScript

Most of the resolvers can infer the output type from the schema. See comparison table for more details.

```tsx
useForm<Input, Context, Output>()
```

Example:

```tsx
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

const schema = z.object({
id: z.number(),
});

// Automatically infers the output type from the schema
useForm({
resolver: zodResolver(schema),
});

// Different input type
useForm<{ id: string }>({
resolver: zodResolver(schema),
});

// Force the output type
useForm<z.input<typeof schema>, any, { id: boolean }>({
resolver: zodResolver(schema),
});
```

## Links

- [React-hook-form validation resolver documentation ](https://react-hook-form.com/docs/useform#resolver)
Expand Down
72 changes: 72 additions & 0 deletions zod/src/__tests__/zod.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { FieldValues, Resolver, SubmitHandler, useForm } from 'react-hook-form';
import { z } from 'zod';
import { zodResolver } from '..';
import { fields, invalidData, schema, validData } from './__fixtures__/data';

Expand Down Expand Up @@ -89,4 +91,74 @@ describe('zodResolver', () => {

await expect(promise).rejects.toThrow('custom error');
});

/**
* Type inference tests
*/
it('should correctly infer the output type from a zod schema', () => {
const resolver = zodResolver(z.object({ id: z.number() }));

expectTypeOf(resolver).toEqualTypeOf<
Resolver<FieldValues, unknown, { id: number }>
>();
});

it('should correctly infer the output type from a zod schema when a different input type is specified', () => {
const schema = z.object({ id: z.number() });
const resolver = zodResolver<{ id: string }, any, z.output<typeof schema>>(
schema,
);

expectTypeOf(resolver).toEqualTypeOf<
Resolver<{ id: string }, any, { id: number }>
>();
});

it('should correctly infer the output type from a zod schema when different input and output types are specified', () => {
const resolver = zodResolver<
{ id: string },
{ context: any },
{ id: number }
>(z.object({ id: z.number() }));

expectTypeOf(resolver).toEqualTypeOf<
Resolver<{ id: string }, { context: any }, { id: number }>
>();
});

it('should correctly infer the output type from a Zod schema for the handleSubmit function in useForm', () => {
const { handleSubmit } = useForm({
resolver: zodResolver(z.object({ id: z.number() })),
});

expectTypeOf(handleSubmit).parameter(0).toEqualTypeOf<
SubmitHandler<{
id: number;
}>
>();
});

it('should correctly infer the output type from a Zod schema when a different input type is specified for the handleSubmit function in useForm', () => {
const { handleSubmit } = useForm<{ id: number }>({
resolver: zodResolver(z.object({ id: z.string() })),
});

expectTypeOf(handleSubmit).parameter(0).toEqualTypeOf<
SubmitHandler<{
id: string;
}>
>();
});

it('should correctly infer the output type from a Zod schema when different input and output types are specified for the handleSubmit function in useForm', () => {
const { handleSubmit } = useForm<{ id: string }, any, { id: boolean }>({
resolver: zodResolver(z.object({ id: z.number() })),
});

expectTypeOf(handleSubmit).parameter(0).toEqualTypeOf<
SubmitHandler<{
id: boolean;
}>
>();
});
});
23 changes: 18 additions & 5 deletions zod/src/zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ function parseErrorSchema(

/**
* Creates a resolver function for react-hook-form that validates form data using a Zod schema
* @param {z.ZodSchema<TFieldValues>} schema - The Zod schema used to validate the form data
* @param {z.ZodSchema<Input>} schema - The Zod schema used to validate the form data
* @param {Partial<z.ParseParams>} [schemaOptions] - Optional configuration options for Zod parsing
* @param {Object} [resolverOptions] - Optional resolver-specific configuration
* @param {('async'|'sync')} [resolverOptions.mode='async'] - Validation mode. Use 'sync' for synchronous validation
* @param {boolean} [resolverOptions.raw=false] - If true, returns the raw form values instead of the parsed data
* @returns {Resolver<z.infer<typeof schema>>} A resolver function compatible with react-hook-form
* @returns {Resolver<z.ouput<typeof schema>>} A resolver function compatible with react-hook-form
* @throws {Error} Throws if validation fails with a non-Zod error
* @example
* const schema = z.object({
Expand All @@ -80,14 +80,27 @@ function parseErrorSchema(
* resolver: zodResolver(schema)
* });
*/
export function zodResolver<TFieldValues extends FieldValues>(
schema: z.ZodSchema<TFieldValues, any, any>,
export function zodResolver<
Input extends FieldValues,
Context,
Output,
Schema extends z.ZodSchema<Output, any, any> = z.ZodSchema<Output, any, any>,
>(
schema: Schema,
schemaOptions?: Partial<z.ParseParams>,
resolverOptions: {
mode?: 'async' | 'sync';
raw?: boolean;
} = {},
): Resolver<z.infer<typeof schema>> {
): Resolver<
Input,
Context,
(typeof resolverOptions)['raw'] extends true
? Input
: unknown extends Output
? z.output<Schema>
: Output
> {
return async (values, _, options) => {
try {
const data = await schema[
Expand Down
Loading