@hookform/resolvers 2.3.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -11
- package/computed-types/dist/computed-types.d.ts +2 -0
- package/computed-types/dist/computed-types.js +2 -0
- package/computed-types/dist/computed-types.js.map +1 -0
- package/computed-types/dist/computed-types.mjs +2 -0
- package/computed-types/dist/computed-types.modern.js +2 -0
- package/computed-types/dist/computed-types.modern.js.map +1 -0
- package/computed-types/dist/computed-types.module.js +2 -0
- package/computed-types/dist/computed-types.module.js.map +1 -0
- package/computed-types/dist/computed-types.umd.js +2 -0
- package/computed-types/dist/computed-types.umd.js.map +1 -0
- package/computed-types/dist/index.d.ts +2 -0
- package/computed-types/dist/types.d.ts +2 -0
- package/computed-types/package.json +17 -0
- package/computed-types/src/__tests__/Form.tsx +54 -0
- package/computed-types/src/__tests__/__fixtures__/data.ts +72 -0
- package/computed-types/src/__tests__/__snapshots__/computed-types.ts.snap +65 -0
- package/computed-types/src/__tests__/computed-types.ts +20 -0
- package/computed-types/src/computed-types.ts +42 -0
- package/computed-types/src/index.ts +2 -0
- package/computed-types/src/types.ts +14 -0
- package/io-ts/src/__tests__/Form.tsx +65 -0
- package/io-ts/src/__tests__/__fixtures__/data.ts +129 -0
- package/io-ts/src/__tests__/__snapshots__/io-ts.ts.snap +89 -0
- package/io-ts/src/__tests__/io-ts.ts +32 -0
- package/joi/dist/joi.js +1 -1
- package/joi/dist/joi.js.map +1 -1
- package/joi/dist/joi.mjs +1 -1
- package/joi/dist/joi.modern.js +1 -1
- package/joi/dist/joi.modern.js.map +1 -1
- package/joi/dist/joi.module.js +1 -1
- package/joi/dist/joi.module.js.map +1 -1
- package/joi/dist/joi.umd.js +1 -1
- package/joi/dist/joi.umd.js.map +1 -1
- package/joi/src/__tests__/__fixtures__/data.ts +12 -3
- package/joi/src/__tests__/__snapshots__/joi.ts.snap +20 -10
- package/joi/src/joi.ts +6 -1
- package/nope/dist/index.d.ts +2 -0
- package/nope/dist/nope.d.ts +2 -0
- package/nope/dist/nope.js +2 -0
- package/nope/dist/nope.js.map +1 -0
- package/nope/dist/nope.mjs +2 -0
- package/nope/dist/nope.modern.js +2 -0
- package/nope/dist/nope.modern.js.map +1 -0
- package/nope/dist/nope.module.js +2 -0
- package/nope/dist/nope.module.js.map +1 -0
- package/nope/dist/nope.umd.js +2 -0
- package/nope/dist/nope.umd.js.map +1 -0
- package/nope/dist/types.d.ts +6 -0
- package/nope/package.json +18 -0
- package/nope/src/__tests__/Form.tsx +57 -0
- package/nope/src/__tests__/__fixtures__/data.ts +70 -0
- package/nope/src/__tests__/__snapshots__/nope.ts.snap +43 -0
- package/nope/src/__tests__/nope.ts +24 -0
- package/nope/src/index.ts +2 -0
- package/nope/src/nope.ts +40 -0
- package/nope/src/types.ts +19 -0
- package/package.json +51 -32
- package/yup/dist/yup.js +1 -1
- package/yup/dist/yup.js.map +1 -1
- package/yup/dist/yup.mjs +1 -1
- package/yup/dist/yup.modern.js +1 -1
- package/yup/dist/yup.modern.js.map +1 -1
- package/yup/dist/yup.module.js +1 -1
- package/yup/dist/yup.module.js.map +1 -1
- package/yup/dist/yup.umd.js +1 -1
- package/yup/dist/yup.umd.js.map +1 -1
- package/yup/src/__tests__/__fixtures__/data.ts +11 -4
- package/yup/src/__tests__/__snapshots__/yup.ts.snap +16 -6
- package/yup/src/yup.ts +6 -1
- package/zod/dist/zod.js +1 -1
- package/zod/dist/zod.js.map +1 -1
- package/zod/dist/zod.mjs +1 -1
- package/zod/dist/zod.modern.js +1 -1
- package/zod/dist/zod.modern.js.map +1 -1
- package/zod/dist/zod.module.js +1 -1
- package/zod/dist/zod.module.js.map +1 -1
- package/zod/dist/zod.umd.js +1 -1
- package/zod/dist/zod.umd.js.map +1 -1
- package/zod/src/__tests__/__fixtures__/data.ts +12 -3
- package/zod/src/__tests__/__snapshots__/zod.ts.snap +24 -8
- package/zod/src/zod.ts +6 -1
package/README.md
CHANGED
|
@@ -56,8 +56,8 @@ const App = () => {
|
|
|
56
56
|
|
|
57
57
|
return (
|
|
58
58
|
<form onSubmit={handleSubmit((d) => console.log(d))}>
|
|
59
|
-
<input
|
|
60
|
-
<input
|
|
59
|
+
<input {...register('name')} />
|
|
60
|
+
<input type="number" {...register('age')} />
|
|
61
61
|
<input type="submit" />
|
|
62
62
|
</form>
|
|
63
63
|
);
|
|
@@ -96,9 +96,9 @@ const App = () => {
|
|
|
96
96
|
|
|
97
97
|
return (
|
|
98
98
|
<form onSubmit={handleSubmit((d) => console.log(d))}>
|
|
99
|
-
<input
|
|
99
|
+
<input {...register('name')} />
|
|
100
100
|
{errors.name?.message && <p>{errors.name?.message}</p>}
|
|
101
|
-
<input
|
|
101
|
+
<input type="number" {...register('age', { valueAsNumber: true })} />
|
|
102
102
|
{errors.age?.message && <p>{errors.age?.message}</p>}
|
|
103
103
|
<input type="submit" />
|
|
104
104
|
</form>
|
|
@@ -132,8 +132,8 @@ const App = () => {
|
|
|
132
132
|
|
|
133
133
|
return (
|
|
134
134
|
<form onSubmit={handleSubmit((d) => console.log(d))}>
|
|
135
|
-
<input
|
|
136
|
-
<input
|
|
135
|
+
<input {...register('name')} />
|
|
136
|
+
<input type="number" ref={register('age', { valueAsNumber: true })} />
|
|
137
137
|
<input type="submit" />
|
|
138
138
|
</form>
|
|
139
139
|
);
|
|
@@ -165,8 +165,8 @@ const App = () => {
|
|
|
165
165
|
|
|
166
166
|
return (
|
|
167
167
|
<form onSubmit={handleSubmit((d) => console.log(d))}>
|
|
168
|
-
<input
|
|
169
|
-
<input
|
|
168
|
+
<input {...register('name')} />
|
|
169
|
+
<input type="number" {...register('age')} />
|
|
170
170
|
<input type="submit" />
|
|
171
171
|
</form>
|
|
172
172
|
);
|
|
@@ -220,8 +220,8 @@ const App = () => {
|
|
|
220
220
|
|
|
221
221
|
return (
|
|
222
222
|
<form onSubmit={handleSubmit((data) => console.log(data))}>
|
|
223
|
-
<input
|
|
224
|
-
<input type="
|
|
223
|
+
<input {...register('username')} />
|
|
224
|
+
<input type="password" {...register('password')} />
|
|
225
225
|
<input type="submit" />
|
|
226
226
|
</form>
|
|
227
227
|
);
|
|
@@ -315,7 +315,82 @@ const App = () => {
|
|
|
315
315
|
return (
|
|
316
316
|
<form onSubmit={handleSubmit((d) => console.log(d))}>
|
|
317
317
|
<input name="username" ref={register} />
|
|
318
|
-
<input
|
|
318
|
+
<input type="number" {...register('age')} />
|
|
319
|
+
<input type="submit" />
|
|
320
|
+
</form>
|
|
321
|
+
);
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
export default App;
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### [Nope](https://github.com/bvego/nope-validator)
|
|
328
|
+
|
|
329
|
+
A small, simple, and fast JS validator
|
|
330
|
+
|
|
331
|
+
[](https://bundlephobia.com/result?p=nope-validator)
|
|
332
|
+
|
|
333
|
+
```typescript jsx
|
|
334
|
+
import React from 'react';
|
|
335
|
+
import { useForm } from 'react-hook-form';
|
|
336
|
+
import { nopeResolver } from '@hookform/resolvers/nope';
|
|
337
|
+
import Nope from 'nope-validator';
|
|
338
|
+
|
|
339
|
+
const schema = Nope.object().shape({
|
|
340
|
+
name: Nope.string().required(),
|
|
341
|
+
age: Nope.number().required(),
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
const App = () => {
|
|
345
|
+
const { register, handleSubmit } = useForm({
|
|
346
|
+
resolver: nopeResolver(schema),
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
return (
|
|
350
|
+
<form onSubmit={handleSubmit((d) => console.log(d))}>
|
|
351
|
+
<input {...register('name')} />
|
|
352
|
+
<input type="number" {...register('age')} />
|
|
353
|
+
<input type="submit" />
|
|
354
|
+
</form>
|
|
355
|
+
);
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
export default App;
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### [computed-types](https://github.com/neuledge/computed-types)
|
|
362
|
+
|
|
363
|
+
TypeScript-first schema validation with static type inference
|
|
364
|
+
|
|
365
|
+
[](https://bundlephobia.com/result?p=computed-types)
|
|
366
|
+
|
|
367
|
+
```tsx
|
|
368
|
+
import React from 'react';
|
|
369
|
+
import { useForm } from 'react-hook-form';
|
|
370
|
+
import { computedTypesResolver } from '@hookform/resolvers/zod';
|
|
371
|
+
import Schema, { number, string } from 'computed-types';
|
|
372
|
+
|
|
373
|
+
const schema = Schema({
|
|
374
|
+
username: string.min(1).error('username field is required'),
|
|
375
|
+
password: string.min(1).error('password field is required'),
|
|
376
|
+
password: number,
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
const App = () => {
|
|
380
|
+
const {
|
|
381
|
+
register,
|
|
382
|
+
handleSubmit,
|
|
383
|
+
formState: { errors },
|
|
384
|
+
} = useForm({
|
|
385
|
+
resolver: computedTypesResolver(schema),
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
return (
|
|
389
|
+
<form onSubmit={handleSubmit((d) => console.log(d))}>
|
|
390
|
+
<input {...register('name')} />
|
|
391
|
+
{errors.name?.message && <p>{errors.name?.message}</p>}
|
|
392
|
+
<input type="number" {...register('age', { valueAsNumber: true })} />
|
|
393
|
+
{errors.age?.message && <p>{errors.age?.message}</p>}
|
|
319
394
|
<input type="submit" />
|
|
320
395
|
</form>
|
|
321
396
|
);
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var r=require("@hookform/resolvers"),e=function r(e,t,o){return void 0===t&&(t={}),void 0===o&&(o=""),(e.errors||[]).reduce(function(e,t){var n=String(t.path[0]),s=o?o+"."+n:n;return e[s]={type:t.error.name,message:t.error.message},r(t.error,e,s),e},t)};exports.computedTypesResolver=function(t){return function(o,n,s){try{return Promise.resolve(function(r,e){try{var n=Promise.resolve(t(o)).then(function(r){return{errors:{},values:r}})}catch(r){return e(r)}return n&&n.then?n.then(void 0,e):n}(0,function(t){return{values:{},errors:r.toNestError(e(t),s.fields)}}))}catch(r){return Promise.reject(r)}}};
|
|
2
|
+
//# sourceMappingURL=computed-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"computed-types.js","sources":["../src/computed-types.ts"],"sourcesContent":["import type { FieldErrors } from 'react-hook-form';\nimport { toNestError } from '@hookform/resolvers';\nimport type { ValidationError } from 'computed-types';\nimport type { Resolver } from './types';\n\nconst parseErrorSchema = (\n computedTypesError: ValidationError,\n parsedErrors: FieldErrors = {},\n path = '',\n) => {\n return (computedTypesError.errors || []).reduce((acc, error) => {\n const _currentPath = String(error.path[0]);\n const _path = path ? `${path}.${_currentPath}` : _currentPath;\n\n acc[_path] = {\n type: error.error.name,\n message: error.error.message,\n };\n\n parseErrorSchema(error.error, acc, _path);\n\n return acc;\n }, parsedErrors);\n};\n\nexport const computedTypesResolver: Resolver = (schema) => async (\n values,\n _,\n options,\n) => {\n try {\n return {\n errors: {},\n values: await schema(values),\n };\n } catch (error) {\n return {\n values: {},\n errors: toNestError(parseErrorSchema(error), options.fields),\n };\n }\n};\n"],"names":["parseErrorSchema","computedTypesError","parsedErrors","path","errors","reduce","acc","error","_currentPath","String","_path","type","name","message","schema","values","_","options","toNestError","fields"],"mappings":"qCAKMA,EAAmB,SAAnBA,EACJC,EACAC,EACAC,GAEA,gBAHAD,IAAAA,EAA4B,aAC5BC,IAAAA,EAAO,KAECF,EAAmBG,QAAU,IAAIC,OAAO,SAACC,EAAKC,GACpD,IAAMC,EAAeC,OAAOF,EAAMJ,KAAK,IACjCO,EAAQP,EAAUA,MAAQK,EAAiBA,EASjD,OAPAF,EAAII,GAAS,CACXC,KAAMJ,EAAMA,MAAMK,KAClBC,QAASN,EAAMA,MAAMM,SAGvBb,EAAiBO,EAAMA,MAAOD,EAAKI,GAE5BJ,GACNJ,kCAG0C,SAACY,mBAC9CC,EACAC,EACAC,sEAKkBH,EAAOC,qBAFvB,MAAO,CACLX,OAAQ,GACRW,iFAEKR,GACP,MAAO,CACLQ,OAAQ,GACRX,OAAQc,cAAYlB,EAAiBO,GAAQU,EAAQE,YAbZ"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{toNestError as r}from"@hookform/resolvers";var e=function r(e,t,n){return void 0===t&&(t={}),void 0===n&&(n=""),(e.errors||[]).reduce(function(e,t){var o=String(t.path[0]),u=n?n+"."+o:o;return e[u]={type:t.error.name,message:t.error.message},r(t.error,e,u),e},t)},t=function(t){return function(n,o,u){try{return Promise.resolve(function(r,e){try{var o=Promise.resolve(t(n)).then(function(r){return{errors:{},values:r}})}catch(r){return e(r)}return o&&o.then?o.then(void 0,e):o}(0,function(t){return{values:{},errors:r(e(t),u.fields)}}))}catch(r){return Promise.reject(r)}}};export{t as computedTypesResolver};
|
|
2
|
+
//# sourceMappingURL=computed-types.module.js.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{toNestError as r}from"@hookform/resolvers";const e=(r,o={},s="")=>(r.errors||[]).reduce((r,o)=>{const t=String(o.path[0]),a=s?`${s}.${t}`:t;return r[a]={type:o.error.name,message:o.error.message},e(o.error,r,a),r},o),o=o=>async(s,t,a)=>{try{return{errors:{},values:await o(s)}}catch(o){return{values:{},errors:r(e(o),a.fields)}}};export{o as computedTypesResolver};
|
|
2
|
+
//# sourceMappingURL=computed-types.modern.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"computed-types.modern.js","sources":["../src/computed-types.ts"],"sourcesContent":["import type { FieldErrors } from 'react-hook-form';\nimport { toNestError } from '@hookform/resolvers';\nimport type { ValidationError } from 'computed-types';\nimport type { Resolver } from './types';\n\nconst parseErrorSchema = (\n computedTypesError: ValidationError,\n parsedErrors: FieldErrors = {},\n path = '',\n) => {\n return (computedTypesError.errors || []).reduce((acc, error) => {\n const _currentPath = String(error.path[0]);\n const _path = path ? `${path}.${_currentPath}` : _currentPath;\n\n acc[_path] = {\n type: error.error.name,\n message: error.error.message,\n };\n\n parseErrorSchema(error.error, acc, _path);\n\n return acc;\n }, parsedErrors);\n};\n\nexport const computedTypesResolver: Resolver = (schema) => async (\n values,\n _,\n options,\n) => {\n try {\n return {\n errors: {},\n values: await schema(values),\n };\n } catch (error) {\n return {\n values: {},\n errors: toNestError(parseErrorSchema(error), options.fields),\n };\n }\n};\n"],"names":["parseErrorSchema","computedTypesError","parsedErrors","path","errors","reduce","acc","error","_currentPath","String","_path","type","name","message","computedTypesResolver","schema","async","values","_","options","toNestError","fields"],"mappings":"kDAKA,MAAMA,EAAmB,CACvBC,EACAC,EAA4B,GAC5BC,EAAO,MAECF,EAAmBG,QAAU,IAAIC,OAAO,CAACC,EAAKC,KACpD,MAAMC,EAAeC,OAAOF,EAAMJ,KAAK,IACjCO,EAAQP,KAAUA,KAAQK,IAAiBA,EASjD,OAPAF,EAAII,GAAS,CACXC,KAAMJ,EAAMA,MAAMK,KAClBC,QAASN,EAAMA,MAAMM,SAGvBb,EAAiBO,EAAMA,MAAOD,EAAKI,GAE5BJ,GACNJ,GAGQY,EAAmCC,GAAWC,MACzDC,EACAC,EACAC,KAEA,IACE,MAAO,CACLf,OAAQ,GACRa,aAAcF,EAAOE,IAEvB,MAAOV,GACP,MAAO,CACLU,OAAQ,GACRb,OAAQgB,EAAYpB,EAAiBO,GAAQY,EAAQE"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{toNestError as r}from"@hookform/resolvers";var e=function r(e,t,n){return void 0===t&&(t={}),void 0===n&&(n=""),(e.errors||[]).reduce(function(e,t){var o=String(t.path[0]),u=n?n+"."+o:o;return e[u]={type:t.error.name,message:t.error.message},r(t.error,e,u),e},t)},t=function(t){return function(n,o,u){try{return Promise.resolve(function(r,e){try{var o=Promise.resolve(t(n)).then(function(r){return{errors:{},values:r}})}catch(r){return e(r)}return o&&o.then?o.then(void 0,e):o}(0,function(t){return{values:{},errors:r(e(t),u.fields)}}))}catch(r){return Promise.reject(r)}}};export{t as computedTypesResolver};
|
|
2
|
+
//# sourceMappingURL=computed-types.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"computed-types.module.js","sources":["../src/computed-types.ts"],"sourcesContent":["import type { FieldErrors } from 'react-hook-form';\nimport { toNestError } from '@hookform/resolvers';\nimport type { ValidationError } from 'computed-types';\nimport type { Resolver } from './types';\n\nconst parseErrorSchema = (\n computedTypesError: ValidationError,\n parsedErrors: FieldErrors = {},\n path = '',\n) => {\n return (computedTypesError.errors || []).reduce((acc, error) => {\n const _currentPath = String(error.path[0]);\n const _path = path ? `${path}.${_currentPath}` : _currentPath;\n\n acc[_path] = {\n type: error.error.name,\n message: error.error.message,\n };\n\n parseErrorSchema(error.error, acc, _path);\n\n return acc;\n }, parsedErrors);\n};\n\nexport const computedTypesResolver: Resolver = (schema) => async (\n values,\n _,\n options,\n) => {\n try {\n return {\n errors: {},\n values: await schema(values),\n };\n } catch (error) {\n return {\n values: {},\n errors: toNestError(parseErrorSchema(error), options.fields),\n };\n }\n};\n"],"names":["parseErrorSchema","computedTypesError","parsedErrors","path","errors","reduce","acc","error","_currentPath","String","_path","type","name","message","computedTypesResolver","schema","values","_","options","toNestError","fields"],"mappings":"kDAKA,IAAMA,EAAmB,SAAnBA,EACJC,EACAC,EACAC,GAEA,gBAHAD,IAAAA,EAA4B,aAC5BC,IAAAA,EAAO,KAECF,EAAmBG,QAAU,IAAIC,OAAO,SAACC,EAAKC,GACpD,IAAMC,EAAeC,OAAOF,EAAMJ,KAAK,IACjCO,EAAQP,EAAUA,MAAQK,EAAiBA,EASjD,OAPAF,EAAII,GAAS,CACXC,KAAMJ,EAAMA,MAAMK,KAClBC,QAASN,EAAMA,MAAMM,SAGvBb,EAAiBO,EAAMA,MAAOD,EAAKI,GAE5BJ,GACNJ,IAGQY,EAAkC,SAACC,mBAC9CC,EACAC,EACAC,sEAKkBH,EAAOC,qBAFvB,MAAO,CACLZ,OAAQ,GACRY,iFAEKT,GACP,MAAO,CACLS,OAAQ,GACRZ,OAAQe,EAAYnB,EAAiBO,GAAQW,EAAQE,YAbZ"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
!function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports,require("@hookform/resolvers")):"function"==typeof define&&define.amd?define(["exports","@hookform/resolvers"],r):r((e||self).hookformResolversComputedTypes={},e.hookformResolvers)}(this,function(e,r){var o=function e(r,o,t){return void 0===o&&(o={}),void 0===t&&(t=""),(r.errors||[]).reduce(function(r,o){var n=String(o.path[0]),s=t?t+"."+n:n;return r[s]={type:o.error.name,message:o.error.message},e(o.error,r,s),r},o)};e.computedTypesResolver=function(e){return function(t,n,s){try{return Promise.resolve(function(r,o){try{var n=Promise.resolve(e(t)).then(function(e){return{errors:{},values:e}})}catch(e){return o(e)}return n&&n.then?n.then(void 0,o):n}(0,function(e){return{values:{},errors:r.toNestError(o(e),s.fields)}}))}catch(e){return Promise.reject(e)}}}});
|
|
2
|
+
//# sourceMappingURL=computed-types.umd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"computed-types.umd.js","sources":["../src/computed-types.ts"],"sourcesContent":["import type { FieldErrors } from 'react-hook-form';\nimport { toNestError } from '@hookform/resolvers';\nimport type { ValidationError } from 'computed-types';\nimport type { Resolver } from './types';\n\nconst parseErrorSchema = (\n computedTypesError: ValidationError,\n parsedErrors: FieldErrors = {},\n path = '',\n) => {\n return (computedTypesError.errors || []).reduce((acc, error) => {\n const _currentPath = String(error.path[0]);\n const _path = path ? `${path}.${_currentPath}` : _currentPath;\n\n acc[_path] = {\n type: error.error.name,\n message: error.error.message,\n };\n\n parseErrorSchema(error.error, acc, _path);\n\n return acc;\n }, parsedErrors);\n};\n\nexport const computedTypesResolver: Resolver = (schema) => async (\n values,\n _,\n options,\n) => {\n try {\n return {\n errors: {},\n values: await schema(values),\n };\n } catch (error) {\n return {\n values: {},\n errors: toNestError(parseErrorSchema(error), options.fields),\n };\n }\n};\n"],"names":["parseErrorSchema","computedTypesError","parsedErrors","path","errors","reduce","acc","error","_currentPath","String","_path","type","name","message","schema","values","_","options","toNestError","fields"],"mappings":"mUAKA,IAAMA,EAAmB,SAAnBA,EACJC,EACAC,EACAC,GAEA,gBAHAD,IAAAA,EAA4B,aAC5BC,IAAAA,EAAO,KAECF,EAAmBG,QAAU,IAAIC,OAAO,SAACC,EAAKC,GACpD,IAAMC,EAAeC,OAAOF,EAAMJ,KAAK,IACjCO,EAAQP,EAAUA,MAAQK,EAAiBA,EASjD,OAPAF,EAAII,GAAS,CACXC,KAAMJ,EAAMA,MAAMK,KAClBC,QAASN,EAAMA,MAAMM,SAGvBb,EAAiBO,EAAMA,MAAOD,EAAKI,GAE5BJ,GACNJ,4BAG0C,SAACY,mBAC9CC,EACAC,EACAC,sEAKkBH,EAAOC,qBAFvB,MAAO,CACLX,OAAQ,GACRW,iFAEKR,GACP,MAAO,CACLQ,OAAQ,GACRX,OAAQc,cAAYlB,EAAiBO,GAAQU,EAAQE,YAbZ"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import type { FieldValues, ResolverResult, UnpackNestedValue, ResolverOptions } from 'react-hook-form';
|
|
2
|
+
export declare type Resolver = (schema: any) => <TFieldValues extends FieldValues, TContext>(values: UnpackNestedValue<TFieldValues>, context: TContext | undefined, options: ResolverOptions<TFieldValues>) => Promise<ResolverResult<TFieldValues>>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "computed-types",
|
|
3
|
+
"amdName": "hookformResolversComputedTypes",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"private": true,
|
|
6
|
+
"description": "React Hook Form validation resolver: computed-types",
|
|
7
|
+
"main": "dist/computed-types.js",
|
|
8
|
+
"module": "dist/computed-types.module.js",
|
|
9
|
+
"umd:main": "dist/computed-types.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
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, act } from '@testing-library/react';
|
|
3
|
+
import user from '@testing-library/user-event';
|
|
4
|
+
import { useForm } from 'react-hook-form';
|
|
5
|
+
import Schema, { Type, string } from 'computed-types';
|
|
6
|
+
import { computedTypesResolver } from '..';
|
|
7
|
+
|
|
8
|
+
const schema = Schema({
|
|
9
|
+
username: string.min(2).error('username field is required'),
|
|
10
|
+
password: string.min(2).error('password field is required'),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
type FormData = Type<typeof schema> & { unusedProperty: string };
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
onSubmit: (data: FormData) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function TestComponent({ onSubmit }: Props) {
|
|
20
|
+
const {
|
|
21
|
+
register,
|
|
22
|
+
handleSubmit,
|
|
23
|
+
formState: { errors },
|
|
24
|
+
} = useForm<FormData>({
|
|
25
|
+
resolver: computedTypesResolver(schema), // Useful to check TypeScript regressions
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<form onSubmit={handleSubmit(onSubmit)}>
|
|
30
|
+
<input {...register('username')} />
|
|
31
|
+
{errors.username && <span role="alert">{errors.username.message}</span>}
|
|
32
|
+
|
|
33
|
+
<input {...register('password')} />
|
|
34
|
+
{errors.password && <span role="alert">{errors.password.message}</span>}
|
|
35
|
+
|
|
36
|
+
<button type="submit">submit</button>
|
|
37
|
+
</form>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
test("form's validation with computed-types and TypeScript's integration", async () => {
|
|
42
|
+
const handleSubmit = jest.fn();
|
|
43
|
+
render(<TestComponent onSubmit={handleSubmit} />);
|
|
44
|
+
|
|
45
|
+
expect(screen.queryAllByRole(/alert/i)).toHaveLength(0);
|
|
46
|
+
|
|
47
|
+
await act(async () => {
|
|
48
|
+
user.click(screen.getByText(/submit/i));
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
expect(screen.getByText(/username field is required/i)).toBeInTheDocument();
|
|
52
|
+
expect(screen.getByText(/password field is required/i)).toBeInTheDocument();
|
|
53
|
+
expect(handleSubmit).not.toHaveBeenCalled();
|
|
54
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Field, InternalFieldName } from 'react-hook-form';
|
|
2
|
+
import Schema, { Type, string, number, array, boolean } from 'computed-types';
|
|
3
|
+
|
|
4
|
+
export const schema = Schema({
|
|
5
|
+
username: string.regexp(/^\w+$/).min(3).max(30),
|
|
6
|
+
password: string
|
|
7
|
+
.regexp(new RegExp('.*[A-Z].*'), 'One uppercase character')
|
|
8
|
+
.regexp(new RegExp('.*[a-z].*'), 'One lowercase character')
|
|
9
|
+
.regexp(new RegExp('.*\\d.*'), 'One number')
|
|
10
|
+
.regexp(
|
|
11
|
+
new RegExp('.*[`~<>?,./!@#$%^&*()\\-_+="\'|{}\\[\\];:\\\\].*'),
|
|
12
|
+
'One special character',
|
|
13
|
+
)
|
|
14
|
+
.min(8, 'Must be at least 8 characters in length'),
|
|
15
|
+
repeatPassword: string,
|
|
16
|
+
accessToken: Schema.either(string, number).optional(),
|
|
17
|
+
birthYear: number.min(1900).max(2013).optional(),
|
|
18
|
+
email: string
|
|
19
|
+
.regexp(/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/)
|
|
20
|
+
.error('Incorrect email'),
|
|
21
|
+
tags: array.of(string),
|
|
22
|
+
enabled: boolean,
|
|
23
|
+
like: array
|
|
24
|
+
.of({
|
|
25
|
+
id: number,
|
|
26
|
+
name: string.min(4).max(4),
|
|
27
|
+
})
|
|
28
|
+
.optional(),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export const validData: Type<typeof schema> = {
|
|
32
|
+
username: 'Doe',
|
|
33
|
+
password: 'Password123_',
|
|
34
|
+
repeatPassword: 'Password123_',
|
|
35
|
+
accessToken: 'accessToken',
|
|
36
|
+
birthYear: 2000,
|
|
37
|
+
email: 'john@doe.com',
|
|
38
|
+
tags: ['tag1', 'tag2'],
|
|
39
|
+
enabled: true,
|
|
40
|
+
like: [
|
|
41
|
+
{
|
|
42
|
+
id: 1,
|
|
43
|
+
name: 'name',
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const invalidData = {
|
|
49
|
+
password: '___',
|
|
50
|
+
email: '',
|
|
51
|
+
birthYear: 'birthYear',
|
|
52
|
+
like: [{ id: 'z' }],
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const fields: Record<InternalFieldName, Field['_f']> = {
|
|
56
|
+
username: {
|
|
57
|
+
ref: { name: 'username' },
|
|
58
|
+
name: 'username',
|
|
59
|
+
},
|
|
60
|
+
password: {
|
|
61
|
+
ref: { name: 'password' },
|
|
62
|
+
name: 'password',
|
|
63
|
+
},
|
|
64
|
+
email: {
|
|
65
|
+
ref: { name: 'email' },
|
|
66
|
+
name: 'email',
|
|
67
|
+
},
|
|
68
|
+
birthday: {
|
|
69
|
+
ref: { name: 'birthday' },
|
|
70
|
+
name: 'birthday',
|
|
71
|
+
},
|
|
72
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`computedTypesResolver should return a single error from computedTypesResolver when validation fails 1`] = `
|
|
4
|
+
Object {
|
|
5
|
+
"errors": Object {
|
|
6
|
+
"birthYear": Object {
|
|
7
|
+
"message": "Expect value to be \\"number\\"",
|
|
8
|
+
"ref": undefined,
|
|
9
|
+
"type": "ValidationError",
|
|
10
|
+
},
|
|
11
|
+
"email": Object {
|
|
12
|
+
"message": "Incorrect email",
|
|
13
|
+
"ref": Object {
|
|
14
|
+
"name": "email",
|
|
15
|
+
},
|
|
16
|
+
"type": "ValidationError",
|
|
17
|
+
},
|
|
18
|
+
"enabled": Object {
|
|
19
|
+
"message": "Expect value to be \\"boolean\\"",
|
|
20
|
+
"ref": undefined,
|
|
21
|
+
"type": "ValidationError",
|
|
22
|
+
},
|
|
23
|
+
"like": Object {
|
|
24
|
+
"id": Object {
|
|
25
|
+
"message": "Expect value to be \\"number\\"",
|
|
26
|
+
"ref": undefined,
|
|
27
|
+
"type": "ValidationError",
|
|
28
|
+
},
|
|
29
|
+
"message": "id: Expect value to be \\"number\\"",
|
|
30
|
+
"name": Object {
|
|
31
|
+
"message": "Expect value to be \\"string\\"",
|
|
32
|
+
"ref": undefined,
|
|
33
|
+
"type": "ValidationError",
|
|
34
|
+
},
|
|
35
|
+
"ref": undefined,
|
|
36
|
+
"type": "ValidationError",
|
|
37
|
+
},
|
|
38
|
+
"password": Object {
|
|
39
|
+
"message": "One uppercase character",
|
|
40
|
+
"ref": Object {
|
|
41
|
+
"name": "password",
|
|
42
|
+
},
|
|
43
|
+
"type": "ValidationError",
|
|
44
|
+
},
|
|
45
|
+
"repeatPassword": Object {
|
|
46
|
+
"message": "Expect value to be \\"string\\"",
|
|
47
|
+
"ref": undefined,
|
|
48
|
+
"type": "ValidationError",
|
|
49
|
+
},
|
|
50
|
+
"tags": Object {
|
|
51
|
+
"message": "Expecting value to be an array",
|
|
52
|
+
"ref": undefined,
|
|
53
|
+
"type": "ValidationError",
|
|
54
|
+
},
|
|
55
|
+
"username": Object {
|
|
56
|
+
"message": "Expect value to be \\"string\\"",
|
|
57
|
+
"ref": Object {
|
|
58
|
+
"name": "username",
|
|
59
|
+
},
|
|
60
|
+
"type": "ValidationError",
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
"values": Object {},
|
|
64
|
+
}
|
|
65
|
+
`;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { computedTypesResolver } from '..';
|
|
2
|
+
import { schema, validData, invalidData, fields } from './__fixtures__/data';
|
|
3
|
+
|
|
4
|
+
describe('computedTypesResolver', () => {
|
|
5
|
+
it('should return values from computedTypesResolver when validation pass', async () => {
|
|
6
|
+
const result = await computedTypesResolver(schema)(validData, undefined, {
|
|
7
|
+
fields,
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
expect(result).toEqual({ errors: {}, values: validData });
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it.only('should return a single error from computedTypesResolver when validation fails', async () => {
|
|
14
|
+
const result = await computedTypesResolver(schema)(invalidData, undefined, {
|
|
15
|
+
fields,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
expect(result).toMatchSnapshot();
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { FieldErrors } from 'react-hook-form';
|
|
2
|
+
import { toNestError } from '@hookform/resolvers';
|
|
3
|
+
import type { ValidationError } from 'computed-types';
|
|
4
|
+
import type { Resolver } from './types';
|
|
5
|
+
|
|
6
|
+
const parseErrorSchema = (
|
|
7
|
+
computedTypesError: ValidationError,
|
|
8
|
+
parsedErrors: FieldErrors = {},
|
|
9
|
+
path = '',
|
|
10
|
+
) => {
|
|
11
|
+
return (computedTypesError.errors || []).reduce((acc, error) => {
|
|
12
|
+
const _currentPath = String(error.path[0]);
|
|
13
|
+
const _path = path ? `${path}.${_currentPath}` : _currentPath;
|
|
14
|
+
|
|
15
|
+
acc[_path] = {
|
|
16
|
+
type: error.error.name,
|
|
17
|
+
message: error.error.message,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
parseErrorSchema(error.error, acc, _path);
|
|
21
|
+
|
|
22
|
+
return acc;
|
|
23
|
+
}, parsedErrors);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const computedTypesResolver: Resolver = (schema) => async (
|
|
27
|
+
values,
|
|
28
|
+
_,
|
|
29
|
+
options,
|
|
30
|
+
) => {
|
|
31
|
+
try {
|
|
32
|
+
return {
|
|
33
|
+
errors: {},
|
|
34
|
+
values: await schema(values),
|
|
35
|
+
};
|
|
36
|
+
} catch (error) {
|
|
37
|
+
return {
|
|
38
|
+
values: {},
|
|
39
|
+
errors: toNestError(parseErrorSchema(error), options.fields),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
FieldValues,
|
|
3
|
+
ResolverResult,
|
|
4
|
+
UnpackNestedValue,
|
|
5
|
+
ResolverOptions,
|
|
6
|
+
} from 'react-hook-form';
|
|
7
|
+
|
|
8
|
+
export type Resolver = (
|
|
9
|
+
schema: any,
|
|
10
|
+
) => <TFieldValues extends FieldValues, TContext>(
|
|
11
|
+
values: UnpackNestedValue<TFieldValues>,
|
|
12
|
+
context: TContext | undefined,
|
|
13
|
+
options: ResolverOptions<TFieldValues>,
|
|
14
|
+
) => Promise<ResolverResult<TFieldValues>>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, act } from '@testing-library/react';
|
|
3
|
+
import user from '@testing-library/user-event';
|
|
4
|
+
import { useForm } from 'react-hook-form';
|
|
5
|
+
import * as t from 'io-ts';
|
|
6
|
+
import * as tt from 'io-ts-types';
|
|
7
|
+
import { ioTsResolver } from '..';
|
|
8
|
+
|
|
9
|
+
const schema = t.type({
|
|
10
|
+
username: tt.withMessage(
|
|
11
|
+
tt.NonEmptyString,
|
|
12
|
+
() => 'username is a required field',
|
|
13
|
+
),
|
|
14
|
+
password: tt.withMessage(
|
|
15
|
+
tt.NonEmptyString,
|
|
16
|
+
() => 'password is a required field',
|
|
17
|
+
),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
interface FormData {
|
|
21
|
+
username: string;
|
|
22
|
+
password: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface Props {
|
|
26
|
+
onSubmit: (data: FormData) => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function TestComponent({ onSubmit }: Props) {
|
|
30
|
+
const {
|
|
31
|
+
register,
|
|
32
|
+
formState: { errors },
|
|
33
|
+
handleSubmit,
|
|
34
|
+
} = useForm<FormData>({
|
|
35
|
+
resolver: ioTsResolver(schema),
|
|
36
|
+
criteriaMode: 'all',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<form onSubmit={handleSubmit(onSubmit)}>
|
|
41
|
+
<input {...register('username')} />
|
|
42
|
+
{errors.username && <span role="alert">{errors.username.message}</span>}
|
|
43
|
+
|
|
44
|
+
<input {...register('password')} />
|
|
45
|
+
{errors.password && <span role="alert">{errors.password.message}</span>}
|
|
46
|
+
|
|
47
|
+
<button type="submit">submit</button>
|
|
48
|
+
</form>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
test("form's validation with io-ts and TypeScript's integration", async () => {
|
|
53
|
+
const handleSubmit = jest.fn();
|
|
54
|
+
render(<TestComponent onSubmit={handleSubmit} />);
|
|
55
|
+
|
|
56
|
+
expect(screen.queryAllByRole(/alert/i)).toHaveLength(0);
|
|
57
|
+
|
|
58
|
+
await act(async () => {
|
|
59
|
+
user.click(screen.getByText(/submit/i));
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
expect(screen.getByText(/username is a required field/i)).toBeInTheDocument();
|
|
63
|
+
expect(screen.getByText(/password is a required field/i)).toBeInTheDocument();
|
|
64
|
+
expect(handleSubmit).not.toHaveBeenCalled();
|
|
65
|
+
});
|