@octanejs/hook-form 0.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/LICENSE +21 -0
- package/README.md +62 -0
- package/package.json +46 -0
- package/src/FormProvider.tsrx +76 -0
- package/src/FormProvider.tsrx.d.ts +10 -0
- package/src/constants.ts +31 -0
- package/src/controller.tsrx +8 -0
- package/src/controller.tsrx.d.ts +52 -0
- package/src/form.tsrx +119 -0
- package/src/form.tsrx.d.ts +6 -0
- package/src/formStateSubscribe.tsrx +10 -0
- package/src/formStateSubscribe.tsrx.d.ts +16 -0
- package/src/index.ts +19 -0
- package/src/logic/appendErrors.ts +19 -0
- package/src/logic/createFormControl.ts +1841 -0
- package/src/logic/generateId.ts +14 -0
- package/src/logic/generateWatchOutput.ts +27 -0
- package/src/logic/getCheckboxValue.ts +36 -0
- package/src/logic/getDirtyFields.ts +120 -0
- package/src/logic/getEventValue.ts +12 -0
- package/src/logic/getFieldValue.ts +33 -0
- package/src/logic/getFieldValueAs.ts +22 -0
- package/src/logic/getFocusFieldName.ts +13 -0
- package/src/logic/getNodeParentName.ts +4 -0
- package/src/logic/getProxyFormState.ts +33 -0
- package/src/logic/getRadioValue.ts +24 -0
- package/src/logic/getResolverOptions.ts +33 -0
- package/src/logic/getRuleValue.ts +16 -0
- package/src/logic/getValidateError.ts +22 -0
- package/src/logic/getValidationModes.ts +11 -0
- package/src/logic/getValueAndMessage.ts +12 -0
- package/src/logic/hasPromiseValidation.ts +27 -0
- package/src/logic/hasValidation.ts +12 -0
- package/src/logic/index.ts +3 -0
- package/src/logic/isNameInFieldArray.ts +7 -0
- package/src/logic/isWatched.ts +11 -0
- package/src/logic/iterateFieldsByAction.ts +37 -0
- package/src/logic/schemaErrorLookup.ts +54 -0
- package/src/logic/shouldRenderFormState.ts +25 -0
- package/src/logic/shouldSubscribeByName.ts +18 -0
- package/src/logic/skipValidation.ts +21 -0
- package/src/logic/unsetEmptyArray.ts +6 -0
- package/src/logic/updateFieldArrayRootError.ts +17 -0
- package/src/logic/validateField.ts +273 -0
- package/src/types/controller.ts +98 -0
- package/src/types/errors.ts +56 -0
- package/src/types/events.ts +26 -0
- package/src/types/fieldArray.ts +288 -0
- package/src/types/fields.ts +46 -0
- package/src/types/form.ts +961 -0
- package/src/types/index.ts +12 -0
- package/src/types/path/common.ts +391 -0
- package/src/types/path/eager.ts +224 -0
- package/src/types/path/index.ts +16 -0
- package/src/types/resolvers.ts +38 -0
- package/src/types/utils.ts +125 -0
- package/src/types/validator.ts +94 -0
- package/src/types/watch.ts +66 -0
- package/src/useController.ts +268 -0
- package/src/useFieldArray.ts +490 -0
- package/src/useForm.ts +182 -0
- package/src/useFormContext.ts +47 -0
- package/src/useFormControlContext.ts +25 -0
- package/src/useFormState.ts +86 -0
- package/src/useIsomorphicLayoutEffect.ts +6 -0
- package/src/useWatch.ts +356 -0
- package/src/utils/append.ts +4 -0
- package/src/utils/cloneObject.ts +32 -0
- package/src/utils/compact.ts +2 -0
- package/src/utils/convertToArrayPayload.ts +2 -0
- package/src/utils/createSubject.ts +48 -0
- package/src/utils/deepEqual.ts +81 -0
- package/src/utils/deepMerge.ts +27 -0
- package/src/utils/extractFormValues.ts +28 -0
- package/src/utils/fillEmptyArray.ts +3 -0
- package/src/utils/flatten.ts +23 -0
- package/src/utils/get.ts +29 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/insert.ts +8 -0
- package/src/utils/isBoolean.ts +2 -0
- package/src/utils/isCheckBoxInput.ts +4 -0
- package/src/utils/isDateObject.ts +2 -0
- package/src/utils/isEmptyObject.ts +7 -0
- package/src/utils/isFileInput.ts +4 -0
- package/src/utils/isFunction.ts +2 -0
- package/src/utils/isHTMLElement.ts +13 -0
- package/src/utils/isKey.ts +4 -0
- package/src/utils/isMultipleSelect.ts +5 -0
- package/src/utils/isNullOrUndefined.ts +2 -0
- package/src/utils/isObject.ts +8 -0
- package/src/utils/isPlainObject.ts +8 -0
- package/src/utils/isPrimitive.ts +8 -0
- package/src/utils/isRadioInput.ts +4 -0
- package/src/utils/isRadioOrCheckbox.ts +8 -0
- package/src/utils/isRegex.ts +2 -0
- package/src/utils/isString.ts +2 -0
- package/src/utils/isUndefined.ts +2 -0
- package/src/utils/isWeb.ts +4 -0
- package/src/utils/live.ts +6 -0
- package/src/utils/move.ts +15 -0
- package/src/utils/noop.ts +2 -0
- package/src/utils/objectHasFunction.ts +11 -0
- package/src/utils/prepend.ts +7 -0
- package/src/utils/remove.ts +24 -0
- package/src/utils/set.ts +36 -0
- package/src/utils/sleep.ts +2 -0
- package/src/utils/stringToPath.ts +4 -0
- package/src/utils/swap.ts +4 -0
- package/src/utils/unset.ts +65 -0
- package/src/utils/update.ts +5 -0
- package/src/watch.tsrx +7 -0
- package/src/watch.tsrx.d.ts +42 -0
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
// Vendored from react-hook-form@7.81.0 src/useFieldArray.ts (octane port).
|
|
2
|
+
import { useState, useRef, useEffect, useMemo, useCallback } from 'octane';
|
|
3
|
+
|
|
4
|
+
import generateId from './logic/generateId';
|
|
5
|
+
import getFocusFieldName from './logic/getFocusFieldName';
|
|
6
|
+
import getValidationModes from './logic/getValidationModes';
|
|
7
|
+
import isWatched from './logic/isWatched';
|
|
8
|
+
import iterateFieldsByAction from './logic/iterateFieldsByAction';
|
|
9
|
+
import updateFieldArrayRootError from './logic/updateFieldArrayRootError';
|
|
10
|
+
import validateField from './logic/validateField';
|
|
11
|
+
import appendAt from './utils/append';
|
|
12
|
+
import cloneObject from './utils/cloneObject';
|
|
13
|
+
import convertToArrayPayload from './utils/convertToArrayPayload';
|
|
14
|
+
import fillEmptyArray from './utils/fillEmptyArray';
|
|
15
|
+
import get from './utils/get';
|
|
16
|
+
import insertAt from './utils/insert';
|
|
17
|
+
import isBoolean from './utils/isBoolean';
|
|
18
|
+
import isEmptyObject from './utils/isEmptyObject';
|
|
19
|
+
import isObject from './utils/isObject';
|
|
20
|
+
import moveArrayAt from './utils/move';
|
|
21
|
+
import prependAt from './utils/prepend';
|
|
22
|
+
import removeArrayAt from './utils/remove';
|
|
23
|
+
import set from './utils/set';
|
|
24
|
+
import swapArrayAt from './utils/swap';
|
|
25
|
+
import unset from './utils/unset';
|
|
26
|
+
import updateAt from './utils/update';
|
|
27
|
+
import { VALIDATION_MODE } from './constants';
|
|
28
|
+
import type {
|
|
29
|
+
Control,
|
|
30
|
+
Field,
|
|
31
|
+
FieldArray,
|
|
32
|
+
FieldArrayMethodProps,
|
|
33
|
+
FieldArrayPath,
|
|
34
|
+
FieldArrayWithId,
|
|
35
|
+
FieldError,
|
|
36
|
+
FieldErrors,
|
|
37
|
+
FieldPath,
|
|
38
|
+
FieldValues,
|
|
39
|
+
FormState,
|
|
40
|
+
InternalFieldName,
|
|
41
|
+
RegisterOptions,
|
|
42
|
+
UseFieldArrayProps,
|
|
43
|
+
UseFieldArrayReturn,
|
|
44
|
+
} from './types';
|
|
45
|
+
import { useFormControlContext } from './useFormControlContext';
|
|
46
|
+
import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect';
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* A custom hook that exposes convenient methods to perform operations with a list of dynamic inputs that need to be appended, updated, removed etc. • [Demo](https://codesandbox.io/s/react-hook-form-usefieldarray-ssugn) • [Video](https://youtu.be/4MrbfGSFY2A)
|
|
50
|
+
*
|
|
51
|
+
* @remarks
|
|
52
|
+
* [API](https://react-hook-form.com/docs/usefieldarray) • [Demo](https://codesandbox.io/s/react-hook-form-usefieldarray-ssugn)
|
|
53
|
+
*
|
|
54
|
+
* @param props - useFieldArray props
|
|
55
|
+
*
|
|
56
|
+
* @returns methods - functions to manipulate with the Field Arrays (dynamic inputs) {@link UseFieldArrayReturn}
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```tsx
|
|
60
|
+
* function App() {
|
|
61
|
+
* const { register, control, handleSubmit, reset, trigger, setError } = useForm({
|
|
62
|
+
* defaultValues: {
|
|
63
|
+
* test: []
|
|
64
|
+
* }
|
|
65
|
+
* });
|
|
66
|
+
* const { fields, append } = useFieldArray({
|
|
67
|
+
* control,
|
|
68
|
+
* name: "test"
|
|
69
|
+
* });
|
|
70
|
+
*
|
|
71
|
+
* return (
|
|
72
|
+
* <form onSubmit={handleSubmit(data => console.log(data))}>
|
|
73
|
+
* {fields.map((item, index) => (
|
|
74
|
+
* <input key={item.id} {...register(`test.${index}.firstName`)} />
|
|
75
|
+
* ))}
|
|
76
|
+
* <button type="button" onClick={() => append({ firstName: "bill" })}>
|
|
77
|
+
* append
|
|
78
|
+
* </button>
|
|
79
|
+
* <input type="submit" />
|
|
80
|
+
* </form>
|
|
81
|
+
* );
|
|
82
|
+
* }
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export function useFieldArray<
|
|
86
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
87
|
+
TFieldArrayName extends FieldArrayPath<TFieldValues> = FieldArrayPath<TFieldValues>,
|
|
88
|
+
TKeyName extends string = 'id',
|
|
89
|
+
TTransformedValues = TFieldValues,
|
|
90
|
+
>(
|
|
91
|
+
props: UseFieldArrayProps<TFieldValues, TFieldArrayName, TKeyName, TTransformedValues>,
|
|
92
|
+
): UseFieldArrayReturn<TFieldValues, TFieldArrayName, TKeyName> {
|
|
93
|
+
const formControl = useFormControlContext<TFieldValues, any, TTransformedValues>();
|
|
94
|
+
const { control = formControl, name, keyName = 'id', disabled, shouldUnregister, rules } = props;
|
|
95
|
+
const [fields, setFields] = useState(control._getFieldArray(name));
|
|
96
|
+
const ids = useRef<string[]>(control._getFieldArray(name).map(generateId));
|
|
97
|
+
|
|
98
|
+
const _actioned = useRef(false);
|
|
99
|
+
|
|
100
|
+
if (!disabled) {
|
|
101
|
+
control._names.array.add(name);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
useMemo(
|
|
105
|
+
() =>
|
|
106
|
+
!disabled &&
|
|
107
|
+
rules &&
|
|
108
|
+
fields.length >= 0 &&
|
|
109
|
+
(control as Control<TFieldValues, any, TTransformedValues>).register(
|
|
110
|
+
name as FieldPath<TFieldValues>,
|
|
111
|
+
rules as RegisterOptions<TFieldValues>,
|
|
112
|
+
),
|
|
113
|
+
[control, name, fields.length, rules, disabled],
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
useIsomorphicLayoutEffect(() => {
|
|
117
|
+
if (disabled) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return control._subjects.array.subscribe({
|
|
122
|
+
next: ({
|
|
123
|
+
values,
|
|
124
|
+
name: fieldArrayName,
|
|
125
|
+
}: {
|
|
126
|
+
values?: FieldValues;
|
|
127
|
+
name?: InternalFieldName;
|
|
128
|
+
}) => {
|
|
129
|
+
if (fieldArrayName === name || !fieldArrayName) {
|
|
130
|
+
const fieldValues = get(values, name);
|
|
131
|
+
if (Array.isArray(fieldValues)) {
|
|
132
|
+
setFields(fieldValues);
|
|
133
|
+
ids.current = fieldValues.map(generateId);
|
|
134
|
+
} else if (!fieldArrayName) {
|
|
135
|
+
setFields([]);
|
|
136
|
+
ids.current = [];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
}).unsubscribe;
|
|
141
|
+
}, [control, name, disabled]);
|
|
142
|
+
|
|
143
|
+
const updateValues = useCallback(
|
|
144
|
+
<T extends Partial<FieldArrayWithId<TFieldValues, TFieldArrayName, TKeyName>>[]>(
|
|
145
|
+
updatedFieldArrayValues: T,
|
|
146
|
+
) => {
|
|
147
|
+
_actioned.current = true;
|
|
148
|
+
control._setFieldArray(name, updatedFieldArrayValues);
|
|
149
|
+
},
|
|
150
|
+
[control, name],
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const append = (
|
|
154
|
+
value:
|
|
155
|
+
| Partial<FieldArray<TFieldValues, TFieldArrayName>>
|
|
156
|
+
| Partial<FieldArray<TFieldValues, TFieldArrayName>>[],
|
|
157
|
+
options?: FieldArrayMethodProps,
|
|
158
|
+
) => {
|
|
159
|
+
if (disabled) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const appendValue = convertToArrayPayload(cloneObject(value));
|
|
164
|
+
const updatedFieldArrayValues = appendAt(control._getFieldArray(name), appendValue);
|
|
165
|
+
control._names.focus = getFocusFieldName(name, updatedFieldArrayValues.length - 1, options);
|
|
166
|
+
ids.current = appendAt(ids.current, appendValue.map(generateId));
|
|
167
|
+
updateValues(updatedFieldArrayValues);
|
|
168
|
+
setFields(updatedFieldArrayValues);
|
|
169
|
+
control._setFieldArray(name, updatedFieldArrayValues, appendAt, {
|
|
170
|
+
argA: fillEmptyArray(value),
|
|
171
|
+
});
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const prepend = (
|
|
175
|
+
value:
|
|
176
|
+
| Partial<FieldArray<TFieldValues, TFieldArrayName>>
|
|
177
|
+
| Partial<FieldArray<TFieldValues, TFieldArrayName>>[],
|
|
178
|
+
options?: FieldArrayMethodProps,
|
|
179
|
+
) => {
|
|
180
|
+
if (disabled) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const prependValue = convertToArrayPayload(cloneObject(value));
|
|
185
|
+
const updatedFieldArrayValues = prependAt(control._getFieldArray(name), prependValue);
|
|
186
|
+
control._names.focus = getFocusFieldName(name, 0, options);
|
|
187
|
+
ids.current = prependAt(ids.current, prependValue.map(generateId));
|
|
188
|
+
updateValues(updatedFieldArrayValues);
|
|
189
|
+
setFields(updatedFieldArrayValues);
|
|
190
|
+
control._setFieldArray(name, updatedFieldArrayValues, prependAt, {
|
|
191
|
+
argA: fillEmptyArray(value),
|
|
192
|
+
});
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const remove = (index?: number | number[]) => {
|
|
196
|
+
if (disabled) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const updatedFieldArrayValues: Partial<
|
|
201
|
+
FieldArrayWithId<TFieldValues, TFieldArrayName, TKeyName>
|
|
202
|
+
>[] = removeArrayAt(control._getFieldArray(name), index);
|
|
203
|
+
ids.current = removeArrayAt(ids.current, index);
|
|
204
|
+
updateValues(updatedFieldArrayValues);
|
|
205
|
+
setFields(updatedFieldArrayValues);
|
|
206
|
+
!Array.isArray(get(control._fields, name)) && set(control._fields, name, undefined);
|
|
207
|
+
control._setFieldArray(name, updatedFieldArrayValues, removeArrayAt, {
|
|
208
|
+
argA: index,
|
|
209
|
+
});
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const insert = (
|
|
213
|
+
index: number,
|
|
214
|
+
value:
|
|
215
|
+
| Partial<FieldArray<TFieldValues, TFieldArrayName>>
|
|
216
|
+
| Partial<FieldArray<TFieldValues, TFieldArrayName>>[],
|
|
217
|
+
options?: FieldArrayMethodProps,
|
|
218
|
+
) => {
|
|
219
|
+
if (disabled) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const insertValue = convertToArrayPayload(cloneObject(value));
|
|
224
|
+
const updatedFieldArrayValues = insertAt(control._getFieldArray(name), index, insertValue);
|
|
225
|
+
control._names.focus = getFocusFieldName(name, index, options);
|
|
226
|
+
ids.current = insertAt(ids.current, index, insertValue.map(generateId));
|
|
227
|
+
updateValues(updatedFieldArrayValues);
|
|
228
|
+
setFields(updatedFieldArrayValues);
|
|
229
|
+
control._setFieldArray(name, updatedFieldArrayValues, insertAt, {
|
|
230
|
+
argA: index,
|
|
231
|
+
argB: fillEmptyArray(value),
|
|
232
|
+
});
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const swap = (indexA: number, indexB: number) => {
|
|
236
|
+
if (disabled) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const updatedFieldArrayValues = control._getFieldArray(name);
|
|
241
|
+
swapArrayAt(updatedFieldArrayValues, indexA, indexB);
|
|
242
|
+
swapArrayAt(ids.current, indexA, indexB);
|
|
243
|
+
updateValues(updatedFieldArrayValues);
|
|
244
|
+
setFields(updatedFieldArrayValues);
|
|
245
|
+
control._setFieldArray(
|
|
246
|
+
name,
|
|
247
|
+
updatedFieldArrayValues,
|
|
248
|
+
swapArrayAt,
|
|
249
|
+
{
|
|
250
|
+
argA: indexA,
|
|
251
|
+
argB: indexB,
|
|
252
|
+
},
|
|
253
|
+
false,
|
|
254
|
+
);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const move = (from: number, to: number) => {
|
|
258
|
+
if (disabled) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const updatedFieldArrayValues = control._getFieldArray(name);
|
|
263
|
+
moveArrayAt(updatedFieldArrayValues, from, to);
|
|
264
|
+
moveArrayAt(ids.current, from, to);
|
|
265
|
+
updateValues(updatedFieldArrayValues);
|
|
266
|
+
setFields(updatedFieldArrayValues);
|
|
267
|
+
control._setFieldArray(
|
|
268
|
+
name,
|
|
269
|
+
updatedFieldArrayValues,
|
|
270
|
+
moveArrayAt,
|
|
271
|
+
{
|
|
272
|
+
argA: from,
|
|
273
|
+
argB: to,
|
|
274
|
+
},
|
|
275
|
+
false,
|
|
276
|
+
);
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const update = (index: number, value: FieldArray<TFieldValues, TFieldArrayName>) => {
|
|
280
|
+
if (disabled) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const updateValue = cloneObject(value);
|
|
285
|
+
const updatedFieldArrayValues = updateAt(
|
|
286
|
+
control._getFieldArray<FieldArrayWithId<TFieldValues, TFieldArrayName, TKeyName>>(name),
|
|
287
|
+
index,
|
|
288
|
+
updateValue as FieldArrayWithId<TFieldValues, TFieldArrayName, TKeyName>,
|
|
289
|
+
);
|
|
290
|
+
ids.current = [...updatedFieldArrayValues].map((item, i) =>
|
|
291
|
+
!item || i === index ? generateId() : ids.current[i],
|
|
292
|
+
);
|
|
293
|
+
updateValues(updatedFieldArrayValues);
|
|
294
|
+
setFields([...updatedFieldArrayValues]);
|
|
295
|
+
control._setFieldArray(
|
|
296
|
+
name,
|
|
297
|
+
updatedFieldArrayValues,
|
|
298
|
+
updateAt,
|
|
299
|
+
{
|
|
300
|
+
argA: index,
|
|
301
|
+
argB: updateValue,
|
|
302
|
+
},
|
|
303
|
+
true,
|
|
304
|
+
false,
|
|
305
|
+
);
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const replace = (
|
|
309
|
+
value:
|
|
310
|
+
| Partial<FieldArray<TFieldValues, TFieldArrayName>>
|
|
311
|
+
| Partial<FieldArray<TFieldValues, TFieldArrayName>>[],
|
|
312
|
+
) => {
|
|
313
|
+
if (disabled) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const updatedFieldArrayValues = convertToArrayPayload(cloneObject(value));
|
|
318
|
+
ids.current = updatedFieldArrayValues.map(generateId);
|
|
319
|
+
updateValues([...updatedFieldArrayValues]);
|
|
320
|
+
setFields([...updatedFieldArrayValues]);
|
|
321
|
+
control._setFieldArray(
|
|
322
|
+
name,
|
|
323
|
+
[...updatedFieldArrayValues],
|
|
324
|
+
<T>(data: T): T => data,
|
|
325
|
+
{},
|
|
326
|
+
true,
|
|
327
|
+
false,
|
|
328
|
+
);
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
useEffect(() => {
|
|
332
|
+
if (disabled) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
control._state.action = false;
|
|
337
|
+
|
|
338
|
+
isWatched(name, control._names) &&
|
|
339
|
+
control._subjects.state.next({
|
|
340
|
+
...control._formState,
|
|
341
|
+
} as FormState<TFieldValues>);
|
|
342
|
+
|
|
343
|
+
const validationModes = getValidationModes(control._options.mode);
|
|
344
|
+
|
|
345
|
+
if (
|
|
346
|
+
_actioned.current &&
|
|
347
|
+
(!validationModes.isOnSubmit || control._formState.isSubmitted) &&
|
|
348
|
+
!getValidationModes(control._options.reValidateMode).isOnSubmit &&
|
|
349
|
+
!validationModes.isOnBlur
|
|
350
|
+
) {
|
|
351
|
+
if (control._options.resolver) {
|
|
352
|
+
control._runSchema([name]).then((result) => {
|
|
353
|
+
control._updateIsValidating([name]);
|
|
354
|
+
const error = get(result.errors, name);
|
|
355
|
+
const existingError = get(control._formState.errors, name);
|
|
356
|
+
const existingErrorType =
|
|
357
|
+
existingError && (existingError.type || existingError.root?.type);
|
|
358
|
+
const existingErrorMessage =
|
|
359
|
+
existingError && (existingError.message || existingError.root?.message);
|
|
360
|
+
|
|
361
|
+
if (
|
|
362
|
+
existingError
|
|
363
|
+
? (!error && existingErrorType) ||
|
|
364
|
+
(error &&
|
|
365
|
+
(existingErrorType !== error.type || existingErrorMessage !== error.message))
|
|
366
|
+
: error && error.type
|
|
367
|
+
) {
|
|
368
|
+
if (error) {
|
|
369
|
+
isObject(error) && !Object.keys(error).some((key) => !Number.isNaN(+key))
|
|
370
|
+
? updateFieldArrayRootError(
|
|
371
|
+
control._formState.errors as FieldErrors<TFieldValues>,
|
|
372
|
+
{ [name]: error } as Partial<Record<string, FieldError>>,
|
|
373
|
+
name,
|
|
374
|
+
)
|
|
375
|
+
: set(control._formState.errors, name, error);
|
|
376
|
+
} else {
|
|
377
|
+
unset(control._formState.errors, name);
|
|
378
|
+
}
|
|
379
|
+
control._subjects.state.next({
|
|
380
|
+
errors: control._formState.errors as FieldErrors<TFieldValues>,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
} else {
|
|
385
|
+
const field: Field = get(control._fields, name);
|
|
386
|
+
if (
|
|
387
|
+
field &&
|
|
388
|
+
field._f &&
|
|
389
|
+
!(
|
|
390
|
+
getValidationModes(control._options.reValidateMode).isOnSubmit &&
|
|
391
|
+
getValidationModes(control._options.mode).isOnSubmit
|
|
392
|
+
)
|
|
393
|
+
) {
|
|
394
|
+
validateField(
|
|
395
|
+
field,
|
|
396
|
+
control._names.disabled,
|
|
397
|
+
control._formValues,
|
|
398
|
+
control._options.criteriaMode === VALIDATION_MODE.all,
|
|
399
|
+
control._options.shouldUseNativeValidation,
|
|
400
|
+
true,
|
|
401
|
+
).then(
|
|
402
|
+
(error) =>
|
|
403
|
+
!isEmptyObject(error) &&
|
|
404
|
+
control._subjects.state.next({
|
|
405
|
+
errors: updateFieldArrayRootError(
|
|
406
|
+
control._formState.errors as FieldErrors<TFieldValues>,
|
|
407
|
+
error,
|
|
408
|
+
name,
|
|
409
|
+
) as FieldErrors<TFieldValues>,
|
|
410
|
+
}),
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// External updates that change `fields` (e.g. reset() or setValue() on
|
|
417
|
+
// the array) already notify subscribers with the up-to-date values
|
|
418
|
+
// themselves, so only re-broadcast here for genuine array method calls.
|
|
419
|
+
_actioned.current &&
|
|
420
|
+
control._subjects.state.next({
|
|
421
|
+
name,
|
|
422
|
+
values: cloneObject(control._formValues) as TFieldValues,
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
control._names.focus &&
|
|
426
|
+
iterateFieldsByAction(control._fields, (ref, key: string) => {
|
|
427
|
+
if (control._names.focus && key.startsWith(control._names.focus) && ref.focus) {
|
|
428
|
+
ref.focus();
|
|
429
|
+
return 1;
|
|
430
|
+
}
|
|
431
|
+
return;
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
control._names.focus = '';
|
|
435
|
+
|
|
436
|
+
control._setValid();
|
|
437
|
+
_actioned.current = false;
|
|
438
|
+
}, [fields, name, control, disabled]);
|
|
439
|
+
|
|
440
|
+
useEffect(() => {
|
|
441
|
+
if (!disabled) {
|
|
442
|
+
!get(control._formValues, name) && control._setFieldArray(name);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return () => {
|
|
446
|
+
if (disabled) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const shouldKeepFieldArrayValues = !(control._options.shouldUnregister || shouldUnregister);
|
|
451
|
+
const updateMounted = (name: InternalFieldName, value: boolean) => {
|
|
452
|
+
const field: Field = get(control._fields, name);
|
|
453
|
+
if (field && field._f) {
|
|
454
|
+
field._f.mount = value;
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
if (_actioned.current && shouldKeepFieldArrayValues) {
|
|
459
|
+
control._subjects.state.next({
|
|
460
|
+
name,
|
|
461
|
+
values: cloneObject(control._formValues) as TFieldValues,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
shouldKeepFieldArrayValues
|
|
466
|
+
? updateMounted(name, false)
|
|
467
|
+
: control.unregister(name as FieldPath<TFieldValues>);
|
|
468
|
+
};
|
|
469
|
+
}, [name, control, keyName, shouldUnregister, disabled]);
|
|
470
|
+
|
|
471
|
+
return {
|
|
472
|
+
swap: useCallback(swap, [updateValues, name, control, disabled]),
|
|
473
|
+
move: useCallback(move, [updateValues, name, control, disabled]),
|
|
474
|
+
prepend: useCallback(prepend, [updateValues, name, control, disabled]),
|
|
475
|
+
append: useCallback(append, [updateValues, name, control, disabled]),
|
|
476
|
+
remove: useCallback(remove, [updateValues, name, control, disabled]),
|
|
477
|
+
insert: useCallback(insert, [updateValues, name, control, disabled]),
|
|
478
|
+
update: useCallback(update, [updateValues, name, control, disabled]),
|
|
479
|
+
replace: useCallback(replace, [updateValues, name, control, disabled]),
|
|
480
|
+
fields: useMemo(
|
|
481
|
+
() =>
|
|
482
|
+
fields.map((field, index) => ({
|
|
483
|
+
...field,
|
|
484
|
+
...(isBoolean(disabled) ? { disabled } : {}),
|
|
485
|
+
[keyName]: ids.current[index] || generateId(),
|
|
486
|
+
})) as FieldArrayWithId<TFieldValues, TFieldArrayName, TKeyName>[],
|
|
487
|
+
[fields, keyName, disabled],
|
|
488
|
+
),
|
|
489
|
+
};
|
|
490
|
+
}
|
package/src/useForm.ts
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// Vendored from react-hook-form@7.81.0 src/useForm.ts (octane port).
|
|
2
|
+
import { useState, useRef, useEffect, useMemo } from 'octane';
|
|
3
|
+
|
|
4
|
+
import { DEFAULT_FORM_STATE } from './logic/createFormControl';
|
|
5
|
+
import getProxyFormState from './logic/getProxyFormState';
|
|
6
|
+
import cloneObject from './utils/cloneObject';
|
|
7
|
+
import deepEqual from './utils/deepEqual';
|
|
8
|
+
import isFunction from './utils/isFunction';
|
|
9
|
+
import { createFormControl } from './logic';
|
|
10
|
+
import type { FieldValues, FormState, UseFormProps, UseFormReturn } from './types';
|
|
11
|
+
import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Custom hook to manage the entire form.
|
|
15
|
+
*
|
|
16
|
+
* @remarks
|
|
17
|
+
* [API](https://react-hook-form.com/docs/useform) • [Demo](https://codesandbox.io/s/react-hook-form-get-started-ts-5ksmm) • [Video](https://www.youtube.com/watch?v=RkXv4AXXC_4)
|
|
18
|
+
*
|
|
19
|
+
* @param props - form configuration and validation parameters.
|
|
20
|
+
*
|
|
21
|
+
* @returns methods - individual functions to manage the form state. {@link UseFormReturn}
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```tsx
|
|
25
|
+
* function App() {
|
|
26
|
+
* const { register, handleSubmit, watch, formState: { errors } } = useForm();
|
|
27
|
+
* const onSubmit = data => console.log(data);
|
|
28
|
+
*
|
|
29
|
+
* console.log(watch("example"));
|
|
30
|
+
*
|
|
31
|
+
* return (
|
|
32
|
+
* <form onSubmit={handleSubmit(onSubmit)}>
|
|
33
|
+
* <input defaultValue="test" {...register("example")} />
|
|
34
|
+
* <input {...register("exampleRequired", { required: true })} />
|
|
35
|
+
* {errors.exampleRequired && <span>This field is required</span>}
|
|
36
|
+
* <button>Submit</button>
|
|
37
|
+
* </form>
|
|
38
|
+
* );
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function useForm<
|
|
43
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
44
|
+
TContext = any,
|
|
45
|
+
TTransformedValues = TFieldValues,
|
|
46
|
+
>(
|
|
47
|
+
props: UseFormProps<TFieldValues, TContext, TTransformedValues> = {},
|
|
48
|
+
): UseFormReturn<TFieldValues, TContext, TTransformedValues> {
|
|
49
|
+
const _formControl = useRef<
|
|
50
|
+
UseFormReturn<TFieldValues, TContext, TTransformedValues> | undefined
|
|
51
|
+
>(undefined);
|
|
52
|
+
const _values = useRef<typeof props.values>(undefined);
|
|
53
|
+
const _formControlProp = useRef(props.formControl);
|
|
54
|
+
const [formState, updateFormState] = useState<FormState<TFieldValues>>(() => ({
|
|
55
|
+
...cloneObject(DEFAULT_FORM_STATE),
|
|
56
|
+
isLoading: isFunction(props.defaultValues),
|
|
57
|
+
errors: props.errors || {},
|
|
58
|
+
disabled: props.disabled || false,
|
|
59
|
+
defaultValues: isFunction(props.defaultValues) ? undefined : props.defaultValues,
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
if (
|
|
63
|
+
!_formControl.current ||
|
|
64
|
+
(props.formControl && _formControlProp.current !== props.formControl)
|
|
65
|
+
) {
|
|
66
|
+
_formControlProp.current = props.formControl;
|
|
67
|
+
if (props.formControl) {
|
|
68
|
+
_formControl.current = {
|
|
69
|
+
...props.formControl,
|
|
70
|
+
formState,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
if (props.defaultValues && !isFunction(props.defaultValues)) {
|
|
74
|
+
props.formControl.reset(props.defaultValues, props.resetOptions);
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
const { formControl, ...rest } = createFormControl(props);
|
|
78
|
+
|
|
79
|
+
_formControl.current = {
|
|
80
|
+
...rest,
|
|
81
|
+
formState,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const control = _formControl.current.control;
|
|
87
|
+
control._options = props;
|
|
88
|
+
|
|
89
|
+
useIsomorphicLayoutEffect(() => {
|
|
90
|
+
const sub = control._subscribe({
|
|
91
|
+
formState: control._proxyFormState,
|
|
92
|
+
callback: () =>
|
|
93
|
+
updateFormState({
|
|
94
|
+
...control._formState,
|
|
95
|
+
defaultValues: control._defaultValues as FormState<TFieldValues>['defaultValues'],
|
|
96
|
+
}),
|
|
97
|
+
reRenderRoot: true,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
updateFormState((data) => ({
|
|
101
|
+
...data,
|
|
102
|
+
isReady: true,
|
|
103
|
+
}));
|
|
104
|
+
|
|
105
|
+
control._formState.isReady = true;
|
|
106
|
+
|
|
107
|
+
return sub;
|
|
108
|
+
}, [control]);
|
|
109
|
+
|
|
110
|
+
useEffect(() => control._disableForm(props.disabled), [control, props.disabled]);
|
|
111
|
+
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
if (props.mode) {
|
|
114
|
+
control._options.mode = props.mode;
|
|
115
|
+
}
|
|
116
|
+
if (props.reValidateMode) {
|
|
117
|
+
control._options.reValidateMode = props.reValidateMode;
|
|
118
|
+
}
|
|
119
|
+
}, [control, props.mode, props.reValidateMode]);
|
|
120
|
+
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
if (props.errors) {
|
|
123
|
+
control._setErrors(props.errors);
|
|
124
|
+
control._focusError();
|
|
125
|
+
}
|
|
126
|
+
}, [control, props.errors]);
|
|
127
|
+
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
props.shouldUnregister &&
|
|
130
|
+
control._subjects.state.next({
|
|
131
|
+
values: control._getWatch(),
|
|
132
|
+
});
|
|
133
|
+
}, [control, props.shouldUnregister]);
|
|
134
|
+
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
if (control._proxyFormState.isDirty) {
|
|
137
|
+
const isDirty = control._getDirty();
|
|
138
|
+
if (isDirty !== formState.isDirty) {
|
|
139
|
+
control._subjects.state.next({
|
|
140
|
+
isDirty,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}, [control, formState.isDirty]);
|
|
145
|
+
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
if (props.values && !deepEqual(props.values, _values.current)) {
|
|
148
|
+
control._reset(props.values, {
|
|
149
|
+
keepFieldsRef: true,
|
|
150
|
+
...control._options.resetOptions,
|
|
151
|
+
});
|
|
152
|
+
if (!control._options.resetOptions?.keepIsValid) {
|
|
153
|
+
control._setValid();
|
|
154
|
+
}
|
|
155
|
+
_values.current = props.values;
|
|
156
|
+
updateFormState((state) => ({ ...state }));
|
|
157
|
+
} else {
|
|
158
|
+
control._resetDefaultValues();
|
|
159
|
+
}
|
|
160
|
+
}, [control, props.values]);
|
|
161
|
+
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
if (!control._state.mount) {
|
|
164
|
+
control._setValid();
|
|
165
|
+
control._state.mount = true;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (control._state.watch) {
|
|
169
|
+
control._state.watch = false;
|
|
170
|
+
control._subjects.state.next({ ...control._formState });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
control._removeUnmounted();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
_formControl.current.formState = useMemo(
|
|
177
|
+
() => getProxyFormState(formState, control),
|
|
178
|
+
[control, formState],
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
return _formControl.current;
|
|
182
|
+
}
|