remix-validated-form 5.0.2 → 5.1.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/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
|
-
);
|