Skip to content

Commit 2185330

Browse files
feat(valibot): Add valibot resolver (#602)
* Add valibot resolver * fix docs * fix node 13 export * remove random lock file
1 parent 1a64cd1 commit 2185330

File tree

11 files changed

+390
-14
lines changed

11 files changed

+390
-14
lines changed

README.md

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,28 @@
2626

2727
### Supported resolvers
2828

29-
- [Yup](#yup)
30-
- [Zod](#zod)
31-
- [Superstruct](#superstruct)
32-
- [Joi](#joi)
33-
- [Class Validator](#class-validator)
34-
- [io-ts](#io-ts)
35-
- [Nope](#nope)
36-
- [computed-types](#computed-types)
37-
- [typanion](#typanion)
38-
- [Ajv](#ajv)
39-
- [TypeBox](#typebox)
40-
- [ArkType](#arktype)
29+
- [Install](#install)
30+
- [Links](#links)
31+
- [Supported resolvers](#supported-resolvers)
32+
- [API](#api)
33+
- [Quickstart](#quickstart)
34+
- [Yup](#yup)
35+
- [Zod](#zod)
36+
- [Superstruct](#superstruct)
37+
- [Joi](#joi)
38+
- [Vest](#vest)
39+
- [Class Validator](#class-validator)
40+
- [io-ts](#io-ts)
41+
- [Nope](#nope)
42+
- [computed-types](#computed-types)
43+
- [typanion](#typanion)
44+
- [Ajv](#ajv)
45+
- [TypeBox](#typebox)
46+
- [ArkType](#arktype)
47+
- [Valibot](#valibot)
48+
- [Backers](#backers)
49+
- [Sponsors](#sponsors)
50+
- [Contributors](#contributors)
4151

4252
## API
4353

@@ -532,6 +542,40 @@ const App = () => {
532542
};
533543
```
534544

545+
### [Valibot](https://github.com/fabian-hiller/valibot)
546+
547+
The modular and type safe schema library for validating structural data
548+
549+
[![npm](https://img.shields.io/bundlephobia/minzip/valibot?style=for-the-badge)](https://bundlephobia.com/result?p=valibot)
550+
551+
```typescript jsx
552+
import { useForm } from 'react-hook-form';
553+
import { valibotResolver } from '@hookform/resolvers/valibot';
554+
import { object, string, minLength, endsWith } from 'valibot';
555+
556+
const schema = object({
557+
username: string('username is required', [
558+
minLength(3, 'Needs to be at least 3 characters'),
559+
endsWith('cool', 'Needs to end with `cool`'),
560+
]),
561+
password: string('password is required'),
562+
});
563+
564+
const App = () => {
565+
const { register, handleSubmit } = useForm({
566+
resolver: valibotResolver(schema),
567+
});
568+
569+
return (
570+
<form onSubmit={handleSubmit((d) => console.log(d))}>
571+
<input {...register('username')} />
572+
<input type="password" {...register('password')} />
573+
<input type="submit" />
574+
</form>
575+
);
576+
};
577+
```
578+
535579
## Backers
536580

537581
Thanks goes to all our backers! [[Become a backer](https://opencollective.com/react-hook-form#backer)].

config/node-13-exports.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ const subRepositories = [
1414
'typanion',
1515
'ajv',
1616
'typebox',
17-
'arktype'
17+
'arktype',
18+
'valibot',
1819
];
1920

2021
const copySrc = () => {

package.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@
9393
"import": "./arktype/dist/arktype.mjs",
9494
"require": "./arktype/dist/arktype.js"
9595
},
96+
"./valibot": {
97+
"types": "./valibot/dist/index.d.ts",
98+
"umd": "./valibot/dist/valibot.umd.js",
99+
"import": "./valibot/dist/valibot.mjs",
100+
"require": "./valibot/dist/valibot.js"
101+
},
96102
"./package.json": "./package.json",
97103
"./*": "./*"
98104
},
@@ -136,7 +142,10 @@
136142
"typebox/dist",
137143
"arktype/package.json",
138144
"arktype/src",
139-
"arktype/dist"
145+
"arktype/dist",
146+
"valibot/package.json",
147+
"valibot/src",
148+
"valibot/dist"
140149
],
141150
"publishConfig": {
142151
"access": "public"
@@ -158,6 +167,7 @@
158167
"build:ajv": "microbundle --cwd ajv --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm",
159168
"build:typebox": "microbundle --cwd typebox --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm,@sinclair/typebox/value=value",
160169
"build:arktype": "microbundle --cwd arktype --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm",
170+
"build:valibot": "microbundle --cwd valibot --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm",
161171
"postbuild": "node ./config/node-13-exports.js",
162172
"lint": "eslint . --ext .ts,.js --ignore-path .gitignore",
163173
"lint:types": "tsc",
@@ -238,6 +248,7 @@
238248
"superstruct": "^1.0.3",
239249
"typanion": "^3.12.1",
240250
"typescript": "^5.0.4",
251+
"valibot": "^0.8.0",
241252
"vest": "^4.6.9",
242253
"vite": "^4.2.1",
243254
"vite-tsconfig-paths": "^4.2.0",

pnpm-lock.yaml

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

valibot/package.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "@hookform/resolvers/valibot",
3+
"amdName": "hookformResolversValibot",
4+
"version": "1.0.0",
5+
"private": true,
6+
"description": "React Hook Form validation resolver: valibot",
7+
"main": "dist/valibot.js",
8+
"module": "dist/valibot.module.js",
9+
"umd:main": "dist/valibot.umd.js",
10+
"source": "src/index.ts",
11+
"types": "dist/index.d.ts",
12+
"license": "MIT",
13+
"peerDependencies": {
14+
"react-hook-form": "^7.0.0",
15+
"@hookform/resolvers": "^2.0.0",
16+
"valibot": ">=0.8"
17+
}
18+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { Field, InternalFieldName } from 'react-hook-form';
2+
import {
3+
object,
4+
string,
5+
minLength,
6+
maxLength,
7+
regex,
8+
number,
9+
minValue,
10+
maxValue,
11+
email,
12+
array,
13+
boolean,
14+
required,
15+
} from 'valibot';
16+
17+
export const schema = required(
18+
object({
19+
username: string([minLength(2), maxLength(30), regex(/^\w+$/)]),
20+
password: string('New Password is required', [
21+
regex(new RegExp('.*[A-Z].*'), 'One uppercase character'),
22+
regex(new RegExp('.*[a-z].*'), 'One lowercase character'),
23+
regex(new RegExp('.*\\d.*'), 'One number'),
24+
regex(
25+
new RegExp('.*[`~<>?,./!@#$%^&*()\\-_+="\'|{}\\[\\];:\\\\].*'),
26+
'One special character',
27+
),
28+
minLength(8, 'Must be at least 8 characters in length'),
29+
]),
30+
repeatPassword: string('Repeat Password is required'),
31+
accessToken: string('Access token is required'),
32+
birthYear: number('Please enter your birth year', [
33+
minValue(1900),
34+
maxValue(2013),
35+
]),
36+
email: string([email('Invalid email address')]),
37+
tags: array(string('Tags should be strings')),
38+
enabled: boolean(),
39+
like: required(
40+
object({
41+
id: number('Like id is required'),
42+
name: string('Like name is required', [minLength(4, 'Too short')]),
43+
}),
44+
),
45+
}),
46+
);
47+
48+
export const validData = {
49+
username: 'Doe',
50+
password: 'Password123_',
51+
repeatPassword: 'Password123_',
52+
birthYear: 2000,
53+
email: 'john@doe.com',
54+
tags: ['tag1', 'tag2'],
55+
enabled: true,
56+
accessToken: 'accessToken',
57+
like: {
58+
id: 1,
59+
name: 'name',
60+
},
61+
};
62+
63+
export const invalidData = {
64+
password: '___',
65+
email: '',
66+
birthYear: 'birthYear',
67+
like: { id: 'z' },
68+
tags: [1, 2, 3],
69+
};
70+
71+
export const fields: Record<InternalFieldName, Field['_f']> = {
72+
username: {
73+
ref: { name: 'username' },
74+
name: 'username',
75+
},
76+
password: {
77+
ref: { name: 'password' },
78+
name: 'password',
79+
},
80+
email: {
81+
ref: { name: 'email' },
82+
name: 'email',
83+
},
84+
birthday: {
85+
ref: { name: 'birthday' },
86+
name: 'birthday',
87+
},
88+
};
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`valibotResolver > should return a single error from valibotResolver when validation fails 1`] = `
4+
{
5+
"errors": {
6+
"accessToken": {
7+
"message": "Invalid type",
8+
"ref": undefined,
9+
"type": "non_optional",
10+
},
11+
"birthYear": {
12+
"message": "Please enter your birth year",
13+
"ref": undefined,
14+
"type": "number",
15+
},
16+
"email": {
17+
"message": "Invalid email address",
18+
"ref": {
19+
"name": "email",
20+
},
21+
"type": "email",
22+
},
23+
"enabled": {
24+
"message": "Invalid type",
25+
"ref": undefined,
26+
"type": "non_optional",
27+
},
28+
"like": {
29+
"id": {
30+
"message": "Like id is required",
31+
"ref": undefined,
32+
"type": "number",
33+
},
34+
"name": {
35+
"message": "Invalid type",
36+
"ref": undefined,
37+
"type": "non_optional",
38+
},
39+
},
40+
"password": {
41+
"message": "One uppercase character",
42+
"ref": {
43+
"name": "password",
44+
},
45+
"type": "regex",
46+
},
47+
"repeatPassword": {
48+
"message": "Invalid type",
49+
"ref": undefined,
50+
"type": "non_optional",
51+
},
52+
"tags": [
53+
{
54+
"message": "Tags should be strings",
55+
"ref": undefined,
56+
"type": "string",
57+
},
58+
{
59+
"message": "Tags should be strings",
60+
"ref": undefined,
61+
"type": "string",
62+
},
63+
{
64+
"message": "Tags should be strings",
65+
"ref": undefined,
66+
"type": "string",
67+
},
68+
],
69+
"username": {
70+
"message": "Invalid type",
71+
"ref": {
72+
"name": "username",
73+
},
74+
"type": "non_optional",
75+
},
76+
},
77+
"values": {},
78+
}
79+
`;

valibot/src/__tests__/valibot.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/* eslint-disable no-console, @typescript-eslint/ban-ts-comment */
2+
import { valibotResolver } from '..';
3+
import { schema, validData, fields, invalidData } from './__fixtures__/data';
4+
5+
const shouldUseNativeValidation = false;
6+
describe('valibotResolver', () => {
7+
it('should return values from valibotResolver when validation pass', async () => {
8+
const result = await valibotResolver(schema)(validData, undefined, {
9+
fields,
10+
shouldUseNativeValidation,
11+
});
12+
13+
expect(result).toEqual({ errors: {}, values: validData });
14+
});
15+
16+
it('should return a single error from valibotResolver when validation fails', async () => {
17+
const result = await valibotResolver(schema)(invalidData, undefined, {
18+
fields,
19+
shouldUseNativeValidation,
20+
});
21+
22+
expect(result).toMatchSnapshot();
23+
});
24+
});

valibot/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './valibot';
2+
export * from './types';

0 commit comments

Comments
 (0)