remix-validated-form 4.5.0 → 4.5.1
Sign up to get free protection for your applications and to get access to all the features.
- package/.turbo/turbo-build.log +8 -8
- package/browser/ValidatedForm.js +4 -4
- package/browser/internal/hooks.d.ts +1 -0
- package/browser/internal/hooks.js +1 -0
- package/browser/internal/logic/nestedObjectToPathObject.d.ts +0 -0
- package/browser/internal/logic/nestedObjectToPathObject.js +0 -0
- package/browser/internal/state/arrayUtil.d.ts +6 -0
- package/browser/internal/state/arrayUtil.js +236 -7
- package/browser/internal/state/createFormStore.d.ts +1 -0
- package/browser/internal/state/createFormStore.js +6 -0
- package/browser/internal/state/fieldArray.d.ts +18 -11
- package/browser/internal/state/fieldArray.js +44 -21
- package/browser/unreleased/formStateHooks.d.ts +4 -0
- package/browser/unreleased/formStateHooks.js +4 -1
- package/browser/userFacingFormContext.d.ts +4 -0
- package/browser/userFacingFormContext.js +3 -1
- package/dist/remix-validated-form.cjs.js +2 -2
- package/dist/remix-validated-form.cjs.js.map +1 -1
- package/dist/remix-validated-form.es.js +23 -10
- package/dist/remix-validated-form.es.js.map +1 -1
- package/dist/remix-validated-form.umd.js +2 -2
- package/dist/remix-validated-form.umd.js.map +1 -1
- package/dist/types/internal/hooks.d.ts +1 -0
- package/dist/types/internal/state/createFormStore.d.ts +1 -0
- package/dist/types/unreleased/formStateHooks.d.ts +4 -0
- package/dist/types/userFacingFormContext.d.ts +4 -0
- package/package.json +1 -1
- package/src/ValidatedForm.tsx +5 -4
- package/src/internal/hooks.ts +3 -0
- package/src/internal/state/createFormStore.ts +12 -1
- package/src/unreleased/formStateHooks.ts +8 -0
- package/src/userFacingFormContext.ts +7 -0
- package/src/validation/validation.test.ts +7 -7
package/.turbo/turbo-build.log
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
[2K[1G[2m$ vite build[22m
|
2
2
|
[36mvite v2.9.5 [32mbuilding for production...[36m[39m
|
3
3
|
transforming...
|
4
|
-
[32m✓[39m
|
4
|
+
[32m✓[39m 319 modules transformed.
|
5
5
|
rendering chunks...
|
6
|
-
[90m[37m[2mdist/[22m[90m[39m[36mremix-validated-form.cjs.js [39m [
|
7
|
-
[90m[37m[2mdist/[22m[90m[39m[90mremix-validated-form.cjs.js.map[39m [
|
8
|
-
[90m[37m[2mdist/[22m[90m[39m[36mremix-validated-form.es.js [39m [
|
9
|
-
[90m[37m[2mdist/[22m[90m[39m[90mremix-validated-form.es.js.map[39m [
|
10
|
-
[90m[37m[2mdist/[22m[90m[39m[36mremix-validated-form.umd.js [39m [
|
11
|
-
[90m[37m[2mdist/[22m[90m[39m[90mremix-validated-form.umd.js.map[39m [
|
6
|
+
[90m[37m[2mdist/[22m[90m[39m[36mremix-validated-form.cjs.js [39m [2m45.46 KiB / gzip: 17.05 KiB[22m
|
7
|
+
[90m[37m[2mdist/[22m[90m[39m[90mremix-validated-form.cjs.js.map[39m [2m252.62 KiB[22m
|
8
|
+
[90m[37m[2mdist/[22m[90m[39m[36mremix-validated-form.es.js [39m [2m101.10 KiB / gzip: 23.71 KiB[22m
|
9
|
+
[90m[37m[2mdist/[22m[90m[39m[90mremix-validated-form.es.js.map[39m [2m260.60 KiB[22m
|
10
|
+
[90m[37m[2mdist/[22m[90m[39m[36mremix-validated-form.umd.js [39m [2m45.71 KiB / gzip: 17.16 KiB[22m
|
11
|
+
[90m[37m[2mdist/[22m[90m[39m[90mremix-validated-form.umd.js.map[39m [2m252.60 KiB[22m
|
12
12
|
[32m[39m
|
13
13
|
[32m[36m[vite:dts][39m[32m Start generate declaration files...[39m
|
14
|
-
[32m[36m[vite:dts][39m[32m Declaration files built in
|
14
|
+
[32m[36m[vite:dts][39m[32m Declaration files built in 2049ms.[39m
|
15
15
|
[32m[39m
|
16
16
|
No name was provided for external module 'react' in output.globals – guessing 'React'
|
17
17
|
No name was provided for external module '@remix-run/react' in output.globals – guessing 'react'
|
package/browser/ValidatedForm.js
CHANGED
@@ -21,7 +21,7 @@ const focusFirstInvalidInput = (fieldErrors, customFocusHandlers, formElement) =
|
|
21
21
|
const namesInOrder = [...formElement.elements]
|
22
22
|
.map((el) => {
|
23
23
|
const input = el instanceof RadioNodeList ? el[0] : el;
|
24
|
-
if (input instanceof
|
24
|
+
if (input instanceof HTMLElement && "name" in input)
|
25
25
|
return input.name;
|
26
26
|
return null;
|
27
27
|
})
|
@@ -47,8 +47,8 @@ const focusFirstInvalidInput = (fieldErrors, customFocusHandlers, formElement) =
|
|
47
47
|
break;
|
48
48
|
}
|
49
49
|
}
|
50
|
-
if (elem instanceof
|
51
|
-
if (elem.type === "hidden") {
|
50
|
+
if (elem instanceof HTMLElement) {
|
51
|
+
if (elem instanceof HTMLInputElement && elem.type === "hidden") {
|
52
52
|
continue;
|
53
53
|
}
|
54
54
|
elem.focus();
|
@@ -189,7 +189,7 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
|
|
189
189
|
if (fetcher)
|
190
190
|
fetcher.submit(submitter || e.currentTarget);
|
191
191
|
else
|
192
|
-
submit(submitter || target, {
|
192
|
+
submit(submitter || target, { replace });
|
193
193
|
}
|
194
194
|
};
|
195
195
|
return (_jsx(Form, { ref: mergeRefs([formRef, formRefProp]), ...rest, id: id, action: action, method: method, replace: replace, onSubmit: (e) => {
|
@@ -31,3 +31,4 @@ export declare const useResetFormElement: (formId: InternalFormId) => () => void
|
|
31
31
|
export declare const useSubmitForm: (formId: InternalFormId) => () => void;
|
32
32
|
export declare const useFormActionProp: (formId: InternalFormId) => string | undefined;
|
33
33
|
export declare const useFormSubactionProp: (formId: InternalFormId) => string | undefined;
|
34
|
+
export declare const useFormValues: (formId: InternalFormId) => () => FormData;
|
@@ -116,3 +116,4 @@ export const useResetFormElement = (formId) => useFormStore(formId, (state) => s
|
|
116
116
|
export const useSubmitForm = (formId) => useFormStore(formId, (state) => state.submit);
|
117
117
|
export const useFormActionProp = (formId) => useFormStore(formId, (state) => { var _a; return (_a = state.formProps) === null || _a === void 0 ? void 0 : _a.action; });
|
118
118
|
export const useFormSubactionProp = (formId) => useFormStore(formId, (state) => { var _a; return (_a = state.formProps) === null || _a === void 0 ? void 0 : _a.subaction; });
|
119
|
+
export const useFormValues = (formId) => useFormStore(formId, (state) => state.getValues);
|
File without changes
|
File without changes
|
@@ -4,3 +4,9 @@ export declare const move: (array: unknown[], from: number, to: number) => void;
|
|
4
4
|
export declare const insert: (array: unknown[], index: number, value: unknown) => void;
|
5
5
|
export declare const remove: (array: unknown[], index: number) => void;
|
6
6
|
export declare const replace: (array: unknown[], index: number, value: unknown) => void;
|
7
|
+
/**
|
8
|
+
* The purpose of this helper is to make it easier to update `fieldErrors` and `touchedFields`.
|
9
|
+
* We key those objects by full paths to the fields.
|
10
|
+
* When we're doing array mutations, that makes it difficult to update those objects.
|
11
|
+
*/
|
12
|
+
export declare const mutateAsArray: (field: string, obj: Record<string, any>, mutate: (arr: any[]) => void) => void;
|
@@ -18,24 +18,93 @@ export const getArray = (values, field) => {
|
|
18
18
|
export const swap = (array, indexA, indexB) => {
|
19
19
|
const itemA = array[indexA];
|
20
20
|
const itemB = array[indexB];
|
21
|
-
|
22
|
-
|
21
|
+
const hasItemA = indexA in array;
|
22
|
+
const hasItemB = indexB in array;
|
23
|
+
// If we're dealing with a sparse array (i.e. one of the indeces doesn't exist),
|
24
|
+
// we should keep it sparse
|
25
|
+
if (hasItemA) {
|
26
|
+
array[indexB] = itemA;
|
27
|
+
}
|
28
|
+
else {
|
29
|
+
delete array[indexB];
|
30
|
+
}
|
31
|
+
if (hasItemB) {
|
32
|
+
array[indexA] = itemB;
|
33
|
+
}
|
34
|
+
else {
|
35
|
+
delete array[indexA];
|
36
|
+
}
|
23
37
|
};
|
38
|
+
// A splice that can handle sparse arrays
|
39
|
+
function sparseSplice(array, start, deleteCount, item) {
|
40
|
+
// Inserting an item into an array won't behave as we need it to if the array isn't
|
41
|
+
// at least as long as the start index. We can force the array to be long enough like this.
|
42
|
+
if (array.length < start && item) {
|
43
|
+
array.length = start;
|
44
|
+
}
|
45
|
+
// If we just pass item in, it'll be undefined and splice will delete the item.
|
46
|
+
if (arguments.length === 4)
|
47
|
+
return array.splice(start, deleteCount, item);
|
48
|
+
return array.splice(start, deleteCount);
|
49
|
+
}
|
24
50
|
export const move = (array, from, to) => {
|
25
|
-
const [item] = array
|
26
|
-
array
|
51
|
+
const [item] = sparseSplice(array, from, 1);
|
52
|
+
sparseSplice(array, to, 0, item);
|
27
53
|
};
|
28
54
|
export const insert = (array, index, value) => {
|
29
|
-
array
|
55
|
+
sparseSplice(array, index, 0, value);
|
30
56
|
};
|
31
57
|
export const remove = (array, index) => {
|
32
|
-
array
|
58
|
+
sparseSplice(array, index, 1);
|
33
59
|
};
|
34
60
|
export const replace = (array, index, value) => {
|
35
|
-
array
|
61
|
+
sparseSplice(array, index, 1, value);
|
62
|
+
};
|
63
|
+
/**
|
64
|
+
* The purpose of this helper is to make it easier to update `fieldErrors` and `touchedFields`.
|
65
|
+
* We key those objects by full paths to the fields.
|
66
|
+
* When we're doing array mutations, that makes it difficult to update those objects.
|
67
|
+
*/
|
68
|
+
export const mutateAsArray = (field, obj, mutate) => {
|
69
|
+
const beforeKeys = new Set();
|
70
|
+
const arr = [];
|
71
|
+
for (const [key, value] of Object.entries(obj)) {
|
72
|
+
if (key.startsWith(field) && key !== field) {
|
73
|
+
beforeKeys.add(key);
|
74
|
+
}
|
75
|
+
lodashSet(arr, key.substring(field.length), value);
|
76
|
+
}
|
77
|
+
mutate(arr);
|
78
|
+
for (const key of beforeKeys) {
|
79
|
+
delete obj[key];
|
80
|
+
}
|
81
|
+
const newKeys = getDeepArrayPaths(arr);
|
82
|
+
for (const key of newKeys) {
|
83
|
+
const val = lodashGet(arr, key);
|
84
|
+
obj[`${field}${key}`] = val;
|
85
|
+
}
|
86
|
+
};
|
87
|
+
const getDeepArrayPaths = (obj, basePath = "") => {
|
88
|
+
// This only needs to handle arrays and plain objects
|
89
|
+
// and we can assume the first call is always an array.
|
90
|
+
if (Array.isArray(obj)) {
|
91
|
+
return obj.flatMap((item, index) => getDeepArrayPaths(item, `${basePath}[${index}]`));
|
92
|
+
}
|
93
|
+
if (typeof obj === "object") {
|
94
|
+
return Object.keys(obj).flatMap((key) => getDeepArrayPaths(obj[key], `${basePath}.${key}`));
|
95
|
+
}
|
96
|
+
return [basePath];
|
36
97
|
};
|
37
98
|
if (import.meta.vitest) {
|
38
99
|
const { describe, expect, it } = import.meta.vitest;
|
100
|
+
// Count the actual number of items in the array
|
101
|
+
// instead of just getting the length.
|
102
|
+
// This is useful for validating that sparse arrays are handled correctly.
|
103
|
+
const countArrayItems = (arr) => {
|
104
|
+
let count = 0;
|
105
|
+
arr.forEach(() => count++);
|
106
|
+
return count;
|
107
|
+
};
|
39
108
|
describe("getArray", () => {
|
40
109
|
it("shoud get a deeply nested array that can be mutated to update the nested value", () => {
|
41
110
|
const values = {
|
@@ -76,6 +145,16 @@ if (import.meta.vitest) {
|
|
76
145
|
swap(array, 0, 1);
|
77
146
|
expect(array).toEqual([2, 1, 3]);
|
78
147
|
});
|
148
|
+
it("should work for sparse arrays", () => {
|
149
|
+
// A bit of a sanity check for native array behavior
|
150
|
+
const arr = [];
|
151
|
+
arr[0] = true;
|
152
|
+
swap(arr, 0, 2);
|
153
|
+
expect(countArrayItems(arr)).toEqual(1);
|
154
|
+
expect(0 in arr).toBe(false);
|
155
|
+
expect(2 in arr).toBe(true);
|
156
|
+
expect(arr[2]).toEqual(true);
|
157
|
+
});
|
79
158
|
});
|
80
159
|
describe("move", () => {
|
81
160
|
it("should move an item to a new index", () => {
|
@@ -83,6 +162,12 @@ if (import.meta.vitest) {
|
|
83
162
|
move(array, 0, 1);
|
84
163
|
expect(array).toEqual([2, 1, 3]);
|
85
164
|
});
|
165
|
+
it("should work with sparse arrays", () => {
|
166
|
+
const array = [1];
|
167
|
+
move(array, 0, 2);
|
168
|
+
expect(countArrayItems(array)).toEqual(1);
|
169
|
+
expect(array).toEqual([undefined, undefined, 1]);
|
170
|
+
});
|
86
171
|
});
|
87
172
|
describe("insert", () => {
|
88
173
|
it("should insert an item at a new index", () => {
|
@@ -90,6 +175,18 @@ if (import.meta.vitest) {
|
|
90
175
|
insert(array, 1, 4);
|
91
176
|
expect(array).toEqual([1, 4, 2, 3]);
|
92
177
|
});
|
178
|
+
it("should be able to insert falsey values", () => {
|
179
|
+
const array = [1, 2, 3];
|
180
|
+
insert(array, 1, null);
|
181
|
+
expect(array).toEqual([1, null, 2, 3]);
|
182
|
+
});
|
183
|
+
it("should handle sparse arrays", () => {
|
184
|
+
const array = [];
|
185
|
+
array[2] = true;
|
186
|
+
insert(array, 0, true);
|
187
|
+
expect(countArrayItems(array)).toEqual(2);
|
188
|
+
expect(array).toEqual([true, undefined, undefined, true]);
|
189
|
+
});
|
93
190
|
});
|
94
191
|
describe("remove", () => {
|
95
192
|
it("should remove an item at a given index", () => {
|
@@ -97,6 +194,13 @@ if (import.meta.vitest) {
|
|
97
194
|
remove(array, 1);
|
98
195
|
expect(array).toEqual([1, 3]);
|
99
196
|
});
|
197
|
+
it("should handle sparse arrays", () => {
|
198
|
+
const array = [];
|
199
|
+
array[2] = true;
|
200
|
+
remove(array, 0);
|
201
|
+
expect(countArrayItems(array)).toEqual(1);
|
202
|
+
expect(array).toEqual([undefined, true]);
|
203
|
+
});
|
100
204
|
});
|
101
205
|
describe("replace", () => {
|
102
206
|
it("should replace an item at a given index", () => {
|
@@ -104,5 +208,130 @@ if (import.meta.vitest) {
|
|
104
208
|
replace(array, 1, 4);
|
105
209
|
expect(array).toEqual([1, 4, 3]);
|
106
210
|
});
|
211
|
+
it("should handle sparse arrays", () => {
|
212
|
+
const array = [];
|
213
|
+
array[2] = true;
|
214
|
+
replace(array, 0, true);
|
215
|
+
expect(countArrayItems(array)).toEqual(2);
|
216
|
+
expect(array).toEqual([true, undefined, true]);
|
217
|
+
});
|
218
|
+
});
|
219
|
+
describe("mutateAsArray", () => {
|
220
|
+
it("should handle swap", () => {
|
221
|
+
const values = {
|
222
|
+
myField: "something",
|
223
|
+
"myField[0]": "foo",
|
224
|
+
"myField[2]": "bar",
|
225
|
+
otherField: "baz",
|
226
|
+
"otherField[0]": "something else",
|
227
|
+
};
|
228
|
+
mutateAsArray("myField", values, (arr) => {
|
229
|
+
swap(arr, 0, 2);
|
230
|
+
});
|
231
|
+
expect(values).toEqual({
|
232
|
+
myField: "something",
|
233
|
+
"myField[0]": "bar",
|
234
|
+
"myField[2]": "foo",
|
235
|
+
otherField: "baz",
|
236
|
+
"otherField[0]": "something else",
|
237
|
+
});
|
238
|
+
});
|
239
|
+
it("should swap sparse arrays", () => {
|
240
|
+
const values = {
|
241
|
+
myField: "something",
|
242
|
+
"myField[0]": "foo",
|
243
|
+
otherField: "baz",
|
244
|
+
"otherField[0]": "something else",
|
245
|
+
};
|
246
|
+
mutateAsArray("myField", values, (arr) => {
|
247
|
+
swap(arr, 0, 2);
|
248
|
+
});
|
249
|
+
expect(values).toEqual({
|
250
|
+
myField: "something",
|
251
|
+
"myField[2]": "foo",
|
252
|
+
otherField: "baz",
|
253
|
+
"otherField[0]": "something else",
|
254
|
+
});
|
255
|
+
});
|
256
|
+
it("should handle arrays with nested values", () => {
|
257
|
+
const values = {
|
258
|
+
myField: "something",
|
259
|
+
"myField[0].title": "foo",
|
260
|
+
"myField[0].note": "bar",
|
261
|
+
"myField[2].title": "other",
|
262
|
+
"myField[2].note": "other",
|
263
|
+
otherField: "baz",
|
264
|
+
"otherField[0]": "something else",
|
265
|
+
};
|
266
|
+
mutateAsArray("myField", values, (arr) => {
|
267
|
+
swap(arr, 0, 2);
|
268
|
+
});
|
269
|
+
expect(values).toEqual({
|
270
|
+
myField: "something",
|
271
|
+
"myField[0].title": "other",
|
272
|
+
"myField[0].note": "other",
|
273
|
+
"myField[2].title": "foo",
|
274
|
+
"myField[2].note": "bar",
|
275
|
+
otherField: "baz",
|
276
|
+
"otherField[0]": "something else",
|
277
|
+
});
|
278
|
+
});
|
279
|
+
it("should handle move", () => {
|
280
|
+
const values = {
|
281
|
+
myField: "something",
|
282
|
+
"myField[0]": "foo",
|
283
|
+
"myField[1]": "bar",
|
284
|
+
"myField[2]": "baz",
|
285
|
+
"otherField[0]": "something else",
|
286
|
+
};
|
287
|
+
mutateAsArray("myField", values, (arr) => {
|
288
|
+
move(arr, 0, 2);
|
289
|
+
});
|
290
|
+
expect(values).toEqual({
|
291
|
+
myField: "something",
|
292
|
+
"myField[0]": "bar",
|
293
|
+
"myField[1]": "baz",
|
294
|
+
"myField[2]": "foo",
|
295
|
+
"otherField[0]": "something else",
|
296
|
+
});
|
297
|
+
});
|
298
|
+
it("should handle remove", () => {
|
299
|
+
const values = {
|
300
|
+
myField: "something",
|
301
|
+
"myField[0]": "foo",
|
302
|
+
"myField[1]": "bar",
|
303
|
+
"myField[2]": "baz",
|
304
|
+
"otherField[0]": "something else",
|
305
|
+
};
|
306
|
+
mutateAsArray("myField", values, (arr) => {
|
307
|
+
remove(arr, 1);
|
308
|
+
});
|
309
|
+
expect(values).toEqual({
|
310
|
+
myField: "something",
|
311
|
+
"myField[0]": "foo",
|
312
|
+
"myField[1]": "baz",
|
313
|
+
"otherField[0]": "something else",
|
314
|
+
});
|
315
|
+
expect("myField[2]" in values).toBe(false);
|
316
|
+
});
|
317
|
+
});
|
318
|
+
describe("getDeepArrayPaths", () => {
|
319
|
+
it("should return all paths recursively", () => {
|
320
|
+
const obj = [
|
321
|
+
true,
|
322
|
+
true,
|
323
|
+
[true, true],
|
324
|
+
{ foo: true, bar: { baz: true, test: [true] } },
|
325
|
+
];
|
326
|
+
expect(getDeepArrayPaths(obj, "myField")).toEqual([
|
327
|
+
"myField[0]",
|
328
|
+
"myField[1]",
|
329
|
+
"myField[2][0]",
|
330
|
+
"myField[2][1]",
|
331
|
+
"myField[3].foo",
|
332
|
+
"myField[3].bar.baz",
|
333
|
+
"myField[3].bar.test[0]",
|
334
|
+
]);
|
335
|
+
});
|
107
336
|
});
|
108
337
|
}
|
@@ -42,6 +42,7 @@ export declare type FormState = {
|
|
42
42
|
validate: () => Promise<ValidationResult<unknown>>;
|
43
43
|
resetFormElement: () => void;
|
44
44
|
submit: () => void;
|
45
|
+
getValues: () => FormData;
|
45
46
|
};
|
46
47
|
export declare const useRootFormStore: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<FormStoreState>, "setState"> & {
|
47
48
|
setState(nextStateOrUpdater: FormStoreState | Partial<FormStoreState> | ((state: WritableDraft<FormStoreState>) => void), shouldReplace?: boolean | undefined): void;
|
@@ -29,6 +29,7 @@ const defaultFormState = {
|
|
29
29
|
throw new Error("Submit called before form was initialized.");
|
30
30
|
},
|
31
31
|
resetFormElement: noOp,
|
32
|
+
getValues: () => new FormData(),
|
32
33
|
};
|
33
34
|
const createFormState = (formId, set, get) => ({
|
34
35
|
// It's not "hydrated" until the form props are synced
|
@@ -114,6 +115,11 @@ const createFormState = (formId, set, get) => ({
|
|
114
115
|
invariant(formElement, "Cannot find reference to form. This is probably a bug in remix-validated-form.");
|
115
116
|
formElement.submit();
|
116
117
|
},
|
118
|
+
getValues: () => {
|
119
|
+
const formElement = get().formElement;
|
120
|
+
invariant(formElement, "Cannot find reference to form. This is probably a bug in remix-validated-form.");
|
121
|
+
return new FormData(formElement);
|
122
|
+
},
|
117
123
|
resetFormElement: () => { var _a; return (_a = get().formElement) === null || _a === void 0 ? void 0 : _a.reset(); },
|
118
124
|
});
|
119
125
|
export const useRootFormStore = create()(immer((set, get) => ({
|
@@ -1,21 +1,28 @@
|
|
1
1
|
import React from "react";
|
2
|
-
export declare
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
2
|
+
export declare type FieldArrayValidationBehavior = "onChange" | "onSubmit";
|
3
|
+
export declare type FieldArrayValidationBehaviorOptions = {
|
4
|
+
initial: FieldArrayValidationBehavior;
|
5
|
+
whenSubmitted: FieldArrayValidationBehavior;
|
6
|
+
};
|
7
|
+
export declare type FieldArrayHelpers<Item = any> = {
|
8
|
+
push: (item: Item) => void;
|
8
9
|
swap: (indexA: number, indexB: number) => void;
|
9
10
|
move: (from: number, to: number) => void;
|
10
|
-
insert: (index: number, value:
|
11
|
-
unshift: () => void;
|
11
|
+
insert: (index: number, value: Item) => void;
|
12
|
+
unshift: (value: Item) => void;
|
12
13
|
remove: (index: number) => void;
|
13
14
|
pop: () => void;
|
14
|
-
replace: (index: number, value:
|
15
|
+
replace: (index: number, value: Item) => void;
|
16
|
+
};
|
17
|
+
export declare type UseFieldArrayOptions = {
|
18
|
+
formId?: string;
|
19
|
+
validationBehavior?: Partial<FieldArrayValidationBehaviorOptions>;
|
15
20
|
};
|
21
|
+
export declare function useFieldArray<Item = any>(name: string, { formId, validationBehavior }?: UseFieldArrayOptions): [itemDefaults: Item[], helpers: FieldArrayHelpers<any>, error: string | undefined];
|
16
22
|
export declare type FieldArrayProps = {
|
17
23
|
name: string;
|
18
|
-
children: (itemDefaults: any[], helpers: FieldArrayHelpers) => React.ReactNode;
|
24
|
+
children: (itemDefaults: any[], helpers: FieldArrayHelpers, error: string | undefined) => React.ReactNode;
|
19
25
|
formId?: string;
|
26
|
+
validationBehavior?: FieldArrayValidationBehaviorOptions;
|
20
27
|
};
|
21
|
-
export declare const FieldArray: ({ name, children, formId }: FieldArrayProps) =>
|
28
|
+
export declare const FieldArray: ({ name, children, formId, validationBehavior, }: FieldArrayProps) => React.ReactNode;
|
@@ -1,50 +1,73 @@
|
|
1
|
-
import {
|
2
|
-
import {
|
1
|
+
import { useMemo } from "react";
|
2
|
+
import { useCallback } from "react";
|
3
3
|
import invariant from "tiny-invariant";
|
4
|
-
import { useInternalFormContext } from "../hooks";
|
5
|
-
import {
|
4
|
+
import { useFieldDefaultValue, useFieldError, useInternalFormContext, useInternalHasBeenSubmitted, useValidateField, } from "../hooks";
|
5
|
+
import { useRegisterControlledField } from "./controlledFields";
|
6
6
|
import { useFormStore } from "./storeHooks";
|
7
|
-
const
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
const
|
7
|
+
const useInternalFieldArray = (context, field, validationBehavior) => {
|
8
|
+
const value = useFieldDefaultValue(field, context);
|
9
|
+
useRegisterControlledField(context, field);
|
10
|
+
const hasBeenSubmitted = useInternalHasBeenSubmitted(context.formId);
|
11
|
+
const validateField = useValidateField(context.formId);
|
12
|
+
const error = useFieldError(field, context);
|
13
|
+
const resolvedValidationBehavior = {
|
14
|
+
initial: "onSubmit",
|
15
|
+
whenSubmitted: "onChange",
|
16
|
+
...validationBehavior,
|
17
|
+
};
|
18
|
+
const behavior = hasBeenSubmitted
|
19
|
+
? resolvedValidationBehavior.whenSubmitted
|
20
|
+
: resolvedValidationBehavior.initial;
|
21
|
+
const maybeValidate = useCallback(() => {
|
22
|
+
if (behavior === "onChange") {
|
23
|
+
validateField(field);
|
24
|
+
}
|
25
|
+
}, [behavior, field, validateField]);
|
26
|
+
invariant(value === undefined || value === null || Array.isArray(value), `FieldArray: defaultValue value for ${field} must be an array, null, or undefined`);
|
14
27
|
const arr = useFormStore(context.formId, (state) => state.controlledFields.array);
|
15
28
|
const helpers = useMemo(() => ({
|
16
29
|
push: (item) => {
|
17
30
|
arr.push(field, item);
|
31
|
+
maybeValidate();
|
18
32
|
},
|
19
33
|
swap: (indexA, indexB) => {
|
20
34
|
arr.swap(field, indexA, indexB);
|
35
|
+
maybeValidate();
|
21
36
|
},
|
22
37
|
move: (from, to) => {
|
23
38
|
arr.move(field, from, to);
|
39
|
+
maybeValidate();
|
24
40
|
},
|
25
41
|
insert: (index, value) => {
|
26
42
|
arr.insert(field, index, value);
|
43
|
+
maybeValidate();
|
27
44
|
},
|
28
|
-
unshift: () => {
|
29
|
-
arr.unshift(field);
|
45
|
+
unshift: (value) => {
|
46
|
+
arr.unshift(field, value);
|
47
|
+
maybeValidate();
|
30
48
|
},
|
31
49
|
remove: (index) => {
|
32
50
|
arr.remove(field, index);
|
51
|
+
maybeValidate();
|
33
52
|
},
|
34
53
|
pop: () => {
|
35
54
|
arr.pop(field);
|
55
|
+
maybeValidate();
|
36
56
|
},
|
37
57
|
replace: (index, value) => {
|
38
58
|
arr.replace(field, index, value);
|
59
|
+
maybeValidate();
|
39
60
|
},
|
40
|
-
}), [arr, field]);
|
41
|
-
|
61
|
+
}), [arr, field, maybeValidate]);
|
62
|
+
const arrayValue = useMemo(() => value !== null && value !== void 0 ? value : [], [value]);
|
63
|
+
return [arrayValue, helpers, error];
|
42
64
|
};
|
43
|
-
export
|
44
|
-
export const FieldArray = ({ name, children, formId }) => {
|
65
|
+
export function useFieldArray(name, { formId, validationBehavior } = {}) {
|
45
66
|
const context = useInternalFormContext(formId, "FieldArray");
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
67
|
+
return useInternalFieldArray(context, name, validationBehavior);
|
68
|
+
}
|
69
|
+
export const FieldArray = ({ name, children, formId, validationBehavior, }) => {
|
70
|
+
const context = useInternalFormContext(formId, "FieldArray");
|
71
|
+
const [value, helpers, error] = useInternalFieldArray(context, name, validationBehavior);
|
72
|
+
return children(value, helpers, error);
|
50
73
|
};
|
@@ -51,6 +51,10 @@ export declare type FormHelpers = {
|
|
51
51
|
* _Note_: This is equivalent to clicking a button element with `type="submit"` or calling formElement.submit().
|
52
52
|
*/
|
53
53
|
submit: () => void;
|
54
|
+
/**
|
55
|
+
* Returns the current form values as FormData
|
56
|
+
*/
|
57
|
+
getValues: () => FormData;
|
54
58
|
};
|
55
59
|
/**
|
56
60
|
* Returns helpers that can be used to update the form state.
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { useMemo } from "react";
|
2
|
-
import { useInternalFormContext, useClearError, useSetTouched, useDefaultValuesForForm, useFieldErrorsForForm, useInternalIsSubmitting, useInternalHasBeenSubmitted, useTouchedFields, useInternalIsValid, useFieldErrors, useValidateField, useValidate, useSetFieldErrors, useResetFormElement, useSyncedDefaultValues, useFormActionProp, useFormSubactionProp, useSubmitForm, } from "../internal/hooks";
|
2
|
+
import { useInternalFormContext, useClearError, useSetTouched, useDefaultValuesForForm, useFieldErrorsForForm, useInternalIsSubmitting, useInternalHasBeenSubmitted, useTouchedFields, useInternalIsValid, useFieldErrors, useValidateField, useValidate, useSetFieldErrors, useResetFormElement, useSyncedDefaultValues, useFormActionProp, useFormSubactionProp, useSubmitForm, useFormValues, } from "../internal/hooks";
|
3
3
|
/**
|
4
4
|
* Returns information about the form.
|
5
5
|
*
|
@@ -53,6 +53,7 @@ export const useFormHelpers = (formId) => {
|
|
53
53
|
const setFieldErrors = useSetFieldErrors(formContext.formId);
|
54
54
|
const reset = useResetFormElement(formContext.formId);
|
55
55
|
const submit = useSubmitForm(formContext.formId);
|
56
|
+
const getValues = useFormValues(formContext.formId);
|
56
57
|
return useMemo(() => ({
|
57
58
|
setTouched,
|
58
59
|
validateField,
|
@@ -61,6 +62,7 @@ export const useFormHelpers = (formId) => {
|
|
61
62
|
clearAllErrors: () => setFieldErrors({}),
|
62
63
|
reset,
|
63
64
|
submit,
|
65
|
+
getValues,
|
64
66
|
}), [
|
65
67
|
clearError,
|
66
68
|
reset,
|
@@ -69,5 +71,6 @@ export const useFormHelpers = (formId) => {
|
|
69
71
|
submit,
|
70
72
|
validate,
|
71
73
|
validateField,
|
74
|
+
getValues,
|
72
75
|
]);
|
73
76
|
};
|
@@ -74,6 +74,10 @@ export declare type FormContextValue = {
|
|
74
74
|
* _Note_: This is equivalent to clicking a button element with `type="submit"` or calling formElement.submit().
|
75
75
|
*/
|
76
76
|
submit: () => void;
|
77
|
+
/**
|
78
|
+
* Returns the current form values as FormData
|
79
|
+
*/
|
80
|
+
getValues: () => FormData;
|
77
81
|
};
|
78
82
|
/**
|
79
83
|
* Provides access to some of the internal state of the form.
|
@@ -8,7 +8,7 @@ export const useFormContext = (formId) => {
|
|
8
8
|
// Try to access context so we get our error specific to this hook if it's not there
|
9
9
|
const context = useInternalFormContext(formId, "useFormContext");
|
10
10
|
const state = useFormState(formId);
|
11
|
-
const { clearError: internalClearError, setTouched, validateField, clearAllErrors, validate, reset, submit, } = useFormHelpers(formId);
|
11
|
+
const { clearError: internalClearError, setTouched, validateField, clearAllErrors, validate, reset, submit, getValues, } = useFormHelpers(formId);
|
12
12
|
const registerReceiveFocus = useRegisterReceiveFocus(context.formId);
|
13
13
|
const clearError = useCallback((...names) => {
|
14
14
|
names.forEach((name) => {
|
@@ -25,6 +25,7 @@ export const useFormContext = (formId) => {
|
|
25
25
|
validate,
|
26
26
|
reset,
|
27
27
|
submit,
|
28
|
+
getValues,
|
28
29
|
}), [
|
29
30
|
clearAllErrors,
|
30
31
|
clearError,
|
@@ -35,5 +36,6 @@ export const useFormContext = (formId) => {
|
|
35
36
|
submit,
|
36
37
|
validate,
|
37
38
|
validateField,
|
39
|
+
getValues,
|
38
40
|
]);
|
39
41
|
};
|