remix-validated-form 5.0.0 → 5.0.2
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/.turbo/turbo-build.log +7 -7
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/ValidatedForm.tsx +4 -4
- package/src/internal/hooks.ts +1 -1
- package/tsconfig.json +1 -1
- package/src/validation/validation.test.ts +0 -330
package/package.json
CHANGED
package/src/ValidatedForm.tsx
CHANGED
@@ -51,7 +51,7 @@ type SubactionData<
|
|
51
51
|
// Not all validation libraries support encoding a literal value in the schema type (e.g. yup).
|
52
52
|
// This condition here allows us to provide strictness for users who are using a validation library that does support it,
|
53
53
|
// but also allows us to support users who are using a validation library that doesn't support it.
|
54
|
-
type
|
54
|
+
type DataForSubaction<
|
55
55
|
DataType,
|
56
56
|
Subaction extends string | undefined
|
57
57
|
> = Subaction extends string // Not all validation libraries support encoding a literal value in the schema type.
|
@@ -70,7 +70,7 @@ export type FormProps<DataType, Subaction extends string | undefined> = {
|
|
70
70
|
* after all validations have been run.
|
71
71
|
*/
|
72
72
|
onSubmit?: (
|
73
|
-
data: DataType,
|
73
|
+
data: DataForSubaction<DataType, Subaction>,
|
74
74
|
event: React.FormEvent<HTMLFormElement>
|
75
75
|
) => void | Promise<void>;
|
76
76
|
/**
|
@@ -83,7 +83,7 @@ export type FormProps<DataType, Subaction extends string | undefined> = {
|
|
83
83
|
* Accepts an object of default values for the form
|
84
84
|
* that will automatically be propagated to the form fields via `useField`.
|
85
85
|
*/
|
86
|
-
defaultValues?: Partial<
|
86
|
+
defaultValues?: Partial<DataForSubaction<DataType, Subaction>>;
|
87
87
|
/**
|
88
88
|
* A ref to the form element.
|
89
89
|
*/
|
@@ -362,7 +362,7 @@ export function ValidatedForm<DataType, Subaction extends string | undefined>({
|
|
362
362
|
} else {
|
363
363
|
setFieldErrors({});
|
364
364
|
const eventProxy = formEventProxy(e);
|
365
|
-
await onSubmit?.(result.data, eventProxy);
|
365
|
+
await onSubmit?.(result.data as any, eventProxy);
|
366
366
|
if (eventProxy.defaultPrevented) {
|
367
367
|
endSubmit();
|
368
368
|
return;
|
package/src/internal/hooks.ts
CHANGED
@@ -110,7 +110,7 @@ export const useHasActiveFormSubmit = ({
|
|
110
110
|
let navigation = useNavigation();
|
111
111
|
const hasActiveSubmission = fetcher
|
112
112
|
? fetcher.state === "submitting"
|
113
|
-
: navigation.state === "submitting";
|
113
|
+
: navigation.state === "submitting" || navigation.state === "loading";
|
114
114
|
return hasActiveSubmission;
|
115
115
|
};
|
116
116
|
|
package/tsconfig.json
CHANGED
@@ -1,330 +0,0 @@
|
|
1
|
-
import { anyString, TestFormData } from "@remix-validated-form/test-utils";
|
2
|
-
import { withYup } from "@remix-validated-form/with-yup/src";
|
3
|
-
import { withZod } from "@remix-validated-form/with-zod";
|
4
|
-
import * as R from "remeda";
|
5
|
-
import { Validator } from "remix-validated-form/src";
|
6
|
-
import { objectFromPathEntries } from "remix-validated-form/src/internal/flatten";
|
7
|
-
import { describe, it, expect } from "vitest";
|
8
|
-
import * as yup from "yup";
|
9
|
-
import { z } from "zod";
|
10
|
-
import { FORM_ID_FIELD } from "../internal/constants";
|
11
|
-
|
12
|
-
// If adding an adapter, write a validator that validates this shape
|
13
|
-
type Person = {
|
14
|
-
firstName: string;
|
15
|
-
lastName: string;
|
16
|
-
age?: number;
|
17
|
-
address: {
|
18
|
-
streetAddress: string;
|
19
|
-
city: string;
|
20
|
-
country: string;
|
21
|
-
};
|
22
|
-
pets?: {
|
23
|
-
animal: string;
|
24
|
-
name: string;
|
25
|
-
}[];
|
26
|
-
};
|
27
|
-
|
28
|
-
type ValidationTestCase = {
|
29
|
-
name: string;
|
30
|
-
validator: Validator<Person>;
|
31
|
-
};
|
32
|
-
|
33
|
-
const validationTestCases: ValidationTestCase[] = [
|
34
|
-
{
|
35
|
-
name: "yup",
|
36
|
-
validator: withYup(
|
37
|
-
yup.object({
|
38
|
-
firstName: yup.string().required(),
|
39
|
-
lastName: yup.string().required(),
|
40
|
-
age: yup.number(),
|
41
|
-
address: yup
|
42
|
-
.object({
|
43
|
-
streetAddress: yup.string().required(),
|
44
|
-
city: yup.string().required(),
|
45
|
-
country: yup.string().required(),
|
46
|
-
})
|
47
|
-
.required(),
|
48
|
-
pets: yup.array().of(
|
49
|
-
yup.object({
|
50
|
-
animal: yup.string().required(),
|
51
|
-
name: yup.string().required(),
|
52
|
-
})
|
53
|
-
),
|
54
|
-
})
|
55
|
-
),
|
56
|
-
},
|
57
|
-
{
|
58
|
-
name: "zod",
|
59
|
-
validator: withZod(
|
60
|
-
z.object({
|
61
|
-
firstName: z.string().min(1),
|
62
|
-
lastName: z.string().min(1),
|
63
|
-
age: z.optional(z.number()),
|
64
|
-
address: z.preprocess(
|
65
|
-
(value) => (value == null ? {} : value),
|
66
|
-
z.object({
|
67
|
-
streetAddress: z.string().min(1),
|
68
|
-
city: z.string().min(1),
|
69
|
-
country: z.string().min(1),
|
70
|
-
})
|
71
|
-
),
|
72
|
-
pets: z
|
73
|
-
.object({
|
74
|
-
animal: z.string().min(1),
|
75
|
-
name: z.string().min(1),
|
76
|
-
})
|
77
|
-
.array()
|
78
|
-
.optional(),
|
79
|
-
})
|
80
|
-
),
|
81
|
-
},
|
82
|
-
];
|
83
|
-
|
84
|
-
describe("Validation", () => {
|
85
|
-
describe.each(validationTestCases)("Adapter for $name", ({ validator }) => {
|
86
|
-
describe("validate", () => {
|
87
|
-
it("should return the data when valid", async () => {
|
88
|
-
const person: Person = {
|
89
|
-
firstName: "John",
|
90
|
-
lastName: "Doe",
|
91
|
-
age: 30,
|
92
|
-
address: {
|
93
|
-
streetAddress: "123 Main St",
|
94
|
-
city: "Anytown",
|
95
|
-
country: "USA",
|
96
|
-
},
|
97
|
-
pets: [{ animal: "dog", name: "Fido" }],
|
98
|
-
};
|
99
|
-
expect(await validator.validate(person)).toEqual({
|
100
|
-
data: person,
|
101
|
-
error: undefined,
|
102
|
-
submittedData: person,
|
103
|
-
});
|
104
|
-
});
|
105
|
-
|
106
|
-
it("should omit internal fields", async () => {
|
107
|
-
const person: Person = {
|
108
|
-
firstName: "John",
|
109
|
-
lastName: "Doe",
|
110
|
-
age: 30,
|
111
|
-
address: {
|
112
|
-
streetAddress: "123 Main St",
|
113
|
-
city: "Anytown",
|
114
|
-
country: "USA",
|
115
|
-
},
|
116
|
-
pets: [{ animal: "dog", name: "Fido" }],
|
117
|
-
|
118
|
-
// @ts-expect-error
|
119
|
-
// internal filed technically not part of person type
|
120
|
-
[FORM_ID_FIELD]: "something",
|
121
|
-
};
|
122
|
-
expect(await validator.validate(person)).toEqual({
|
123
|
-
data: R.omit(person as any, [FORM_ID_FIELD]),
|
124
|
-
error: undefined,
|
125
|
-
submittedData: person,
|
126
|
-
formId: "something",
|
127
|
-
});
|
128
|
-
});
|
129
|
-
|
130
|
-
it("should return field errors when invalid", async () => {
|
131
|
-
const obj = { age: "hi!", pets: [{ animal: "dog" }] };
|
132
|
-
expect(await validator.validate(obj)).toEqual({
|
133
|
-
data: undefined,
|
134
|
-
error: {
|
135
|
-
fieldErrors: {
|
136
|
-
firstName: anyString,
|
137
|
-
lastName: anyString,
|
138
|
-
age: anyString,
|
139
|
-
"address.city": anyString,
|
140
|
-
"address.country": anyString,
|
141
|
-
"address.streetAddress": anyString,
|
142
|
-
"pets[0].name": anyString,
|
143
|
-
},
|
144
|
-
subaction: undefined,
|
145
|
-
},
|
146
|
-
submittedData: obj,
|
147
|
-
});
|
148
|
-
});
|
149
|
-
|
150
|
-
it("should unflatten data when validating", async () => {
|
151
|
-
const data = {
|
152
|
-
firstName: "John",
|
153
|
-
lastName: "Doe",
|
154
|
-
age: 30,
|
155
|
-
"address.streetAddress": "123 Main St",
|
156
|
-
"address.city": "Anytown",
|
157
|
-
"address.country": "USA",
|
158
|
-
"pets[0].animal": "dog",
|
159
|
-
"pets[0].name": "Fido",
|
160
|
-
};
|
161
|
-
expect(await validator.validate(data)).toEqual({
|
162
|
-
data: {
|
163
|
-
firstName: "John",
|
164
|
-
lastName: "Doe",
|
165
|
-
age: 30,
|
166
|
-
address: {
|
167
|
-
streetAddress: "123 Main St",
|
168
|
-
city: "Anytown",
|
169
|
-
country: "USA",
|
170
|
-
},
|
171
|
-
pets: [{ animal: "dog", name: "Fido" }],
|
172
|
-
},
|
173
|
-
error: undefined,
|
174
|
-
submittedData: objectFromPathEntries(Object.entries(data)),
|
175
|
-
});
|
176
|
-
});
|
177
|
-
|
178
|
-
it("should accept FormData directly and return errors", async () => {
|
179
|
-
const formData = new TestFormData();
|
180
|
-
formData.set("firstName", "John");
|
181
|
-
formData.set("lastName", "Doe");
|
182
|
-
formData.set("address.streetAddress", "123 Main St");
|
183
|
-
formData.set("address.country", "USA");
|
184
|
-
formData.set("pets[0].animal", "dog");
|
185
|
-
|
186
|
-
expect(await validator.validate(formData)).toEqual({
|
187
|
-
data: undefined,
|
188
|
-
error: {
|
189
|
-
fieldErrors: {
|
190
|
-
"address.city": anyString,
|
191
|
-
"pets[0].name": anyString,
|
192
|
-
},
|
193
|
-
subaction: undefined,
|
194
|
-
},
|
195
|
-
submittedData: objectFromPathEntries([...formData.entries()]),
|
196
|
-
});
|
197
|
-
});
|
198
|
-
|
199
|
-
it("should accept FormData directly and return valid data", async () => {
|
200
|
-
const formData = new TestFormData();
|
201
|
-
formData.set("firstName", "John");
|
202
|
-
formData.set("lastName", "Doe");
|
203
|
-
formData.set("address.streetAddress", "123 Main St");
|
204
|
-
formData.set("address.country", "USA");
|
205
|
-
formData.set("address.city", "Anytown");
|
206
|
-
formData.set("pets[0].animal", "dog");
|
207
|
-
formData.set("pets[0].name", "Fido");
|
208
|
-
|
209
|
-
expect(await validator.validate(formData)).toEqual({
|
210
|
-
data: {
|
211
|
-
firstName: "John",
|
212
|
-
lastName: "Doe",
|
213
|
-
address: {
|
214
|
-
streetAddress: "123 Main St",
|
215
|
-
country: "USA",
|
216
|
-
city: "Anytown",
|
217
|
-
},
|
218
|
-
pets: [{ animal: "dog", name: "Fido" }],
|
219
|
-
},
|
220
|
-
error: undefined,
|
221
|
-
subaction: undefined,
|
222
|
-
submittedData: objectFromPathEntries([...formData.entries()]),
|
223
|
-
});
|
224
|
-
});
|
225
|
-
|
226
|
-
it("should return the subaction in the ValidatorError if there is one", async () => {
|
227
|
-
const person = {
|
228
|
-
lastName: "Doe",
|
229
|
-
age: 20,
|
230
|
-
address: {
|
231
|
-
streetAddress: "123 Main St",
|
232
|
-
city: "Anytown",
|
233
|
-
country: "USA",
|
234
|
-
},
|
235
|
-
pets: [{ animal: "dog", name: "Fido" }],
|
236
|
-
subaction: "updatePerson",
|
237
|
-
};
|
238
|
-
expect(await validator.validate(person)).toEqual({
|
239
|
-
error: {
|
240
|
-
fieldErrors: {
|
241
|
-
firstName: anyString,
|
242
|
-
},
|
243
|
-
subaction: "updatePerson",
|
244
|
-
},
|
245
|
-
data: undefined,
|
246
|
-
submittedData: person,
|
247
|
-
});
|
248
|
-
});
|
249
|
-
});
|
250
|
-
|
251
|
-
describe("validateField", () => {
|
252
|
-
it("should not return an error if field is valid", async () => {
|
253
|
-
const person = {
|
254
|
-
firstName: "John",
|
255
|
-
lastName: {}, // invalid, but we should only be validating firstName
|
256
|
-
};
|
257
|
-
expect(await validator.validateField(person, "firstName")).toEqual({
|
258
|
-
error: undefined,
|
259
|
-
});
|
260
|
-
});
|
261
|
-
it("should not return an error if a nested field is valid", async () => {
|
262
|
-
const person = {
|
263
|
-
firstName: "John",
|
264
|
-
lastName: {}, // invalid, but we should only be validating firstName
|
265
|
-
address: {
|
266
|
-
streetAddress: "123 Main St",
|
267
|
-
city: "Anytown",
|
268
|
-
country: "USA",
|
269
|
-
},
|
270
|
-
pets: [{ animal: "dog", name: "Fido" }],
|
271
|
-
};
|
272
|
-
expect(
|
273
|
-
await validator.validateField(person, "address.streetAddress")
|
274
|
-
).toEqual({
|
275
|
-
error: undefined,
|
276
|
-
});
|
277
|
-
expect(await validator.validateField(person, "address.city")).toEqual({
|
278
|
-
error: undefined,
|
279
|
-
});
|
280
|
-
expect(
|
281
|
-
await validator.validateField(person, "address.country")
|
282
|
-
).toEqual({
|
283
|
-
error: undefined,
|
284
|
-
});
|
285
|
-
expect(await validator.validateField(person, "pets[0].animal")).toEqual(
|
286
|
-
{
|
287
|
-
error: undefined,
|
288
|
-
}
|
289
|
-
);
|
290
|
-
expect(await validator.validateField(person, "pets[0].name")).toEqual({
|
291
|
-
error: undefined,
|
292
|
-
});
|
293
|
-
});
|
294
|
-
|
295
|
-
it("should return an error if field is invalid", async () => {
|
296
|
-
const person = {
|
297
|
-
firstName: "John",
|
298
|
-
lastName: {},
|
299
|
-
address: {
|
300
|
-
streetAddress: "123 Main St",
|
301
|
-
city: 1234,
|
302
|
-
},
|
303
|
-
};
|
304
|
-
expect(await validator.validateField(person, "lastName")).toEqual({
|
305
|
-
error: anyString,
|
306
|
-
});
|
307
|
-
});
|
308
|
-
|
309
|
-
it("should return an error if a nested field is invalid", async () => {
|
310
|
-
const person = {
|
311
|
-
firstName: "John",
|
312
|
-
lastName: {},
|
313
|
-
address: {
|
314
|
-
streetAddress: "123 Main St",
|
315
|
-
city: 1234,
|
316
|
-
},
|
317
|
-
pets: [{ animal: "dog" }],
|
318
|
-
};
|
319
|
-
expect(
|
320
|
-
await validator.validateField(person, "address.country")
|
321
|
-
).toEqual({
|
322
|
-
error: anyString,
|
323
|
-
});
|
324
|
-
expect(await validator.validateField(person, "pets[0].name")).toEqual({
|
325
|
-
error: anyString,
|
326
|
-
});
|
327
|
-
});
|
328
|
-
});
|
329
|
-
});
|
330
|
-
});
|