remix-validated-form 5.0.2 → 5.1.0
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/ValidatedForm.tsx +0 -427
- package/src/hooks.ts +0 -160
- package/src/index.ts +0 -12
- package/src/internal/MultiValueMap.ts +0 -44
- package/src/internal/constants.ts +0 -4
- package/src/internal/flatten.ts +0 -12
- package/src/internal/formContext.ts +0 -13
- package/src/internal/getInputProps.test.ts +0 -251
- package/src/internal/getInputProps.ts +0 -94
- package/src/internal/hooks.ts +0 -217
- package/src/internal/hydratable.ts +0 -28
- package/src/internal/logic/getCheckboxChecked.ts +0 -10
- package/src/internal/logic/getRadioChecked.ts +0 -18
- package/src/internal/logic/nestedObjectToPathObject.ts +0 -63
- package/src/internal/logic/requestSubmit.test.tsx +0 -24
- package/src/internal/logic/requestSubmit.ts +0 -103
- package/src/internal/state/arrayUtil.ts +0 -451
- package/src/internal/state/controlledFields.ts +0 -86
- package/src/internal/state/createFormStore.ts +0 -591
- package/src/internal/state/fieldArray.tsx +0 -197
- package/src/internal/state/storeHooks.ts +0 -9
- package/src/internal/state/types.ts +0 -1
- package/src/internal/submissionCallbacks.ts +0 -15
- package/src/internal/util.ts +0 -39
- package/src/server.ts +0 -53
- package/src/unreleased/formStateHooks.ts +0 -170
- package/src/userFacingFormContext.ts +0 -147
- package/src/validation/createValidator.ts +0 -53
- package/src/validation/types.ts +0 -72
- package/tsconfig.json +0 -8
@@ -1,591 +0,0 @@
|
|
1
|
-
import { WritableDraft } from "immer/dist/internal";
|
2
|
-
import { getPath, setPath } from "set-get";
|
3
|
-
import invariant from "tiny-invariant";
|
4
|
-
import { create, GetState } from "zustand";
|
5
|
-
import { immer } from "zustand/middleware/immer";
|
6
|
-
import {
|
7
|
-
FieldErrors,
|
8
|
-
TouchedFields,
|
9
|
-
ValidationResult,
|
10
|
-
Validator,
|
11
|
-
} from "../../validation/types";
|
12
|
-
import { requestSubmit } from "../logic/requestSubmit";
|
13
|
-
import * as arrayUtil from "./arrayUtil";
|
14
|
-
import { InternalFormId } from "./types";
|
15
|
-
|
16
|
-
export type SyncedFormProps = {
|
17
|
-
formId?: string;
|
18
|
-
action?: string;
|
19
|
-
subaction?: string;
|
20
|
-
defaultValues: { [fieldName: string]: any };
|
21
|
-
registerReceiveFocus: (fieldName: string, handler: () => void) => () => void;
|
22
|
-
validator: Validator<unknown>;
|
23
|
-
};
|
24
|
-
|
25
|
-
export type SmartValidateOpts = {
|
26
|
-
alwaysIncludeErrorsFromFields?: string[];
|
27
|
-
};
|
28
|
-
|
29
|
-
export type FormStoreState = {
|
30
|
-
forms: { [formId: InternalFormId]: FormState };
|
31
|
-
form: (formId: InternalFormId) => FormState;
|
32
|
-
registerForm: (formId: InternalFormId) => void;
|
33
|
-
cleanupForm: (formId: InternalFormId) => void;
|
34
|
-
};
|
35
|
-
|
36
|
-
export type FormState = {
|
37
|
-
isHydrated: boolean;
|
38
|
-
isSubmitting: boolean;
|
39
|
-
hasBeenSubmitted: boolean;
|
40
|
-
fieldErrors: FieldErrors;
|
41
|
-
touchedFields: TouchedFields;
|
42
|
-
formProps?: SyncedFormProps;
|
43
|
-
formElement: HTMLFormElement | null;
|
44
|
-
currentDefaultValues: Record<string, any>;
|
45
|
-
|
46
|
-
isValid: () => boolean;
|
47
|
-
startSubmit: () => void;
|
48
|
-
endSubmit: () => void;
|
49
|
-
setTouched: (field: string, touched: boolean) => void;
|
50
|
-
setFieldError: (field: string, error: string) => void;
|
51
|
-
setFieldErrors: (errors: FieldErrors) => void;
|
52
|
-
clearFieldError: (field: string) => void;
|
53
|
-
reset: () => void;
|
54
|
-
syncFormProps: (props: SyncedFormProps) => void;
|
55
|
-
setFormElement: (formElement: HTMLFormElement | null) => void;
|
56
|
-
validate: () => Promise<ValidationResult<unknown>>;
|
57
|
-
smartValidate: (
|
58
|
-
opts?: SmartValidateOpts
|
59
|
-
) => Promise<ValidationResult<unknown>>;
|
60
|
-
resetFormElement: () => void;
|
61
|
-
submit: () => void;
|
62
|
-
getValues: () => FormData;
|
63
|
-
|
64
|
-
controlledFields: {
|
65
|
-
values: { [fieldName: string]: any };
|
66
|
-
refCounts: { [fieldName: string]: number };
|
67
|
-
valueUpdatePromises: { [fieldName: string]: Promise<void> };
|
68
|
-
valueUpdateResolvers: { [fieldName: string]: () => void };
|
69
|
-
|
70
|
-
register: (fieldName: string) => void;
|
71
|
-
unregister: (fieldName: string) => void;
|
72
|
-
setValue: (fieldName: string, value: unknown) => void;
|
73
|
-
kickoffValueUpdate: (fieldName: string) => void;
|
74
|
-
getValue: (fieldName: string) => unknown;
|
75
|
-
awaitValueUpdate: (fieldName: string) => Promise<void>;
|
76
|
-
|
77
|
-
array: {
|
78
|
-
push: (fieldName: string, value: unknown) => void;
|
79
|
-
swap: (fieldName: string, indexA: number, indexB: number) => void;
|
80
|
-
move: (fieldName: string, fromIndex: number, toIndex: number) => void;
|
81
|
-
insert: (fieldName: string, index: number, value: unknown) => void;
|
82
|
-
unshift: (fieldName: string, value: unknown) => void;
|
83
|
-
remove: (fieldName: string, index: number) => void;
|
84
|
-
pop: (fieldName: string) => void;
|
85
|
-
replace: (fieldName: string, index: number, value: unknown) => void;
|
86
|
-
};
|
87
|
-
};
|
88
|
-
};
|
89
|
-
|
90
|
-
const noOp = () => {};
|
91
|
-
const defaultFormState: FormState = {
|
92
|
-
isHydrated: false,
|
93
|
-
isSubmitting: false,
|
94
|
-
hasBeenSubmitted: false,
|
95
|
-
touchedFields: {},
|
96
|
-
fieldErrors: {},
|
97
|
-
formElement: null,
|
98
|
-
isValid: () => true,
|
99
|
-
startSubmit: noOp,
|
100
|
-
endSubmit: noOp,
|
101
|
-
setTouched: noOp,
|
102
|
-
setFieldError: noOp,
|
103
|
-
setFieldErrors: noOp,
|
104
|
-
clearFieldError: noOp,
|
105
|
-
currentDefaultValues: {},
|
106
|
-
|
107
|
-
reset: () => noOp,
|
108
|
-
syncFormProps: noOp,
|
109
|
-
setFormElement: noOp,
|
110
|
-
|
111
|
-
validate: async () => {
|
112
|
-
throw new Error("Validate called before form was initialized.");
|
113
|
-
},
|
114
|
-
|
115
|
-
smartValidate: async () => {
|
116
|
-
throw new Error("Validate called before form was initialized.");
|
117
|
-
},
|
118
|
-
|
119
|
-
submit: async () => {
|
120
|
-
throw new Error("Submit called before form was initialized.");
|
121
|
-
},
|
122
|
-
|
123
|
-
resetFormElement: noOp,
|
124
|
-
getValues: () => new FormData(),
|
125
|
-
|
126
|
-
controlledFields: {
|
127
|
-
values: {},
|
128
|
-
refCounts: {},
|
129
|
-
valueUpdatePromises: {},
|
130
|
-
valueUpdateResolvers: {},
|
131
|
-
|
132
|
-
register: noOp,
|
133
|
-
unregister: noOp,
|
134
|
-
setValue: noOp,
|
135
|
-
getValue: noOp,
|
136
|
-
kickoffValueUpdate: noOp,
|
137
|
-
awaitValueUpdate: async () => {
|
138
|
-
throw new Error("AwaitValueUpdate called before form was initialized.");
|
139
|
-
},
|
140
|
-
|
141
|
-
array: {
|
142
|
-
push: noOp,
|
143
|
-
swap: noOp,
|
144
|
-
move: noOp,
|
145
|
-
insert: noOp,
|
146
|
-
unshift: noOp,
|
147
|
-
remove: noOp,
|
148
|
-
pop: noOp,
|
149
|
-
replace: noOp,
|
150
|
-
},
|
151
|
-
},
|
152
|
-
};
|
153
|
-
|
154
|
-
const createFormState = (
|
155
|
-
set: (setter: (draft: WritableDraft<FormState>) => void) => void,
|
156
|
-
get: GetState<FormState>
|
157
|
-
): FormState => ({
|
158
|
-
// It's not "hydrated" until the form props are synced
|
159
|
-
isHydrated: false,
|
160
|
-
isSubmitting: false,
|
161
|
-
hasBeenSubmitted: false,
|
162
|
-
touchedFields: {},
|
163
|
-
fieldErrors: {},
|
164
|
-
formElement: null,
|
165
|
-
currentDefaultValues: {},
|
166
|
-
|
167
|
-
isValid: () => Object.keys(get().fieldErrors).length === 0,
|
168
|
-
startSubmit: () =>
|
169
|
-
set((state) => {
|
170
|
-
state.isSubmitting = true;
|
171
|
-
state.hasBeenSubmitted = true;
|
172
|
-
}),
|
173
|
-
endSubmit: () =>
|
174
|
-
set((state) => {
|
175
|
-
state.isSubmitting = false;
|
176
|
-
}),
|
177
|
-
setTouched: (fieldName, touched) =>
|
178
|
-
set((state) => {
|
179
|
-
state.touchedFields[fieldName] = touched;
|
180
|
-
}),
|
181
|
-
setFieldError: (fieldName: string, error: string) =>
|
182
|
-
set((state) => {
|
183
|
-
state.fieldErrors[fieldName] = error;
|
184
|
-
}),
|
185
|
-
setFieldErrors: (errors: FieldErrors) =>
|
186
|
-
set((state) => {
|
187
|
-
state.fieldErrors = errors;
|
188
|
-
}),
|
189
|
-
clearFieldError: (fieldName: string) =>
|
190
|
-
set((state) => {
|
191
|
-
delete state.fieldErrors[fieldName];
|
192
|
-
}),
|
193
|
-
reset: () =>
|
194
|
-
set((state) => {
|
195
|
-
state.fieldErrors = {};
|
196
|
-
state.touchedFields = {};
|
197
|
-
state.hasBeenSubmitted = false;
|
198
|
-
const nextDefaults = state.formProps?.defaultValues ?? {};
|
199
|
-
state.controlledFields.values = nextDefaults;
|
200
|
-
state.currentDefaultValues = nextDefaults;
|
201
|
-
}),
|
202
|
-
syncFormProps: (props: SyncedFormProps) =>
|
203
|
-
set((state) => {
|
204
|
-
if (!state.isHydrated) {
|
205
|
-
state.controlledFields.values = props.defaultValues;
|
206
|
-
state.currentDefaultValues = props.defaultValues;
|
207
|
-
}
|
208
|
-
|
209
|
-
state.formProps = props;
|
210
|
-
state.isHydrated = true;
|
211
|
-
}),
|
212
|
-
setFormElement: (formElement: HTMLFormElement | null) => {
|
213
|
-
// This gets called frequently, so we want to avoid calling set() every time
|
214
|
-
// Or else we wind up with an infinite loop
|
215
|
-
if (get().formElement === formElement) return;
|
216
|
-
set((state) => {
|
217
|
-
// weird type issue here
|
218
|
-
// seems to be because formElement is a writable draft
|
219
|
-
state.formElement = formElement as any;
|
220
|
-
});
|
221
|
-
},
|
222
|
-
validate: async () => {
|
223
|
-
const formElement = get().formElement;
|
224
|
-
invariant(
|
225
|
-
formElement,
|
226
|
-
"Cannot find reference to form. This is probably a bug in remix-validated-form."
|
227
|
-
);
|
228
|
-
|
229
|
-
const validator = get().formProps?.validator;
|
230
|
-
invariant(
|
231
|
-
validator,
|
232
|
-
"Cannot find validator. This is probably a bug in remix-validated-form."
|
233
|
-
);
|
234
|
-
|
235
|
-
const result = await validator.validate(new FormData(formElement));
|
236
|
-
if (result.error) get().setFieldErrors(result.error.fieldErrors);
|
237
|
-
return result;
|
238
|
-
},
|
239
|
-
|
240
|
-
smartValidate: async ({ alwaysIncludeErrorsFromFields = [] } = {}) => {
|
241
|
-
const formElement = get().formElement;
|
242
|
-
invariant(
|
243
|
-
formElement,
|
244
|
-
"Cannot find reference to form. This is probably a bug in remix-validated-form."
|
245
|
-
);
|
246
|
-
|
247
|
-
const validator = get().formProps?.validator;
|
248
|
-
invariant(
|
249
|
-
validator,
|
250
|
-
"Cannot find validator. This is probably a bug in remix-validated-form."
|
251
|
-
);
|
252
|
-
|
253
|
-
await Promise.all(
|
254
|
-
alwaysIncludeErrorsFromFields.map((field) =>
|
255
|
-
get().controlledFields.awaitValueUpdate?.(field)
|
256
|
-
)
|
257
|
-
);
|
258
|
-
|
259
|
-
const validationResult = await validator.validate(
|
260
|
-
new FormData(formElement)
|
261
|
-
);
|
262
|
-
if (!validationResult.error) {
|
263
|
-
// Only update the field errors if it hasn't changed
|
264
|
-
const hadErrors = Object.keys(get().fieldErrors).length > 0;
|
265
|
-
if (hadErrors) get().setFieldErrors({});
|
266
|
-
return validationResult;
|
267
|
-
}
|
268
|
-
|
269
|
-
const {
|
270
|
-
error: { fieldErrors },
|
271
|
-
} = validationResult;
|
272
|
-
const errorFields = new Set<string>();
|
273
|
-
const incomingErrors = new Set<string>();
|
274
|
-
const prevErrors = new Set<string>();
|
275
|
-
|
276
|
-
Object.keys(fieldErrors).forEach((field) => {
|
277
|
-
errorFields.add(field);
|
278
|
-
incomingErrors.add(field);
|
279
|
-
});
|
280
|
-
|
281
|
-
Object.keys(get().fieldErrors).forEach((field) => {
|
282
|
-
errorFields.add(field);
|
283
|
-
prevErrors.add(field);
|
284
|
-
});
|
285
|
-
|
286
|
-
const fieldsToUpdate = new Set<string>();
|
287
|
-
const fieldsToDelete = new Set<string>();
|
288
|
-
|
289
|
-
errorFields.forEach((field) => {
|
290
|
-
// If an error has been cleared, remove it.
|
291
|
-
if (!incomingErrors.has(field)) {
|
292
|
-
fieldsToDelete.add(field);
|
293
|
-
return;
|
294
|
-
}
|
295
|
-
|
296
|
-
// If an error has changed, we should update it.
|
297
|
-
if (prevErrors.has(field) && incomingErrors.has(field)) {
|
298
|
-
// Only update if the error has changed to avoid unnecessary rerenders
|
299
|
-
if (fieldErrors[field] !== get().fieldErrors[field])
|
300
|
-
fieldsToUpdate.add(field);
|
301
|
-
return;
|
302
|
-
}
|
303
|
-
|
304
|
-
// If the error is always included, then we should update it.
|
305
|
-
if (alwaysIncludeErrorsFromFields.includes(field)) {
|
306
|
-
fieldsToUpdate.add(field);
|
307
|
-
return;
|
308
|
-
}
|
309
|
-
|
310
|
-
// If the error is new, then only update if the field has been touched
|
311
|
-
// or if the form has been submitted
|
312
|
-
if (!prevErrors.has(field)) {
|
313
|
-
const fieldTouched = get().touchedFields[field];
|
314
|
-
const formHasBeenSubmitted = get().hasBeenSubmitted;
|
315
|
-
if (fieldTouched || formHasBeenSubmitted) fieldsToUpdate.add(field);
|
316
|
-
return;
|
317
|
-
}
|
318
|
-
});
|
319
|
-
|
320
|
-
if (fieldsToDelete.size === 0 && fieldsToUpdate.size === 0) {
|
321
|
-
return { ...validationResult, error: { fieldErrors: get().fieldErrors } };
|
322
|
-
}
|
323
|
-
|
324
|
-
set((state) => {
|
325
|
-
fieldsToDelete.forEach((field) => {
|
326
|
-
delete state.fieldErrors[field];
|
327
|
-
});
|
328
|
-
|
329
|
-
fieldsToUpdate.forEach((field) => {
|
330
|
-
state.fieldErrors[field] = fieldErrors[field];
|
331
|
-
});
|
332
|
-
});
|
333
|
-
|
334
|
-
return { ...validationResult, error: { fieldErrors: get().fieldErrors } };
|
335
|
-
},
|
336
|
-
|
337
|
-
submit: () => {
|
338
|
-
const formElement = get().formElement;
|
339
|
-
invariant(
|
340
|
-
formElement,
|
341
|
-
"Cannot find reference to form. This is probably a bug in remix-validated-form."
|
342
|
-
);
|
343
|
-
|
344
|
-
requestSubmit(formElement);
|
345
|
-
},
|
346
|
-
|
347
|
-
getValues: () => new FormData(get().formElement ?? undefined),
|
348
|
-
|
349
|
-
resetFormElement: () => get().formElement?.reset(),
|
350
|
-
|
351
|
-
controlledFields: {
|
352
|
-
values: {},
|
353
|
-
refCounts: {},
|
354
|
-
valueUpdatePromises: {},
|
355
|
-
valueUpdateResolvers: {},
|
356
|
-
|
357
|
-
register: (fieldName) => {
|
358
|
-
set((state) => {
|
359
|
-
const current = state.controlledFields.refCounts[fieldName] ?? 0;
|
360
|
-
state.controlledFields.refCounts[fieldName] = current + 1;
|
361
|
-
});
|
362
|
-
},
|
363
|
-
unregister: (fieldName) => {
|
364
|
-
// For this helper in particular, we may run into a case where state is undefined.
|
365
|
-
// When the whole form unmounts, the form state may be cleaned up before the fields are.
|
366
|
-
if (get() === null || get() === undefined) return;
|
367
|
-
set((state) => {
|
368
|
-
const current = state.controlledFields.refCounts[fieldName] ?? 0;
|
369
|
-
if (current > 1) {
|
370
|
-
state.controlledFields.refCounts[fieldName] = current - 1;
|
371
|
-
return;
|
372
|
-
}
|
373
|
-
|
374
|
-
const isNested = Object.keys(state.controlledFields.refCounts).some(
|
375
|
-
(key) => fieldName.startsWith(key) && key !== fieldName
|
376
|
-
);
|
377
|
-
|
378
|
-
// When nested within a field array, we should leave resetting up to the field array
|
379
|
-
if (!isNested) {
|
380
|
-
setPath(
|
381
|
-
state.controlledFields.values,
|
382
|
-
fieldName,
|
383
|
-
getPath(state.formProps?.defaultValues, fieldName)
|
384
|
-
);
|
385
|
-
setPath(
|
386
|
-
state.currentDefaultValues,
|
387
|
-
fieldName,
|
388
|
-
getPath(state.formProps?.defaultValues, fieldName)
|
389
|
-
);
|
390
|
-
}
|
391
|
-
|
392
|
-
delete state.controlledFields.refCounts[fieldName];
|
393
|
-
});
|
394
|
-
},
|
395
|
-
getValue: (fieldName) => getPath(get().controlledFields.values, fieldName),
|
396
|
-
setValue: (fieldName, value) => {
|
397
|
-
set((state) => {
|
398
|
-
setPath(state.controlledFields.values, fieldName, value);
|
399
|
-
});
|
400
|
-
get().controlledFields.kickoffValueUpdate(fieldName);
|
401
|
-
},
|
402
|
-
kickoffValueUpdate: (fieldName) => {
|
403
|
-
const clear = () =>
|
404
|
-
set((state) => {
|
405
|
-
delete state.controlledFields.valueUpdateResolvers[fieldName];
|
406
|
-
delete state.controlledFields.valueUpdatePromises[fieldName];
|
407
|
-
});
|
408
|
-
set((state) => {
|
409
|
-
const promise = new Promise<void>((resolve) => {
|
410
|
-
state.controlledFields.valueUpdateResolvers[fieldName] = resolve;
|
411
|
-
}).then(clear);
|
412
|
-
state.controlledFields.valueUpdatePromises[fieldName] = promise;
|
413
|
-
});
|
414
|
-
},
|
415
|
-
|
416
|
-
awaitValueUpdate: async (fieldName) => {
|
417
|
-
await get().controlledFields.valueUpdatePromises[fieldName];
|
418
|
-
},
|
419
|
-
|
420
|
-
array: {
|
421
|
-
push: (fieldName, item) => {
|
422
|
-
set((state) => {
|
423
|
-
arrayUtil
|
424
|
-
.getArray(state.controlledFields.values, fieldName)
|
425
|
-
.push(item);
|
426
|
-
arrayUtil.getArray(state.currentDefaultValues, fieldName).push(item);
|
427
|
-
// New item added to the end, no need to update touched or error
|
428
|
-
});
|
429
|
-
get().controlledFields.kickoffValueUpdate(fieldName);
|
430
|
-
},
|
431
|
-
|
432
|
-
swap: (fieldName, indexA, indexB) => {
|
433
|
-
set((state) => {
|
434
|
-
arrayUtil.swap(
|
435
|
-
arrayUtil.getArray(state.controlledFields.values, fieldName),
|
436
|
-
indexA,
|
437
|
-
indexB
|
438
|
-
);
|
439
|
-
arrayUtil.swap(
|
440
|
-
arrayUtil.getArray(state.currentDefaultValues, fieldName),
|
441
|
-
indexA,
|
442
|
-
indexB
|
443
|
-
);
|
444
|
-
arrayUtil.mutateAsArray(fieldName, state.touchedFields, (array) =>
|
445
|
-
arrayUtil.swap(array, indexA, indexB)
|
446
|
-
);
|
447
|
-
arrayUtil.mutateAsArray(fieldName, state.fieldErrors, (array) =>
|
448
|
-
arrayUtil.swap(array, indexA, indexB)
|
449
|
-
);
|
450
|
-
});
|
451
|
-
get().controlledFields.kickoffValueUpdate(fieldName);
|
452
|
-
},
|
453
|
-
|
454
|
-
move: (fieldName, from, to) => {
|
455
|
-
set((state) => {
|
456
|
-
arrayUtil.move(
|
457
|
-
arrayUtil.getArray(state.controlledFields.values, fieldName),
|
458
|
-
from,
|
459
|
-
to
|
460
|
-
);
|
461
|
-
arrayUtil.move(
|
462
|
-
arrayUtil.getArray(state.currentDefaultValues, fieldName),
|
463
|
-
from,
|
464
|
-
to
|
465
|
-
);
|
466
|
-
arrayUtil.mutateAsArray(fieldName, state.touchedFields, (array) =>
|
467
|
-
arrayUtil.move(array, from, to)
|
468
|
-
);
|
469
|
-
arrayUtil.mutateAsArray(fieldName, state.fieldErrors, (array) =>
|
470
|
-
arrayUtil.move(array, from, to)
|
471
|
-
);
|
472
|
-
});
|
473
|
-
get().controlledFields.kickoffValueUpdate(fieldName);
|
474
|
-
},
|
475
|
-
insert: (fieldName, index, item) => {
|
476
|
-
set((state) => {
|
477
|
-
arrayUtil.insert(
|
478
|
-
arrayUtil.getArray(state.controlledFields.values, fieldName),
|
479
|
-
index,
|
480
|
-
item
|
481
|
-
);
|
482
|
-
arrayUtil.insert(
|
483
|
-
arrayUtil.getArray(state.currentDefaultValues, fieldName),
|
484
|
-
index,
|
485
|
-
item
|
486
|
-
);
|
487
|
-
// Even though this is a new item, we need to push around other items.
|
488
|
-
arrayUtil.mutateAsArray(fieldName, state.touchedFields, (array) =>
|
489
|
-
arrayUtil.insertEmpty(array, index)
|
490
|
-
);
|
491
|
-
arrayUtil.mutateAsArray(fieldName, state.fieldErrors, (array) =>
|
492
|
-
arrayUtil.insertEmpty(array, index)
|
493
|
-
);
|
494
|
-
});
|
495
|
-
get().controlledFields.kickoffValueUpdate(fieldName);
|
496
|
-
},
|
497
|
-
remove: (fieldName, index) => {
|
498
|
-
set((state) => {
|
499
|
-
arrayUtil.remove(
|
500
|
-
arrayUtil.getArray(state.controlledFields.values, fieldName),
|
501
|
-
index
|
502
|
-
);
|
503
|
-
arrayUtil.remove(
|
504
|
-
arrayUtil.getArray(state.currentDefaultValues, fieldName),
|
505
|
-
index
|
506
|
-
);
|
507
|
-
arrayUtil.mutateAsArray(fieldName, state.touchedFields, (array) =>
|
508
|
-
arrayUtil.remove(array, index)
|
509
|
-
);
|
510
|
-
arrayUtil.mutateAsArray(fieldName, state.fieldErrors, (array) =>
|
511
|
-
arrayUtil.remove(array, index)
|
512
|
-
);
|
513
|
-
});
|
514
|
-
get().controlledFields.kickoffValueUpdate(fieldName);
|
515
|
-
},
|
516
|
-
pop: (fieldName) => {
|
517
|
-
set((state) => {
|
518
|
-
arrayUtil.getArray(state.controlledFields.values, fieldName).pop();
|
519
|
-
arrayUtil.getArray(state.currentDefaultValues, fieldName).pop();
|
520
|
-
arrayUtil.mutateAsArray(fieldName, state.touchedFields, (array) =>
|
521
|
-
array.pop()
|
522
|
-
);
|
523
|
-
arrayUtil.mutateAsArray(fieldName, state.fieldErrors, (array) =>
|
524
|
-
array.pop()
|
525
|
-
);
|
526
|
-
});
|
527
|
-
get().controlledFields.kickoffValueUpdate(fieldName);
|
528
|
-
},
|
529
|
-
unshift: (fieldName, value) => {
|
530
|
-
set((state) => {
|
531
|
-
arrayUtil
|
532
|
-
.getArray(state.controlledFields.values, fieldName)
|
533
|
-
.unshift(value);
|
534
|
-
arrayUtil
|
535
|
-
.getArray(state.currentDefaultValues, fieldName)
|
536
|
-
.unshift(value);
|
537
|
-
arrayUtil.mutateAsArray(fieldName, state.touchedFields, (array) =>
|
538
|
-
arrayUtil.insertEmpty(array, 0)
|
539
|
-
);
|
540
|
-
arrayUtil.mutateAsArray(fieldName, state.fieldErrors, (array) =>
|
541
|
-
arrayUtil.insertEmpty(array, 0)
|
542
|
-
);
|
543
|
-
});
|
544
|
-
},
|
545
|
-
replace: (fieldName, index, item) => {
|
546
|
-
set((state) => {
|
547
|
-
arrayUtil.replace(
|
548
|
-
arrayUtil.getArray(state.controlledFields.values, fieldName),
|
549
|
-
index,
|
550
|
-
item
|
551
|
-
);
|
552
|
-
arrayUtil.replace(
|
553
|
-
arrayUtil.getArray(state.currentDefaultValues, fieldName),
|
554
|
-
index,
|
555
|
-
item
|
556
|
-
);
|
557
|
-
arrayUtil.mutateAsArray(fieldName, state.touchedFields, (array) =>
|
558
|
-
arrayUtil.replace(array, index, item)
|
559
|
-
);
|
560
|
-
arrayUtil.mutateAsArray(fieldName, state.fieldErrors, (array) =>
|
561
|
-
arrayUtil.replace(array, index, item)
|
562
|
-
);
|
563
|
-
});
|
564
|
-
get().controlledFields.kickoffValueUpdate(fieldName);
|
565
|
-
},
|
566
|
-
},
|
567
|
-
},
|
568
|
-
});
|
569
|
-
|
570
|
-
export const useRootFormStore = create<FormStoreState>()(
|
571
|
-
immer((set, get) => ({
|
572
|
-
forms: {},
|
573
|
-
form: (formId) => {
|
574
|
-
return get().forms[formId] ?? defaultFormState;
|
575
|
-
},
|
576
|
-
cleanupForm: (formId: InternalFormId) => {
|
577
|
-
set((state) => {
|
578
|
-
delete state.forms[formId];
|
579
|
-
});
|
580
|
-
},
|
581
|
-
registerForm: (formId: InternalFormId) => {
|
582
|
-
if (get().forms[formId]) return;
|
583
|
-
set((state) => {
|
584
|
-
state.forms[formId] = createFormState(
|
585
|
-
(setter) => set((state) => setter(state.forms[formId])),
|
586
|
-
() => get().forms[formId]
|
587
|
-
) as WritableDraft<FormState>;
|
588
|
-
});
|
589
|
-
},
|
590
|
-
}))
|
591
|
-
);
|