Skip to content

Commit e8e0f80

Browse files
authored
feat(typeboxResolver): make TypeBox resolver work with compiled schema (#674)
* feat(typeboxResolver): make TypeBox resolver work with compiled schema * docs(typeboxResolver): add docs for using TypeBox resolver with compiled schema * chore: cleanup wrong indentation format
1 parent c0d528b commit e8e0f80

File tree

9 files changed

+412
-5
lines changed

9 files changed

+412
-5
lines changed

README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
- [typanion](#typanion)
4444
- [Ajv](#ajv)
4545
- [TypeBox](#typebox)
46+
- [With `ValueCheck`](#with-valuecheck)
47+
- [With `TypeCompiler`](#with-typecompiler)
4648
- [ArkType](#arktype)
4749
- [Valibot](#valibot)
4850
- [effect-ts](#effect-ts)
@@ -488,6 +490,8 @@ JSON Schema Type Builder with Static Type Resolution for TypeScript
488490

489491
[![npm](https://img.shields.io/bundlephobia/minzip/@sinclair/typebox?style=for-the-badge)](https://bundlephobia.com/result?p=@sinclair/typebox)
490492

493+
#### With `ValueCheck`
494+
491495
```typescript jsx
492496
import { useForm } from 'react-hook-form';
493497
import { typeboxResolver } from '@hookform/resolvers/typebox';
@@ -513,6 +517,38 @@ const App = () => {
513517
};
514518
```
515519

520+
#### With `TypeCompiler`
521+
522+
A high-performance JIT of `TypeBox`, [read more](https://github.com/sinclairzx81/typebox#typecompiler)
523+
524+
```typescript jsx
525+
import { useForm } from 'react-hook-form';
526+
import { typeboxResolver } from '@hookform/resolvers/typebox';
527+
import { Type } from '@sinclair/typebox';
528+
import { TypeCompiler } from '@sinclair/typebox/compiler';
529+
530+
const schema = Type.Object({
531+
username: Type.String({ minLength: 1 }),
532+
password: Type.String({ minLength: 1 }),
533+
});
534+
535+
const typecheck = TypeCompiler.Compile(schema);
536+
537+
const App = () => {
538+
const { register, handleSubmit } = useForm({
539+
resolver: typeboxResolver(typecheck),
540+
});
541+
542+
return (
543+
<form onSubmit={handleSubmit((d) => console.log(d))}>
544+
<input {...register('username')} />
545+
<input type="password" {...register('password')} />
546+
<input type="submit" />
547+
</form>
548+
);
549+
};
550+
```
551+
516552
### [ArkType](https://github.com/arktypeio/arktype)
517553

518554
TypeScript's 1:1 validator, optimized from editor to runtime
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import React from 'react';
2+
import { render, screen } from '@testing-library/react';
3+
import user from '@testing-library/user-event';
4+
import { useForm } from 'react-hook-form';
5+
import { typeboxResolver } from '..';
6+
import { Type, Static } from '@sinclair/typebox';
7+
import { TypeCompiler } from '@sinclair/typebox/compiler';
8+
9+
const schema = Type.Object({
10+
username: Type.String({ minLength: 1 }),
11+
password: Type.String({ minLength: 1 }),
12+
});
13+
14+
const typecheck = TypeCompiler.Compile(schema)
15+
16+
type FormData = Static<typeof schema> & { unusedProperty: string };
17+
18+
interface Props {
19+
onSubmit: (data: FormData) => void;
20+
}
21+
22+
function TestComponent({ onSubmit }: Props) {
23+
const {
24+
register,
25+
handleSubmit,
26+
formState: { errors },
27+
} = useForm<FormData>({
28+
resolver: typeboxResolver(typecheck), // Useful to check TypeScript regressions
29+
});
30+
31+
return (
32+
<form onSubmit={handleSubmit(onSubmit)}>
33+
<input {...register('username')} />
34+
{errors.username && <span role="alert">{errors.username.message}</span>}
35+
36+
<input {...register('password')} />
37+
{errors.password && <span role="alert">{errors.password.message}</span>}
38+
39+
<button type="submit">submit</button>
40+
</form>
41+
);
42+
}
43+
44+
test("form's validation with Typebox (with compiler) and TypeScript's integration", async () => {
45+
const handleSubmit = vi.fn();
46+
render(<TestComponent onSubmit={handleSubmit} />);
47+
48+
expect(screen.queryAllByRole('alert')).toHaveLength(0);
49+
50+
await user.click(screen.getByText(/submit/i));
51+
52+
expect(
53+
screen.getAllByText(/Expected string length greater or equal to 1/i),
54+
).toHaveLength(2);
55+
56+
expect(handleSubmit).not.toHaveBeenCalled();
57+
});
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import React from 'react';
2+
import { useForm } from 'react-hook-form';
3+
import { render, screen } from '@testing-library/react';
4+
import user from '@testing-library/user-event';
5+
import { typeboxResolver } from '..';
6+
7+
import { Type, Static } from '@sinclair/typebox';
8+
import { TypeCompiler } from '@sinclair/typebox/compiler';
9+
10+
const schema = Type.Object({
11+
username: Type.String({ minLength: 1 }),
12+
password: Type.String({ minLength: 1 }),
13+
});
14+
15+
const typecheck = TypeCompiler.Compile(schema)
16+
17+
type FormData = Static<typeof schema>;
18+
19+
interface Props {
20+
onSubmit: (data: FormData) => void;
21+
}
22+
23+
function TestComponent({ onSubmit }: Props) {
24+
const { register, handleSubmit } = useForm<FormData>({
25+
resolver: typeboxResolver(typecheck),
26+
shouldUseNativeValidation: true,
27+
});
28+
29+
return (
30+
<form onSubmit={handleSubmit(onSubmit)}>
31+
<input {...register('username')} placeholder="username" />
32+
33+
<input {...register('password')} placeholder="password" />
34+
35+
<button type="submit">submit</button>
36+
</form>
37+
);
38+
}
39+
40+
test("form's native validation with Typebox (with compiler)", async () => {
41+
const handleSubmit = vi.fn();
42+
render(<TestComponent onSubmit={handleSubmit} />);
43+
44+
// username
45+
let usernameField = screen.getByPlaceholderText(
46+
/username/i,
47+
) as HTMLInputElement;
48+
expect(usernameField.validity.valid).toBe(true);
49+
expect(usernameField.validationMessage).toBe('');
50+
51+
// password
52+
let passwordField = screen.getByPlaceholderText(
53+
/password/i,
54+
) as HTMLInputElement;
55+
expect(passwordField.validity.valid).toBe(true);
56+
expect(passwordField.validationMessage).toBe('');
57+
58+
await user.click(screen.getByText(/submit/i));
59+
60+
// username
61+
usernameField = screen.getByPlaceholderText(/username/i) as HTMLInputElement;
62+
expect(usernameField.validity.valid).toBe(false);
63+
expect(usernameField.validationMessage).toBe(
64+
'Expected string length greater or equal to 1',
65+
);
66+
67+
// password
68+
passwordField = screen.getByPlaceholderText(/password/i) as HTMLInputElement;
69+
expect(passwordField.validity.valid).toBe(false);
70+
expect(passwordField.validationMessage).toBe(
71+
'Expected string length greater or equal to 1',
72+
);
73+
74+
await user.type(screen.getByPlaceholderText(/username/i), 'joe');
75+
await user.type(screen.getByPlaceholderText(/password/i), 'password');
76+
77+
// username
78+
usernameField = screen.getByPlaceholderText(/username/i) as HTMLInputElement;
79+
expect(usernameField.validity.valid).toBe(true);
80+
expect(usernameField.validationMessage).toBe('');
81+
82+
// password
83+
passwordField = screen.getByPlaceholderText(/password/i) as HTMLInputElement;
84+
expect(passwordField.validity.valid).toBe(true);
85+
expect(passwordField.validationMessage).toBe('');
86+
});

typebox/src/__tests__/Form-native-validation.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ function TestComponent({ onSubmit }: Props) {
3434
);
3535
}
3636

37-
test("form's native validation with Zod", async () => {
37+
test("form's native validation with Typebox", async () => {
3838
const handleSubmit = vi.fn();
3939
render(<TestComponent onSubmit={handleSubmit} />);
4040

typebox/src/__tests__/Form.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ function TestComponent({ onSubmit }: Props) {
3838
);
3939
}
4040

41-
test("form's validation with Zod and TypeScript's integration", async () => {
41+
test("form's validation with Typebox and TypeScript's integration", async () => {
4242
const handleSubmit = vi.fn();
4343
render(<TestComponent onSubmit={handleSubmit} />);
4444

0 commit comments

Comments
 (0)