@zod-utils/react-hook-form 0.2.0 → 0.4.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 +116 -20
- package/dist/index.d.mts +31 -66
- package/dist/index.d.ts +31 -66
- package/dist/index.js.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@zod-utils/react-hook-form)
|
|
4
4
|
[](https://www.npmjs.com/package/@zod-utils/react-hook-form)
|
|
5
|
+
[](https://bundlephobia.com/package/@zod-utils/react-hook-form)
|
|
5
6
|
[](https://opensource.org/licenses/MIT)
|
|
6
7
|
[](https://www.typescriptlang.org/)
|
|
7
8
|
[](https://github.com/thu-san/zod-utils/actions)
|
|
@@ -11,7 +12,7 @@ React Hook Form integration and utilities for Zod schemas.
|
|
|
11
12
|
|
|
12
13
|
## 💡 Why Use This?
|
|
13
14
|
|
|
14
|
-
**The whole point:** Automatically transforms your Zod schema types so form inputs
|
|
15
|
+
**The whole point:** Automatically transforms your Zod schema types so form inputs accept `undefined` (and `null` for objects only) during editing, while the validated output remains exactly as your Zod schema defines.
|
|
15
16
|
|
|
16
17
|
No more type wrestling with React Hook Form - just pass your schema and it works.
|
|
17
18
|
|
|
@@ -19,27 +20,33 @@ No more type wrestling with React Hook Form - just pass your schema and it works
|
|
|
19
20
|
import { useZodForm } from "@zod-utils/react-hook-form";
|
|
20
21
|
import { z } from "zod";
|
|
21
22
|
|
|
22
|
-
// Your schema
|
|
23
|
+
// Your schema with primitives, arrays, and objects - NOT optional
|
|
23
24
|
const schema = z.object({
|
|
24
25
|
username: z.string().min(3),
|
|
25
|
-
|
|
26
|
+
age: z.number().min(18),
|
|
27
|
+
tags: z.array(z.string()),
|
|
28
|
+
profile: z.object({ bio: z.string() }),
|
|
26
29
|
});
|
|
27
30
|
|
|
28
31
|
const form = useZodForm({ schema });
|
|
29
32
|
|
|
30
|
-
// ✅ Works!
|
|
31
|
-
form.
|
|
33
|
+
// ✅ Works! Primitives and arrays accept undefined during editing
|
|
34
|
+
form.setValue("username", undefined);
|
|
35
|
+
form.setValue("age", undefined);
|
|
36
|
+
form.setValue("tags", undefined);
|
|
32
37
|
|
|
33
|
-
// ✅ Works!
|
|
34
|
-
form.setValue("
|
|
35
|
-
form.setValue("
|
|
38
|
+
// ✅ Works! Objects accept both null and undefined
|
|
39
|
+
form.setValue("profile", null);
|
|
40
|
+
form.setValue("profile", undefined);
|
|
36
41
|
|
|
37
42
|
// ✅ Validated output type is exactly z.infer<typeof schema>
|
|
38
43
|
const onSubmit = form.handleSubmit((data) => {
|
|
39
|
-
// Type: { username: string;
|
|
40
|
-
// NOT { username: string | null | undefined;
|
|
44
|
+
// Type: { username: string; age: number; tags: string[]; profile: { bio: string } }
|
|
45
|
+
// NOT { username: string | null | undefined; ... }
|
|
41
46
|
console.log(data.username); // Type: string
|
|
42
|
-
console.log(data.
|
|
47
|
+
console.log(data.age); // Type: number
|
|
48
|
+
console.log(data.tags); // Type: string[]
|
|
49
|
+
console.log(data.profile); // Type: { bio: string }
|
|
43
50
|
});
|
|
44
51
|
```
|
|
45
52
|
|
|
@@ -49,6 +56,10 @@ const onSubmit = form.handleSubmit((data) => {
|
|
|
49
56
|
npm install @zod-utils/react-hook-form zod react react-hook-form @hookform/resolvers
|
|
50
57
|
```
|
|
51
58
|
|
|
59
|
+
## Related Packages
|
|
60
|
+
|
|
61
|
+
- **[@zod-utils/core](https://www.npmjs.com/package/@zod-utils/core)** - Pure TypeScript utilities for Zod schema manipulation (no React dependencies). All utilities are re-exported from this package for convenience.
|
|
62
|
+
|
|
52
63
|
## Features
|
|
53
64
|
|
|
54
65
|
- 🎣 **useZodForm** - Automatic type transformation for form inputs (nullable/undefined) while preserving Zod schema validation
|
|
@@ -117,11 +128,55 @@ const form = useZodForm({
|
|
|
117
128
|
|
|
118
129
|
**What it does:**
|
|
119
130
|
|
|
120
|
-
- **Input transformation
|
|
131
|
+
- **Input transformation** (by default):
|
|
132
|
+
- **Primitive fields** (string, number, boolean) accept `undefined` only
|
|
133
|
+
- **Array fields** accept `undefined` only
|
|
134
|
+
- **Object fields** accept both `null` and `undefined`
|
|
135
|
+
- You can override this by specifying a custom input type (see examples below)
|
|
121
136
|
- **Output validation**: Validated data matches your Zod schema exactly
|
|
122
137
|
- **Type inference**: No manual type annotations needed - everything is inferred from the schema
|
|
123
138
|
- **Zod integration**: Automatically sets up `zodResolver` for validation
|
|
124
139
|
|
|
140
|
+
#### Custom Input Types
|
|
141
|
+
|
|
142
|
+
You can override the default input type transformation if needed:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
import {
|
|
146
|
+
useZodForm,
|
|
147
|
+
PartialWithAllNullables,
|
|
148
|
+
} from "@zod-utils/react-hook-form";
|
|
149
|
+
import { z } from "zod";
|
|
150
|
+
|
|
151
|
+
const schema = z.object({
|
|
152
|
+
username: z.string().min(3),
|
|
153
|
+
email: z.string().email(),
|
|
154
|
+
age: z.number(),
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Option 1: Use PartialWithAllNullables to make ALL fields accept null
|
|
158
|
+
const form = useZodForm<
|
|
159
|
+
z.infer<typeof schema>,
|
|
160
|
+
PartialWithAllNullables<z.infer<typeof schema>>
|
|
161
|
+
>({
|
|
162
|
+
schema,
|
|
163
|
+
defaultValues: { username: null, email: null, age: null },
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Option 2: Specify exact input types per field
|
|
167
|
+
const form2 = useZodForm<
|
|
168
|
+
z.infer<typeof schema>,
|
|
169
|
+
{
|
|
170
|
+
username?: string | null; // Can be set to null
|
|
171
|
+
email?: string; // Can only be undefined
|
|
172
|
+
age?: number | null; // Can be set to null
|
|
173
|
+
}
|
|
174
|
+
>({
|
|
175
|
+
schema,
|
|
176
|
+
defaultValues: { username: null, email: undefined, age: null },
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
125
180
|
---
|
|
126
181
|
|
|
127
182
|
## Core Utilities (Re-exported)
|
|
@@ -132,14 +187,15 @@ All utilities from `@zod-utils/core` are re-exported for convenience:
|
|
|
132
187
|
import {
|
|
133
188
|
// Schema utilities (from @zod-utils/core)
|
|
134
189
|
getSchemaDefaults,
|
|
135
|
-
|
|
190
|
+
requiresValidInput,
|
|
136
191
|
getPrimitiveType,
|
|
137
192
|
removeDefault,
|
|
138
193
|
extractDefault,
|
|
139
194
|
type Simplify,
|
|
140
195
|
|
|
141
196
|
// Type utilities (react-hook-form specific)
|
|
142
|
-
type
|
|
197
|
+
type PartialWithNullableObjects,
|
|
198
|
+
type PartialWithAllNullables,
|
|
143
199
|
} from "@zod-utils/react-hook-form";
|
|
144
200
|
```
|
|
145
201
|
|
|
@@ -147,23 +203,63 @@ See [@zod-utils/core documentation](../core/README.md) for details on schema uti
|
|
|
147
203
|
|
|
148
204
|
### Type Utilities
|
|
149
205
|
|
|
150
|
-
#### `
|
|
206
|
+
#### `PartialWithNullableObjects<T>`
|
|
207
|
+
|
|
208
|
+
Transforms properties based on their type. Primitive and array fields become optional-only (not nullable), while object fields become optional and nullable.
|
|
209
|
+
|
|
210
|
+
**Transformation rules:**
|
|
211
|
+
|
|
212
|
+
- **Primitives** (string, number, boolean): optional → `type | undefined`
|
|
213
|
+
- **Arrays**: optional → `type[] | undefined`
|
|
214
|
+
- **Objects**: optional and nullable → `type | null | undefined`
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
import type { PartialWithNullableObjects } from "@zod-utils/react-hook-form";
|
|
218
|
+
|
|
219
|
+
type User = {
|
|
220
|
+
name: string;
|
|
221
|
+
age: number;
|
|
222
|
+
tags: string[];
|
|
223
|
+
profile: { bio: string };
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
type FormInput = PartialWithNullableObjects<User>;
|
|
227
|
+
// {
|
|
228
|
+
// name?: string; // Primitive: optional, not nullable
|
|
229
|
+
// age?: number; // Primitive: optional, not nullable
|
|
230
|
+
// tags?: string[]; // Array: optional, not nullable
|
|
231
|
+
// profile?: { bio: string } | null; // Object: optional AND nullable
|
|
232
|
+
// }
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
This type is used internally by `useZodForm` to allow form fields to accept undefined (and null for objects only) during editing while maintaining proper validation types.
|
|
236
|
+
|
|
237
|
+
#### `PartialWithAllNullables<T>`
|
|
238
|
+
|
|
239
|
+
Makes all fields optional and nullable, regardless of type.
|
|
240
|
+
|
|
241
|
+
**Transformation rules:**
|
|
151
242
|
|
|
152
|
-
|
|
243
|
+
- **All fields**: optional and nullable → `type | null | undefined`
|
|
153
244
|
|
|
154
245
|
```typescript
|
|
155
|
-
import type {
|
|
246
|
+
import type { PartialWithAllNullables } from "@zod-utils/react-hook-form";
|
|
156
247
|
|
|
157
248
|
type User = {
|
|
158
249
|
name: string;
|
|
159
250
|
age: number;
|
|
251
|
+
tags: string[];
|
|
160
252
|
};
|
|
161
253
|
|
|
162
|
-
type FormInput =
|
|
163
|
-
// {
|
|
254
|
+
type FormInput = PartialWithAllNullables<User>;
|
|
255
|
+
// {
|
|
256
|
+
// name?: string | null; // All fields: optional AND nullable
|
|
257
|
+
// age?: number | null; // All fields: optional AND nullable
|
|
258
|
+
// tags?: string[] | null; // All fields: optional AND nullable
|
|
259
|
+
// }
|
|
164
260
|
```
|
|
165
261
|
|
|
166
|
-
|
|
262
|
+
Use this when all fields need to accept `null`, not just objects/arrays.
|
|
167
263
|
|
|
168
264
|
---
|
|
169
265
|
|
package/dist/index.d.mts
CHANGED
|
@@ -5,75 +5,40 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
*
|
|
16
|
-
* - Zod schemas define strict output types without these nullable/optional wrappers
|
|
17
|
-
* - This type bridges the gap, eliminating "Type 'null' is not assignable to..." errors
|
|
8
|
+
* Helper type that adds `null` to object-type fields only (excludes arrays).
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
type AddNullToObjects<T> = {
|
|
12
|
+
[K in keyof T]: T[K] extends readonly unknown[] ? T[K] : T[K] extends object ? T[K] | null : T[K];
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Transforms Zod schema types for form inputs.
|
|
18
16
|
*
|
|
19
|
-
*
|
|
17
|
+
* - **Primitives** (string, number, boolean): optional → `type | undefined`
|
|
18
|
+
* - **Arrays**: optional → `type[] | undefined`
|
|
19
|
+
* - **Objects**: optional and nullable → `type | null | undefined`
|
|
20
20
|
*
|
|
21
21
|
* @example
|
|
22
|
-
* Basic transformation
|
|
23
22
|
* ```typescript
|
|
24
|
-
* type User = {
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* };
|
|
28
|
-
*
|
|
29
|
-
* type FormUser = MakeOptionalAndNullable<User>;
|
|
30
|
-
* // Result: {
|
|
31
|
-
* // name?: string | null;
|
|
32
|
-
* // age?: number | null;
|
|
33
|
-
* // }
|
|
23
|
+
* type User = { name: string; tags: string[]; profile: { bio: string } };
|
|
24
|
+
* type FormInput = PartialWithNullableObjects<User>;
|
|
25
|
+
* // { name?: string; tags?: string[]; profile?: { bio: string } | null; }
|
|
34
26
|
* ```
|
|
27
|
+
*/
|
|
28
|
+
type PartialWithNullableObjects<T> = Partial<AddNullToObjects<T>>;
|
|
29
|
+
/**
|
|
30
|
+
* Makes all fields optional and nullable.
|
|
35
31
|
*
|
|
36
|
-
*
|
|
37
|
-
* Usage with useZodForm
|
|
38
|
-
* ```typescript
|
|
39
|
-
* const schema = z.object({
|
|
40
|
-
* title: z.string(),
|
|
41
|
-
* count: z.number(),
|
|
42
|
-
* });
|
|
43
|
-
*
|
|
44
|
-
* const form = useZodForm({ schema });
|
|
45
|
-
*
|
|
46
|
-
* // ✅ These work without type errors:
|
|
47
|
-
* form.setValue('title', null); // Accepts null during editing
|
|
48
|
-
* form.setValue('title', undefined); // Accepts undefined
|
|
49
|
-
* form.reset({ title: null, count: null }); // Reset with null values
|
|
50
|
-
*
|
|
51
|
-
* // But validated output is still:
|
|
52
|
-
* // { title: string, count: number }
|
|
53
|
-
* ```
|
|
32
|
+
* - **All fields**: optional and nullable → `type | null | undefined`
|
|
54
33
|
*
|
|
55
34
|
* @example
|
|
56
|
-
* Comparison with original type
|
|
57
35
|
* ```typescript
|
|
58
|
-
* type
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* };
|
|
62
|
-
*
|
|
63
|
-
* // Original: { email: string; isActive: boolean }
|
|
64
|
-
* // Transformed: { email?: string | null; isActive?: boolean | null }
|
|
65
|
-
*
|
|
66
|
-
* const original: Schema = { email: '', isActive: true }; // OK
|
|
67
|
-
* const original2: Schema = { email: null }; // ❌ Error
|
|
68
|
-
*
|
|
69
|
-
* const transformed: MakeOptionalAndNullable<Schema> = {}; // OK
|
|
70
|
-
* const transformed2: MakeOptionalAndNullable<Schema> = { email: null }; // OK
|
|
36
|
+
* type User = { name: string; age: number; tags: string[] };
|
|
37
|
+
* type FormInput = PartialWithAllNullables<User>;
|
|
38
|
+
* // { name?: string | null; age?: number | null; tags?: string[] | null; }
|
|
71
39
|
* ```
|
|
72
|
-
*
|
|
73
|
-
* @see {@link useZodForm} for how this type is used in practice
|
|
74
|
-
* @since 0.1.0
|
|
75
40
|
*/
|
|
76
|
-
type
|
|
41
|
+
type PartialWithAllNullables<T> = {
|
|
77
42
|
[K in keyof T]?: T[K] | null;
|
|
78
43
|
};
|
|
79
44
|
|
|
@@ -82,7 +47,7 @@ type MakeOptionalAndNullable<T> = {
|
|
|
82
47
|
*
|
|
83
48
|
* This hook eliminates the TypeScript friction between React Hook Form's nullable field values
|
|
84
49
|
* and Zod's strict output types. It uses a two-type schema pattern where:
|
|
85
|
-
* - **Input type** (`
|
|
50
|
+
* - **Input type** (`PartialWithNullableObjects<T>`): Form fields accept `null | undefined` during editing
|
|
86
51
|
* - **Output type** (`T`): Validated data matches exact schema type (no `null | undefined`)
|
|
87
52
|
*
|
|
88
53
|
* **Key Benefits:**
|
|
@@ -94,7 +59,7 @@ type MakeOptionalAndNullable<T> = {
|
|
|
94
59
|
* @template T - The Zod schema output type (extends FieldValues)
|
|
95
60
|
*
|
|
96
61
|
* @param options - Configuration object
|
|
97
|
-
* @param options.schema - Zod schema with two-type signature `z.ZodType<T,
|
|
62
|
+
* @param options.schema - Zod schema with two-type signature `z.ZodType<T, PartialWithNullableObjects<T>>`
|
|
98
63
|
* @param options.defaultValues - Default form values (accepts nullable/undefined values)
|
|
99
64
|
* @param options.zodResolverOptions - Optional zodResolver configuration
|
|
100
65
|
* @param options....formOptions - All other react-hook-form useForm options
|
|
@@ -215,15 +180,15 @@ type MakeOptionalAndNullable<T> = {
|
|
|
215
180
|
* }
|
|
216
181
|
* ```
|
|
217
182
|
*
|
|
218
|
-
* @see {@link
|
|
183
|
+
* @see {@link PartialWithNullableObjects} for the type transformation utility
|
|
219
184
|
* @see https://react-hook-form.com/docs/useform for React Hook Form documentation
|
|
220
185
|
* @see https://zod.dev for Zod schema documentation
|
|
221
186
|
* @since 0.1.0
|
|
222
187
|
*/
|
|
223
|
-
declare const useZodForm: <T extends FieldValues>({ schema, zodResolverOptions, ...formOptions }: {
|
|
224
|
-
schema: z.ZodType<T,
|
|
225
|
-
defaultValues?: DefaultValues<
|
|
188
|
+
declare const useZodForm: <T extends FieldValues, I extends PartialWithAllNullables<T> = PartialWithNullableObjects<T>>({ schema, zodResolverOptions, ...formOptions }: {
|
|
189
|
+
schema: z.ZodType<T, I>;
|
|
190
|
+
defaultValues?: DefaultValues<I>;
|
|
226
191
|
zodResolverOptions?: Parameters<typeof zodResolver>[1];
|
|
227
|
-
} & Omit<UseFormProps<
|
|
192
|
+
} & Omit<UseFormProps<I, unknown, T>, "resolver" | "defaultValues">) => react_hook_form.UseFormReturn<I, unknown, T>;
|
|
228
193
|
|
|
229
|
-
export { type
|
|
194
|
+
export { type PartialWithAllNullables, type PartialWithNullableObjects, useZodForm };
|
package/dist/index.d.ts
CHANGED
|
@@ -5,75 +5,40 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
*
|
|
16
|
-
* - Zod schemas define strict output types without these nullable/optional wrappers
|
|
17
|
-
* - This type bridges the gap, eliminating "Type 'null' is not assignable to..." errors
|
|
8
|
+
* Helper type that adds `null` to object-type fields only (excludes arrays).
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
type AddNullToObjects<T> = {
|
|
12
|
+
[K in keyof T]: T[K] extends readonly unknown[] ? T[K] : T[K] extends object ? T[K] | null : T[K];
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Transforms Zod schema types for form inputs.
|
|
18
16
|
*
|
|
19
|
-
*
|
|
17
|
+
* - **Primitives** (string, number, boolean): optional → `type | undefined`
|
|
18
|
+
* - **Arrays**: optional → `type[] | undefined`
|
|
19
|
+
* - **Objects**: optional and nullable → `type | null | undefined`
|
|
20
20
|
*
|
|
21
21
|
* @example
|
|
22
|
-
* Basic transformation
|
|
23
22
|
* ```typescript
|
|
24
|
-
* type User = {
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* };
|
|
28
|
-
*
|
|
29
|
-
* type FormUser = MakeOptionalAndNullable<User>;
|
|
30
|
-
* // Result: {
|
|
31
|
-
* // name?: string | null;
|
|
32
|
-
* // age?: number | null;
|
|
33
|
-
* // }
|
|
23
|
+
* type User = { name: string; tags: string[]; profile: { bio: string } };
|
|
24
|
+
* type FormInput = PartialWithNullableObjects<User>;
|
|
25
|
+
* // { name?: string; tags?: string[]; profile?: { bio: string } | null; }
|
|
34
26
|
* ```
|
|
27
|
+
*/
|
|
28
|
+
type PartialWithNullableObjects<T> = Partial<AddNullToObjects<T>>;
|
|
29
|
+
/**
|
|
30
|
+
* Makes all fields optional and nullable.
|
|
35
31
|
*
|
|
36
|
-
*
|
|
37
|
-
* Usage with useZodForm
|
|
38
|
-
* ```typescript
|
|
39
|
-
* const schema = z.object({
|
|
40
|
-
* title: z.string(),
|
|
41
|
-
* count: z.number(),
|
|
42
|
-
* });
|
|
43
|
-
*
|
|
44
|
-
* const form = useZodForm({ schema });
|
|
45
|
-
*
|
|
46
|
-
* // ✅ These work without type errors:
|
|
47
|
-
* form.setValue('title', null); // Accepts null during editing
|
|
48
|
-
* form.setValue('title', undefined); // Accepts undefined
|
|
49
|
-
* form.reset({ title: null, count: null }); // Reset with null values
|
|
50
|
-
*
|
|
51
|
-
* // But validated output is still:
|
|
52
|
-
* // { title: string, count: number }
|
|
53
|
-
* ```
|
|
32
|
+
* - **All fields**: optional and nullable → `type | null | undefined`
|
|
54
33
|
*
|
|
55
34
|
* @example
|
|
56
|
-
* Comparison with original type
|
|
57
35
|
* ```typescript
|
|
58
|
-
* type
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* };
|
|
62
|
-
*
|
|
63
|
-
* // Original: { email: string; isActive: boolean }
|
|
64
|
-
* // Transformed: { email?: string | null; isActive?: boolean | null }
|
|
65
|
-
*
|
|
66
|
-
* const original: Schema = { email: '', isActive: true }; // OK
|
|
67
|
-
* const original2: Schema = { email: null }; // ❌ Error
|
|
68
|
-
*
|
|
69
|
-
* const transformed: MakeOptionalAndNullable<Schema> = {}; // OK
|
|
70
|
-
* const transformed2: MakeOptionalAndNullable<Schema> = { email: null }; // OK
|
|
36
|
+
* type User = { name: string; age: number; tags: string[] };
|
|
37
|
+
* type FormInput = PartialWithAllNullables<User>;
|
|
38
|
+
* // { name?: string | null; age?: number | null; tags?: string[] | null; }
|
|
71
39
|
* ```
|
|
72
|
-
*
|
|
73
|
-
* @see {@link useZodForm} for how this type is used in practice
|
|
74
|
-
* @since 0.1.0
|
|
75
40
|
*/
|
|
76
|
-
type
|
|
41
|
+
type PartialWithAllNullables<T> = {
|
|
77
42
|
[K in keyof T]?: T[K] | null;
|
|
78
43
|
};
|
|
79
44
|
|
|
@@ -82,7 +47,7 @@ type MakeOptionalAndNullable<T> = {
|
|
|
82
47
|
*
|
|
83
48
|
* This hook eliminates the TypeScript friction between React Hook Form's nullable field values
|
|
84
49
|
* and Zod's strict output types. It uses a two-type schema pattern where:
|
|
85
|
-
* - **Input type** (`
|
|
50
|
+
* - **Input type** (`PartialWithNullableObjects<T>`): Form fields accept `null | undefined` during editing
|
|
86
51
|
* - **Output type** (`T`): Validated data matches exact schema type (no `null | undefined`)
|
|
87
52
|
*
|
|
88
53
|
* **Key Benefits:**
|
|
@@ -94,7 +59,7 @@ type MakeOptionalAndNullable<T> = {
|
|
|
94
59
|
* @template T - The Zod schema output type (extends FieldValues)
|
|
95
60
|
*
|
|
96
61
|
* @param options - Configuration object
|
|
97
|
-
* @param options.schema - Zod schema with two-type signature `z.ZodType<T,
|
|
62
|
+
* @param options.schema - Zod schema with two-type signature `z.ZodType<T, PartialWithNullableObjects<T>>`
|
|
98
63
|
* @param options.defaultValues - Default form values (accepts nullable/undefined values)
|
|
99
64
|
* @param options.zodResolverOptions - Optional zodResolver configuration
|
|
100
65
|
* @param options....formOptions - All other react-hook-form useForm options
|
|
@@ -215,15 +180,15 @@ type MakeOptionalAndNullable<T> = {
|
|
|
215
180
|
* }
|
|
216
181
|
* ```
|
|
217
182
|
*
|
|
218
|
-
* @see {@link
|
|
183
|
+
* @see {@link PartialWithNullableObjects} for the type transformation utility
|
|
219
184
|
* @see https://react-hook-form.com/docs/useform for React Hook Form documentation
|
|
220
185
|
* @see https://zod.dev for Zod schema documentation
|
|
221
186
|
* @since 0.1.0
|
|
222
187
|
*/
|
|
223
|
-
declare const useZodForm: <T extends FieldValues>({ schema, zodResolverOptions, ...formOptions }: {
|
|
224
|
-
schema: z.ZodType<T,
|
|
225
|
-
defaultValues?: DefaultValues<
|
|
188
|
+
declare const useZodForm: <T extends FieldValues, I extends PartialWithAllNullables<T> = PartialWithNullableObjects<T>>({ schema, zodResolverOptions, ...formOptions }: {
|
|
189
|
+
schema: z.ZodType<T, I>;
|
|
190
|
+
defaultValues?: DefaultValues<I>;
|
|
226
191
|
zodResolverOptions?: Parameters<typeof zodResolver>[1];
|
|
227
|
-
} & Omit<UseFormProps<
|
|
192
|
+
} & Omit<UseFormProps<I, unknown, T>, "resolver" | "defaultValues">) => react_hook_form.UseFormReturn<I, unknown, T>;
|
|
228
193
|
|
|
229
|
-
export { type
|
|
194
|
+
export { type PartialWithAllNullables, type PartialWithNullableObjects, useZodForm };
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/use-zod-form.ts"],"names":["zodResolver","useForm"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"sources":["../src/use-zod-form.ts"],"names":["zodResolver","useForm"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4JO,IAAM,UAAA,GAAa,CAGxB,EAAA,KAQsE;AARtE,EAAA,IAAA,EAAA,GAAA,EAAA,EACA;AAAA,IAAA,MAAA;AAAA,IACA;AAAA,GAjKF,GA+JE,EAAA,EAGG,WAAA,GAAA,SAAA,CAHH,EAAA,EAGG;AAAA,IAFH,QAAA;AAAA,IACA;AAAA,GAAA,CAAA;AAOA,EAAA,MAAM,QAAA,GAAWA,eAAA,CAAY,MAAA,EAAQ,kBAAkB,CAAA;AAEvD,EAAA,OAAOC,qBAAA,CAAQ,cAAA,CAAA;AAAA,IACb;AAAA,GAAA,EACG,WAAA,CACJ,CAAA;AACH","file":"index.js","sourcesContent":["import { zodResolver } from '@hookform/resolvers/zod';\nimport {\n type DefaultValues,\n type FieldValues,\n type UseFormProps,\n useForm,\n} from 'react-hook-form';\nimport type { z } from 'zod';\nimport type {\n PartialWithAllNullables,\n PartialWithNullableObjects,\n} from './types';\n\n/**\n * Type-safe React Hook Form wrapper with automatic Zod v4 schema validation and type transformation.\n *\n * This hook eliminates the TypeScript friction between React Hook Form's nullable field values\n * and Zod's strict output types. It uses a two-type schema pattern where:\n * - **Input type** (`PartialWithNullableObjects<T>`): Form fields accept `null | undefined` during editing\n * - **Output type** (`T`): Validated data matches exact schema type (no `null | undefined`)\n *\n * **Key Benefits:**\n * - ✅ No more \"Type 'null' is not assignable to...\" TypeScript errors\n * - ✅ Use `form.setValue()` and `form.reset()` with `null` values freely\n * - ✅ Validated output is still type-safe with exact Zod schema types\n * - ✅ Automatic zodResolver setup - no manual configuration needed\n *\n * @template T - The Zod schema output type (extends FieldValues)\n *\n * @param options - Configuration object\n * @param options.schema - Zod schema with two-type signature `z.ZodType<T, PartialWithNullableObjects<T>>`\n * @param options.defaultValues - Default form values (accepts nullable/undefined values)\n * @param options.zodResolverOptions - Optional zodResolver configuration\n * @param options....formOptions - All other react-hook-form useForm options\n *\n * @returns React Hook Form instance with type-safe methods\n *\n * @example\n * Basic usage with required fields\n * ```typescript\n * import { useZodForm } from '@zod-utils/react-hook-form';\n * import { z } from 'zod';\n *\n * const schema = z.object({\n * name: z.string().min(1), // Required field\n * age: z.number().min(0),\n * }) satisfies z.ZodType<{ name: string; age: number }, any>;\n *\n * function MyForm() {\n * const form = useZodForm({ schema });\n *\n * // ✅ These work without type errors:\n * form.setValue('name', null); // Accepts null during editing\n * form.reset({ name: null, age: null }); // Reset with null\n *\n * const onSubmit = (data: { name: string; age: number }) => {\n * // ✅ data is exact type - no null | undefined\n * console.log(data.name.toUpperCase()); // Safe to use string methods\n * };\n *\n * return <form onSubmit={form.handleSubmit(onSubmit)}>...</form>;\n * }\n * ```\n *\n * @example\n * With default values\n * ```typescript\n * const schema = z.object({\n * username: z.string(),\n * email: z.string().email(),\n * notifications: z.boolean().default(true),\n * }) satisfies z.ZodType<{\n * username: string;\n * email: string;\n * notifications: boolean;\n * }, any>;\n *\n * const form = useZodForm({\n * schema,\n * defaultValues: {\n * username: '',\n * email: '',\n * // notifications gets default from schema\n * },\n * });\n * ```\n *\n * @example\n * With optional and nullable fields\n * ```typescript\n * const schema = z.object({\n * title: z.string(),\n * description: z.string().optional(), // Optional in output\n * tags: z.array(z.string()).nullable(), // Nullable in output\n * }) satisfies z.ZodType<{\n * title: string;\n * description?: string;\n * tags: string[] | null;\n * }, any>;\n *\n * const form = useZodForm({ schema });\n *\n * // All fields accept null/undefined during editing\n * form.setValue('title', null);\n * form.setValue('description', undefined);\n * form.setValue('tags', null);\n * ```\n *\n * @example\n * With zodResolver options\n * ```typescript\n * const form = useZodForm({\n * schema,\n * zodResolverOptions: {\n * async: true, // Enable async validation\n * errorMap: customErrorMap, // Custom error messages\n * },\n * });\n * ```\n *\n * @example\n * Complete form example\n * ```typescript\n * const userSchema = z.object({\n * name: z.string().min(1, 'Name is required'),\n * email: z.string().email('Invalid email'),\n * age: z.number().min(18, 'Must be 18+'),\n * }) satisfies z.ZodType<{ name: string; email: string; age: number }, any>;\n *\n * function UserForm() {\n * const form = useZodForm({\n * schema: userSchema,\n * defaultValues: { name: '', email: '', age: null },\n * });\n *\n * const onSubmit = (data: { name: string; email: string; age: number }) => {\n * // Type-safe: data has exact types, no null/undefined\n * console.log(`${data.name} is ${data.age} years old`);\n * };\n *\n * return (\n * <form onSubmit={form.handleSubmit(onSubmit)}>\n * <input {...form.register('name')} />\n * <input {...form.register('email')} type=\"email\" />\n * <input {...form.register('age', { valueAsNumber: true })} type=\"number\" />\n * <button type=\"submit\">Submit</button>\n * </form>\n * );\n * }\n * ```\n *\n * @see {@link PartialWithNullableObjects} for the type transformation utility\n * @see https://react-hook-form.com/docs/useform for React Hook Form documentation\n * @see https://zod.dev for Zod schema documentation\n * @since 0.1.0\n */\nexport const useZodForm = <\n T extends FieldValues,\n I extends PartialWithAllNullables<T> = PartialWithNullableObjects<T>,\n>({\n schema,\n zodResolverOptions,\n ...formOptions\n}: {\n schema: z.ZodType<T, I>;\n defaultValues?: DefaultValues<I>;\n zodResolverOptions?: Parameters<typeof zodResolver>[1];\n} & Omit<UseFormProps<I, unknown, T>, 'resolver' | 'defaultValues'>) => {\n const resolver = zodResolver(schema, zodResolverOptions);\n\n return useForm({\n resolver,\n ...formOptions,\n });\n};\n"]}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/use-zod-form.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"sources":["../src/use-zod-form.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4JO,IAAM,UAAA,GAAa,CAGxB,EAAA,KAQsE;AARtE,EAAA,IAAA,EAAA,GAAA,EAAA,EACA;AAAA,IAAA,MAAA;AAAA,IACA;AAAA,GAjKF,GA+JE,EAAA,EAGG,WAAA,GAAA,SAAA,CAHH,EAAA,EAGG;AAAA,IAFH,QAAA;AAAA,IACA;AAAA,GAAA,CAAA;AAOA,EAAA,MAAM,QAAA,GAAW,WAAA,CAAY,MAAA,EAAQ,kBAAkB,CAAA;AAEvD,EAAA,OAAO,OAAA,CAAQ,cAAA,CAAA;AAAA,IACb;AAAA,GAAA,EACG,WAAA,CACJ,CAAA;AACH","file":"index.mjs","sourcesContent":["import { zodResolver } from '@hookform/resolvers/zod';\nimport {\n type DefaultValues,\n type FieldValues,\n type UseFormProps,\n useForm,\n} from 'react-hook-form';\nimport type { z } from 'zod';\nimport type {\n PartialWithAllNullables,\n PartialWithNullableObjects,\n} from './types';\n\n/**\n * Type-safe React Hook Form wrapper with automatic Zod v4 schema validation and type transformation.\n *\n * This hook eliminates the TypeScript friction between React Hook Form's nullable field values\n * and Zod's strict output types. It uses a two-type schema pattern where:\n * - **Input type** (`PartialWithNullableObjects<T>`): Form fields accept `null | undefined` during editing\n * - **Output type** (`T`): Validated data matches exact schema type (no `null | undefined`)\n *\n * **Key Benefits:**\n * - ✅ No more \"Type 'null' is not assignable to...\" TypeScript errors\n * - ✅ Use `form.setValue()` and `form.reset()` with `null` values freely\n * - ✅ Validated output is still type-safe with exact Zod schema types\n * - ✅ Automatic zodResolver setup - no manual configuration needed\n *\n * @template T - The Zod schema output type (extends FieldValues)\n *\n * @param options - Configuration object\n * @param options.schema - Zod schema with two-type signature `z.ZodType<T, PartialWithNullableObjects<T>>`\n * @param options.defaultValues - Default form values (accepts nullable/undefined values)\n * @param options.zodResolverOptions - Optional zodResolver configuration\n * @param options....formOptions - All other react-hook-form useForm options\n *\n * @returns React Hook Form instance with type-safe methods\n *\n * @example\n * Basic usage with required fields\n * ```typescript\n * import { useZodForm } from '@zod-utils/react-hook-form';\n * import { z } from 'zod';\n *\n * const schema = z.object({\n * name: z.string().min(1), // Required field\n * age: z.number().min(0),\n * }) satisfies z.ZodType<{ name: string; age: number }, any>;\n *\n * function MyForm() {\n * const form = useZodForm({ schema });\n *\n * // ✅ These work without type errors:\n * form.setValue('name', null); // Accepts null during editing\n * form.reset({ name: null, age: null }); // Reset with null\n *\n * const onSubmit = (data: { name: string; age: number }) => {\n * // ✅ data is exact type - no null | undefined\n * console.log(data.name.toUpperCase()); // Safe to use string methods\n * };\n *\n * return <form onSubmit={form.handleSubmit(onSubmit)}>...</form>;\n * }\n * ```\n *\n * @example\n * With default values\n * ```typescript\n * const schema = z.object({\n * username: z.string(),\n * email: z.string().email(),\n * notifications: z.boolean().default(true),\n * }) satisfies z.ZodType<{\n * username: string;\n * email: string;\n * notifications: boolean;\n * }, any>;\n *\n * const form = useZodForm({\n * schema,\n * defaultValues: {\n * username: '',\n * email: '',\n * // notifications gets default from schema\n * },\n * });\n * ```\n *\n * @example\n * With optional and nullable fields\n * ```typescript\n * const schema = z.object({\n * title: z.string(),\n * description: z.string().optional(), // Optional in output\n * tags: z.array(z.string()).nullable(), // Nullable in output\n * }) satisfies z.ZodType<{\n * title: string;\n * description?: string;\n * tags: string[] | null;\n * }, any>;\n *\n * const form = useZodForm({ schema });\n *\n * // All fields accept null/undefined during editing\n * form.setValue('title', null);\n * form.setValue('description', undefined);\n * form.setValue('tags', null);\n * ```\n *\n * @example\n * With zodResolver options\n * ```typescript\n * const form = useZodForm({\n * schema,\n * zodResolverOptions: {\n * async: true, // Enable async validation\n * errorMap: customErrorMap, // Custom error messages\n * },\n * });\n * ```\n *\n * @example\n * Complete form example\n * ```typescript\n * const userSchema = z.object({\n * name: z.string().min(1, 'Name is required'),\n * email: z.string().email('Invalid email'),\n * age: z.number().min(18, 'Must be 18+'),\n * }) satisfies z.ZodType<{ name: string; email: string; age: number }, any>;\n *\n * function UserForm() {\n * const form = useZodForm({\n * schema: userSchema,\n * defaultValues: { name: '', email: '', age: null },\n * });\n *\n * const onSubmit = (data: { name: string; email: string; age: number }) => {\n * // Type-safe: data has exact types, no null/undefined\n * console.log(`${data.name} is ${data.age} years old`);\n * };\n *\n * return (\n * <form onSubmit={form.handleSubmit(onSubmit)}>\n * <input {...form.register('name')} />\n * <input {...form.register('email')} type=\"email\" />\n * <input {...form.register('age', { valueAsNumber: true })} type=\"number\" />\n * <button type=\"submit\">Submit</button>\n * </form>\n * );\n * }\n * ```\n *\n * @see {@link PartialWithNullableObjects} for the type transformation utility\n * @see https://react-hook-form.com/docs/useform for React Hook Form documentation\n * @see https://zod.dev for Zod schema documentation\n * @since 0.1.0\n */\nexport const useZodForm = <\n T extends FieldValues,\n I extends PartialWithAllNullables<T> = PartialWithNullableObjects<T>,\n>({\n schema,\n zodResolverOptions,\n ...formOptions\n}: {\n schema: z.ZodType<T, I>;\n defaultValues?: DefaultValues<I>;\n zodResolverOptions?: Parameters<typeof zodResolver>[1];\n} & Omit<UseFormProps<I, unknown, T>, 'resolver' | 'defaultValues'>) => {\n const resolver = zodResolver(schema, zodResolverOptions);\n\n return useForm({\n resolver,\n ...formOptions,\n });\n};\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zod-utils/react-hook-form",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "React Hook Form integration and utilities for Zod schemas",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
"test": "vitest run",
|
|
29
29
|
"test:watch": "vitest",
|
|
30
30
|
"test:coverage": "vitest run --coverage",
|
|
31
|
+
"bench": "vitest bench --run",
|
|
32
|
+
"bench:watch": "vitest bench",
|
|
31
33
|
"prepublishOnly": "npm run build"
|
|
32
34
|
},
|
|
33
35
|
"keywords": [
|