react-hook-form 8.0.0-alpha.0 → 8.0.0-alpha.3

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.
Files changed (47) hide show
  1. package/README.md +387 -317
  2. package/dist/controller.d.ts +42 -0
  3. package/dist/controller.d.ts.map +1 -1
  4. package/dist/index.cjs.js +1 -1
  5. package/dist/index.cjs.js.map +1 -1
  6. package/dist/index.esm.mjs +694 -333
  7. package/dist/index.esm.mjs.map +1 -1
  8. package/dist/index.umd.js +1 -1
  9. package/dist/index.umd.js.map +1 -1
  10. package/dist/logic/createFormControl.d.ts +1 -1
  11. package/dist/logic/createFormControl.d.ts.map +1 -1
  12. package/dist/logic/focusFieldBy.d.ts.map +1 -1
  13. package/dist/logic/getResolverOptions.d.ts +1 -1
  14. package/dist/logic/shouldRenderFormState.d.ts.map +1 -1
  15. package/dist/logic/updateFieldArrayRootError.d.ts +4 -0
  16. package/dist/logic/updateFieldArrayRootError.d.ts.map +1 -0
  17. package/dist/logic/validateField.d.ts +1 -1
  18. package/dist/logic/validateField.d.ts.map +1 -1
  19. package/dist/types/controller.d.ts +24 -3
  20. package/dist/types/controller.d.ts.map +1 -1
  21. package/dist/types/errors.d.ts +9 -2
  22. package/dist/types/errors.d.ts.map +1 -1
  23. package/dist/types/fieldArray.d.ts +173 -8
  24. package/dist/types/fieldArray.d.ts.map +1 -1
  25. package/dist/types/form.d.ts +395 -29
  26. package/dist/types/form.d.ts.map +1 -1
  27. package/dist/types/resolvers.d.ts +3 -3
  28. package/dist/types/resolvers.d.ts.map +1 -1
  29. package/dist/types/utils.d.ts +4 -5
  30. package/dist/types/utils.d.ts.map +1 -1
  31. package/dist/types/validator.d.ts +1 -1
  32. package/dist/types/validator.d.ts.map +1 -1
  33. package/dist/useController.d.ts +24 -0
  34. package/dist/useController.d.ts.map +1 -1
  35. package/dist/useFieldArray.d.ts +39 -2
  36. package/dist/useFieldArray.d.ts.map +1 -1
  37. package/dist/useForm.d.ts +30 -1
  38. package/dist/useForm.d.ts.map +1 -1
  39. package/dist/useFormContext.d.ts +62 -2
  40. package/dist/useFormContext.d.ts.map +1 -1
  41. package/dist/useFormState.d.ts +30 -0
  42. package/dist/useFormState.d.ts.map +1 -1
  43. package/dist/useWatch.d.ts +68 -4
  44. package/dist/useWatch.d.ts.map +1 -1
  45. package/dist/utils/compact.d.ts.map +1 -1
  46. package/dist/utils/unset.d.ts.map +1 -1
  47. package/package.json +21 -21
@@ -22,7 +22,7 @@ var getNodeParentName = (name) => name.substring(0, name.search(/.\d/)) || name;
22
22
 
23
23
  var isNameInFieldArray = (names, name) => [...names].some((current) => getNodeParentName(name) === current);
24
24
 
25
- var compact = (value) => value.filter(Boolean);
25
+ var compact = (value) => Array.isArray(value) ? value.filter(Boolean) : [];
26
26
 
27
27
  var isUndefined = (val) => val === undefined;
28
28
 
@@ -60,15 +60,72 @@ const INPUT_VALIDATION_RULES = {
60
60
  validate: 'validate',
61
61
  };
62
62
 
63
- var omit = (source, key) => {
64
- const copy = Object.assign({}, source);
65
- delete copy[key];
66
- return copy;
67
- };
68
-
69
63
  const HookFormContext = React.createContext(null);
64
+ /**
65
+ * This custom hook allows you to access the form context. useFormContext is intended to be used in deeply nested structures, where it would become inconvenient to pass the context as a prop. To be used with {@link FormProvider}.
66
+ *
67
+ * @remarks
68
+ * [API](https://react-hook-form.com/api/useformcontext) • [Demo](https://codesandbox.io/s/react-hook-form-v7-form-context-ytudi)
69
+ *
70
+ * @returns return all useForm methods
71
+ *
72
+ * @example
73
+ * ```tsx
74
+ * function App() {
75
+ * const methods = useForm();
76
+ * const onSubmit = data => console.log(data);
77
+ *
78
+ * return (
79
+ * <FormProvider {...methods} >
80
+ * <form onSubmit={methods.handleSubmit(onSubmit)}>
81
+ * <NestedInput />
82
+ * <input type="submit" />
83
+ * </form>
84
+ * </FormProvider>
85
+ * );
86
+ * }
87
+ *
88
+ * function NestedInput() {
89
+ * const { register } = useFormContext(); // retrieve all hook methods
90
+ * return <input {...register("test")} />;
91
+ * }
92
+ * ```
93
+ */
70
94
  const useFormContext = () => React.useContext(HookFormContext);
71
- const FormProvider = (props) => (React.createElement(HookFormContext.Provider, { value: omit(props, 'children') }, props.children));
95
+ /**
96
+ * A provider component that propagates the `useForm` methods to all children components via [React Context](https://reactjs.org/docs/context.html) API. To be used with {@link useFormContext}.
97
+ *
98
+ * @remarks
99
+ * [API](https://react-hook-form.com/api/useformcontext) • [Demo](https://codesandbox.io/s/react-hook-form-v7-form-context-ytudi)
100
+ *
101
+ * @param props - all useFrom methods
102
+ *
103
+ * @example
104
+ * ```tsx
105
+ * function App() {
106
+ * const methods = useForm();
107
+ * const onSubmit = data => console.log(data);
108
+ *
109
+ * return (
110
+ * <FormProvider {...methods} >
111
+ * <form onSubmit={methods.handleSubmit(onSubmit)}>
112
+ * <NestedInput />
113
+ * <input type="submit" />
114
+ * </form>
115
+ * </FormProvider>
116
+ * );
117
+ * }
118
+ *
119
+ * function NestedInput() {
120
+ * const { register } = useFormContext(); // retrieve all hook methods
121
+ * return <input {...register("test")} />;
122
+ * }
123
+ * ```
124
+ */
125
+ const FormProvider = (props) => {
126
+ const { children, ...data } = props;
127
+ return (React.createElement(HookFormContext.Provider, { value: data }, props.children));
128
+ };
72
129
 
73
130
  var getProxyFormState = (formState, _proxyFormState, localProxyFormState, isRoot = true) => {
74
131
  const result = {};
@@ -90,7 +147,7 @@ var getProxyFormState = (formState, _proxyFormState, localProxyFormState, isRoot
90
147
  var isEmptyObject = (value) => isObject(value) && !Object.keys(value).length;
91
148
 
92
149
  var shouldRenderFormState = (formStateData, _proxyFormState, isRoot) => {
93
- const formState = omit(formStateData, 'name');
150
+ const { name, ...formState } = formStateData;
94
151
  return (isEmptyObject(formState) ||
95
152
  Object.keys(formState).length >= Object.keys(_proxyFormState).length ||
96
153
  Object.keys(formState).find((key) => _proxyFormState[key] ===
@@ -125,6 +182,36 @@ function useSubscribe(props) {
125
182
  }, [props.disabled]);
126
183
  }
127
184
 
185
+ /**
186
+ * This custom hook allows you to subscribe to each form state, and isolate the re-render at the custom hook level. It has its scope in terms of form state subscription, so it would not affect other useFormState and useForm. Using this hook can reduce the re-render impact on large and complex form application.
187
+ *
188
+ * @remarks
189
+ * [API](https://react-hook-form.com/api/useformstate) • [Demo](https://codesandbox.io/s/useformstate-75xly)
190
+ *
191
+ * @param props - include options on specify fields to subscribe. {@link UseFormStateReturn}
192
+ *
193
+ * @example
194
+ * ```tsx
195
+ * function App() {
196
+ * const { register, handleSubmit, control } = useForm({
197
+ * defaultValues: {
198
+ * firstName: "firstName"
199
+ * }});
200
+ * const { dirtyFields } = useFormState({
201
+ * control
202
+ * });
203
+ * const onSubmit = (data) => console.log(data);
204
+ *
205
+ * return (
206
+ * <form onSubmit={handleSubmit(onSubmit)}>
207
+ * <input {...register("firstName")} placeholder="First Name" />
208
+ * {dirtyFields.firstName && <p>Field is dirty.</p>}
209
+ * <input type="submit" />
210
+ * </form>
211
+ * );
212
+ * }
213
+ * ```
214
+ */
128
215
  function useFormState(props) {
129
216
  const methods = useFormContext();
130
217
  const { control = methods.control, disabled, name, exact } = props || {};
@@ -143,14 +230,20 @@ function useFormState(props) {
143
230
  const callback = React.useCallback((value) => _mounted.current &&
144
231
  shouldSubscribeByName(_name.current, value.name, exact) &&
145
232
  shouldRenderFormState(value, _localProxyFormState.current) &&
146
- updateFormState(Object.assign(Object.assign({}, control._formState), value)), [control, exact]);
233
+ updateFormState({
234
+ ...control._formState,
235
+ ...value,
236
+ }), [control, exact]);
147
237
  useSubscribe({
148
238
  disabled,
149
239
  callback,
150
240
  subject: control._subjects.state,
151
241
  });
152
- React.useEffect(() => () => {
153
- _mounted.current = false;
242
+ React.useEffect(() => {
243
+ _mounted.current = true;
244
+ return () => {
245
+ _mounted.current = false;
246
+ };
154
247
  }, []);
155
248
  return getProxyFormState(formState, control._proxyFormState, _localProxyFormState.current, false);
156
249
  }
@@ -182,6 +275,19 @@ var objectHasFunction = (data) => {
182
275
  return false;
183
276
  };
184
277
 
278
+ /**
279
+ * Custom hook to subscribe to field change and isolate re-rendering at the component level.
280
+ *
281
+ * @remarks
282
+ *
283
+ * [API](https://react-hook-form.com/api/usewatch) • [Demo](https://codesandbox.io/s/react-hook-form-v7-ts-usewatch-h9i5e)
284
+ *
285
+ * @example
286
+ * ```tsx
287
+ * // can skip passing down the control into useWatch if the form is wrapped with the FormProvider
288
+ * const values = useWatch()
289
+ * ```
290
+ */
185
291
  function useWatch(props) {
186
292
  const methods = useFormContext();
187
293
  const { control = methods.control, name, defaultValue, disabled, exact, } = props || {};
@@ -192,11 +298,12 @@ function useWatch(props) {
192
298
  const fieldValues = generateWatchOutput(_name.current, control._names, formState.values || control._formValues);
193
299
  updateValue(isUndefined(_name.current) ||
194
300
  (isObject(fieldValues) && !objectHasFunction(fieldValues))
195
- ? Object.assign({}, fieldValues) : Array.isArray(fieldValues)
196
- ? [...fieldValues]
197
- : isUndefined(fieldValues)
198
- ? defaultValue
199
- : fieldValues);
301
+ ? { ...fieldValues }
302
+ : Array.isArray(fieldValues)
303
+ ? [...fieldValues]
304
+ : isUndefined(fieldValues)
305
+ ? defaultValue
306
+ : fieldValues);
200
307
  }
201
308
  }, [control, exact, defaultValue]);
202
309
  useSubscribe({
@@ -213,6 +320,30 @@ function useWatch(props) {
213
320
  return value;
214
321
  }
215
322
 
323
+ /**
324
+ * Custom hook to work with controlled component, this function provide you with both form and field level state. Re-render is isolated at the hook level.
325
+ *
326
+ * @remarks
327
+ * [API](https://react-hook-form.com/api/usecontroller) • [Demo](https://codesandbox.io/s/usecontroller-0o8px)
328
+ *
329
+ * @param props - the path name to the form field value, and validation rules.
330
+ *
331
+ * @returns field properties, field and form state. {@link UseControllerReturn}
332
+ *
333
+ * @example
334
+ * ```tsx
335
+ * function Input(props) {
336
+ * const { field, fieldState, formState } = useController(props);
337
+ * return (
338
+ * <div>
339
+ * <input {...field} placeholder={props.name} />
340
+ * <p>{fieldState.isTouched && "Touched"}</p>
341
+ * <p>{formState.isSubmitted ? "submitted" : ""}</p>
342
+ * </div>
343
+ * );
344
+ * }
345
+ * ```
346
+ */
216
347
  function useController(props) {
217
348
  const methods = useFormContext();
218
349
  const { name, control = methods.control, shouldUnregister } = props;
@@ -221,13 +352,16 @@ function useController(props) {
221
352
  control,
222
353
  name,
223
354
  defaultValue: get(control._formValues, name, get(control._defaultValues, name, props.defaultValue)),
224
- exact: !isArrayField,
355
+ exact: true,
225
356
  });
226
357
  const formState = useFormState({
227
358
  control,
228
359
  name,
229
360
  });
230
- const _registerProps = React.useRef(control.register(name, Object.assign(Object.assign({}, props.rules), { value: value })));
361
+ const _registerProps = React.useRef(control.register(name, {
362
+ ...props.rules,
363
+ value: value,
364
+ }));
231
365
  React.useEffect(() => {
232
366
  const updateMounted = (name, value) => {
233
367
  const field = get(control._fields, name);
@@ -283,10 +417,59 @@ function useController(props) {
283
417
  };
284
418
  }
285
419
 
420
+ /**
421
+ * Component based on `useController` hook to work with controlled component.
422
+ *
423
+ * @remarks
424
+ * [API](https://react-hook-form.com/api/usecontroller/controller) • [Demo](https://codesandbox.io/s/react-hook-form-v6-controller-ts-jwyzw) • [Video](https://www.youtube.com/watch?v=N2UNk_UCVyA)
425
+ *
426
+ * @param props - the path name to the form field value, and validation rules.
427
+ *
428
+ * @returns provide field handler functions, field and form state.
429
+ *
430
+ * @example
431
+ * ```tsx
432
+ * function App() {
433
+ * const { control } = useForm<FormValues>({
434
+ * defaultValues: {
435
+ * test: ""
436
+ * }
437
+ * });
438
+ *
439
+ * return (
440
+ * <form>
441
+ * <Controller
442
+ * control={control}
443
+ * name="test"
444
+ * render={({ field: { onChange, onBlur, value, ref }, formState, fieldState }) => (
445
+ * <>
446
+ * <input
447
+ * onChange={onChange} // send value to hook form
448
+ * onBlur={onBlur} // notify when input is touched
449
+ * value={value} // return updated value
450
+ * ref={ref} // set ref for focus management
451
+ * />
452
+ * <p>{formState.isSubmitted ? "submitted" : ""}</p>
453
+ * <p>{fieldState.isTouched ? "touched" : ""}</p>
454
+ * </>
455
+ * )}
456
+ * />
457
+ * </form>
458
+ * );
459
+ * }
460
+ * ```
461
+ */
286
462
  const Controller = (props) => props.render(useController(props));
287
463
 
288
464
  var appendErrors = (name, validateAllFieldCriteria, errors, type, message) => validateAllFieldCriteria
289
- ? Object.assign(Object.assign({}, errors[name]), { types: Object.assign(Object.assign({}, (errors[name] && errors[name].types ? errors[name].types : {})), { [type]: message || true }) }) : {};
465
+ ? {
466
+ ...errors[name],
467
+ types: {
468
+ ...(errors[name] && errors[name].types ? errors[name].types : {}),
469
+ [type]: message || true,
470
+ },
471
+ }
472
+ : {};
290
473
 
291
474
  /**
292
475
  * Function for joining two paths / path strings to a {@link TypedFieldPath}.
@@ -350,8 +533,7 @@ const focusFieldBy = (fields, callback, fieldsNames) => {
350
533
  for (const key of fieldsNames || Object.keys(fields)) {
351
534
  const field = get(fields, key);
352
535
  if (field) {
353
- const _f = field._f;
354
- const current = omit(field, '_f');
536
+ const { _f, ...currentField } = field;
355
537
  if (_f && callback(_f.name)) {
356
538
  if (_f.ref.focus && isUndefined(_f.ref.focus())) {
357
539
  break;
@@ -361,8 +543,8 @@ const focusFieldBy = (fields, callback, fieldsNames) => {
361
543
  break;
362
544
  }
363
545
  }
364
- else if (isObject(current)) {
365
- focusFieldBy(current, callback);
546
+ else if (isObject(currentField)) {
547
+ focusFieldBy(currentField, callback);
366
548
  }
367
549
  }
368
550
  }
@@ -387,6 +569,241 @@ var isWatched = (name, _names, isBlurEvent) => !isBlurEvent &&
387
569
  [..._names.watch].some((watchName) => name.startsWith(watchName) &&
388
570
  /^\.\w+/.test(name.slice(watchName.length))));
389
571
 
572
+ var updateFieldArrayRootError = (errors, error, name) => {
573
+ const fieldArrayErrors = compact(get(errors, name));
574
+ set(fieldArrayErrors, 'root', error[name]);
575
+ set(errors, name, fieldArrayErrors);
576
+ return errors;
577
+ };
578
+
579
+ var isBoolean = (value) => typeof value === 'boolean';
580
+
581
+ var isFileInput = (element) => element.type === 'file';
582
+
583
+ var isMessage = (value) => isString(value) || React.isValidElement(value);
584
+
585
+ var isRadioInput = (element) => element.type === 'radio';
586
+
587
+ var isRegex = (value) => value instanceof RegExp;
588
+
589
+ const defaultResult = {
590
+ value: false,
591
+ isValid: false,
592
+ };
593
+ const validResult = { value: true, isValid: true };
594
+ var getCheckboxValue = (options) => {
595
+ if (Array.isArray(options)) {
596
+ if (options.length > 1) {
597
+ const values = options
598
+ .filter((option) => option && option.checked && !option.disabled)
599
+ .map((option) => option.value);
600
+ return { value: values, isValid: !!values.length };
601
+ }
602
+ return options[0].checked && !options[0].disabled
603
+ ? // @ts-expect-error expected to work in the browser
604
+ options[0].attributes && !isUndefined(options[0].attributes.value)
605
+ ? isUndefined(options[0].value) || options[0].value === ''
606
+ ? validResult
607
+ : { value: options[0].value, isValid: true }
608
+ : validResult
609
+ : defaultResult;
610
+ }
611
+ return defaultResult;
612
+ };
613
+
614
+ const defaultReturn = {
615
+ isValid: false,
616
+ value: null,
617
+ };
618
+ var getRadioValue = (options) => Array.isArray(options)
619
+ ? options.reduce((previous, option) => option && option.checked && !option.disabled
620
+ ? {
621
+ isValid: true,
622
+ value: option.value,
623
+ }
624
+ : previous, defaultReturn)
625
+ : defaultReturn;
626
+
627
+ function getValidateError(result, ref, type = 'validate') {
628
+ if (isMessage(result) ||
629
+ (Array.isArray(result) && result.every(isMessage)) ||
630
+ (isBoolean(result) && !result)) {
631
+ return {
632
+ type,
633
+ message: isMessage(result) ? result : '',
634
+ ref,
635
+ };
636
+ }
637
+ }
638
+
639
+ var getValueAndMessage = (validationData) => isObject(validationData) && !isRegex(validationData)
640
+ ? validationData
641
+ : {
642
+ value: validationData,
643
+ message: '',
644
+ };
645
+
646
+ var validateField = async (field, inputValue, validateAllFieldCriteria, shouldUseNativeValidation, isFieldArray) => {
647
+ const { ref, refs, required, maxLength, minLength, min, max, pattern, validate, name, valueAsNumber, mount, disabled, } = field._f;
648
+ if (!mount || disabled) {
649
+ return {};
650
+ }
651
+ const inputRef = refs ? refs[0] : ref;
652
+ const setCustomValidity = (message) => {
653
+ if (shouldUseNativeValidation && inputRef.reportValidity) {
654
+ inputRef.setCustomValidity(isBoolean(message) ? '' : message || ' ');
655
+ inputRef.reportValidity();
656
+ }
657
+ };
658
+ const error = {};
659
+ const isRadio = isRadioInput(ref);
660
+ const isCheckBox = isCheckBoxInput(ref);
661
+ const isRadioOrCheckbox = isRadio || isCheckBox;
662
+ const isEmpty = ((valueAsNumber || isFileInput(ref)) && !ref.value) ||
663
+ inputValue === '' ||
664
+ (Array.isArray(inputValue) && !inputValue.length);
665
+ const appendErrorsCurry = appendErrors.bind(null, name, validateAllFieldCriteria, error);
666
+ const getMinMaxMessage = (exceedMax, maxLengthMessage, minLengthMessage, maxType = INPUT_VALIDATION_RULES.maxLength, minType = INPUT_VALIDATION_RULES.minLength) => {
667
+ const message = exceedMax ? maxLengthMessage : minLengthMessage;
668
+ error[name] = {
669
+ type: exceedMax ? maxType : minType,
670
+ message,
671
+ ref,
672
+ ...appendErrorsCurry(exceedMax ? maxType : minType, message),
673
+ };
674
+ };
675
+ if (required &&
676
+ ((!isRadioOrCheckbox && (isEmpty || isNullOrUndefined(inputValue))) ||
677
+ (isBoolean(inputValue) && !inputValue) ||
678
+ (isCheckBox && !getCheckboxValue(refs).isValid) ||
679
+ (isRadio && !getRadioValue(refs).isValid))) {
680
+ const { value, message } = isMessage(required)
681
+ ? { value: !!required, message: required }
682
+ : getValueAndMessage(required);
683
+ if (value) {
684
+ error[name] = {
685
+ type: INPUT_VALIDATION_RULES.required,
686
+ message,
687
+ ref: inputRef,
688
+ ...appendErrorsCurry(INPUT_VALIDATION_RULES.required, message),
689
+ };
690
+ if (!validateAllFieldCriteria) {
691
+ setCustomValidity(message);
692
+ return error;
693
+ }
694
+ }
695
+ }
696
+ if (!isEmpty && (!isNullOrUndefined(min) || !isNullOrUndefined(max))) {
697
+ let exceedMax;
698
+ let exceedMin;
699
+ const maxOutput = getValueAndMessage(max);
700
+ const minOutput = getValueAndMessage(min);
701
+ if (!isNaN(inputValue)) {
702
+ const valueNumber = ref.valueAsNumber || +inputValue;
703
+ if (!isNullOrUndefined(maxOutput.value)) {
704
+ exceedMax = valueNumber > maxOutput.value;
705
+ }
706
+ if (!isNullOrUndefined(minOutput.value)) {
707
+ exceedMin = valueNumber < minOutput.value;
708
+ }
709
+ }
710
+ else {
711
+ const valueDate = ref.valueAsDate || new Date(inputValue);
712
+ if (isString(maxOutput.value)) {
713
+ exceedMax = valueDate > new Date(maxOutput.value);
714
+ }
715
+ if (isString(minOutput.value)) {
716
+ exceedMin = valueDate < new Date(minOutput.value);
717
+ }
718
+ }
719
+ if (exceedMax || exceedMin) {
720
+ getMinMaxMessage(!!exceedMax, maxOutput.message, minOutput.message, INPUT_VALIDATION_RULES.max, INPUT_VALIDATION_RULES.min);
721
+ if (!validateAllFieldCriteria) {
722
+ setCustomValidity(error[name].message);
723
+ return error;
724
+ }
725
+ }
726
+ }
727
+ if ((maxLength || minLength) &&
728
+ ((!isEmpty && isString(inputValue)) ||
729
+ (isFieldArray && Array.isArray(inputValue)))) {
730
+ const maxLengthOutput = getValueAndMessage(maxLength);
731
+ const minLengthOutput = getValueAndMessage(minLength);
732
+ const exceedMax = !isNullOrUndefined(maxLengthOutput.value) &&
733
+ inputValue.length > maxLengthOutput.value;
734
+ const exceedMin = !isNullOrUndefined(minLengthOutput.value) &&
735
+ inputValue.length < minLengthOutput.value;
736
+ if (exceedMax || exceedMin) {
737
+ getMinMaxMessage(exceedMax, maxLengthOutput.message, minLengthOutput.message);
738
+ if (!validateAllFieldCriteria) {
739
+ setCustomValidity(error[name].message);
740
+ return error;
741
+ }
742
+ }
743
+ }
744
+ if (pattern && !isEmpty && isString(inputValue)) {
745
+ const { value: patternValue, message } = getValueAndMessage(pattern);
746
+ if (isRegex(patternValue) && !inputValue.match(patternValue)) {
747
+ error[name] = {
748
+ type: INPUT_VALIDATION_RULES.pattern,
749
+ message,
750
+ ref,
751
+ ...appendErrorsCurry(INPUT_VALIDATION_RULES.pattern, message),
752
+ };
753
+ if (!validateAllFieldCriteria) {
754
+ setCustomValidity(message);
755
+ return error;
756
+ }
757
+ }
758
+ }
759
+ if (validate) {
760
+ if (isFunction(validate)) {
761
+ const result = await validate(inputValue);
762
+ const validateError = getValidateError(result, inputRef);
763
+ if (validateError) {
764
+ error[name] = {
765
+ ...validateError,
766
+ ...appendErrorsCurry(INPUT_VALIDATION_RULES.validate, validateError.message),
767
+ };
768
+ if (!validateAllFieldCriteria) {
769
+ setCustomValidity(validateError.message);
770
+ return error;
771
+ }
772
+ }
773
+ }
774
+ else if (isObject(validate)) {
775
+ let validationResult = {};
776
+ for (const key in validate) {
777
+ if (!isEmptyObject(validationResult) && !validateAllFieldCriteria) {
778
+ break;
779
+ }
780
+ const validateError = getValidateError(await validate[key](inputValue), inputRef, key);
781
+ if (validateError) {
782
+ validationResult = {
783
+ ...validateError,
784
+ ...appendErrorsCurry(key, validateError.message),
785
+ };
786
+ setCustomValidity(validateError.message);
787
+ if (validateAllFieldCriteria) {
788
+ error[name] = validationResult;
789
+ }
790
+ }
791
+ }
792
+ if (!isEmptyObject(validationResult)) {
793
+ error[name] = {
794
+ ref: inputRef,
795
+ ...validationResult,
796
+ };
797
+ if (!validateAllFieldCriteria) {
798
+ return error;
799
+ }
800
+ }
801
+ }
802
+ }
803
+ setCustomValidity(true);
804
+ return error;
805
+ };
806
+
390
807
  function append(data, value) {
391
808
  return [...data, ...convertToArrayPayload(value)];
392
809
  }
@@ -418,6 +835,14 @@ function cloneObject(data) {
418
835
 
419
836
  var fillEmptyArray = (value) => Array.isArray(value) ? value.map(() => undefined) : undefined;
420
837
 
838
+ var getValidationModes = (mode) => ({
839
+ isOnSubmit: !mode || mode === VALIDATION_MODE.onSubmit,
840
+ isOnBlur: mode === VALIDATION_MODE.onBlur,
841
+ isOnChange: mode === VALIDATION_MODE.onChange,
842
+ isOnAll: mode === VALIDATION_MODE.all,
843
+ isOnTouch: mode === VALIDATION_MODE.onTouched,
844
+ });
845
+
421
846
  function insert(data, index, value) {
422
847
  return [
423
848
  ...data.slice(0, index),
@@ -463,7 +888,44 @@ var updateAt = (fieldValues, index, value) => {
463
888
  return fieldValues;
464
889
  };
465
890
 
466
- const useFieldArray = (props) => {
891
+ /**
892
+ * A custom hook that exposes convenient methods to perform operations with a list of dynamic inputs that need to be appended, updated, removed etc.
893
+ *
894
+ * @remarks
895
+ * [API](https://react-hook-form.com/api/usefieldarray) • [Demo](https://codesandbox.io/s/react-hook-form-usefieldarray-ssugn)
896
+ *
897
+ * @param props - useFieldArray props
898
+ *
899
+ * @returns methods - functions to manipulate with the Field Arrays (dynamic inputs) {@link UseFieldArrayReturn}
900
+ *
901
+ * @example
902
+ * ```tsx
903
+ * function App() {
904
+ * const { register, control, handleSubmit, reset, trigger, setError } = useForm({
905
+ * defaultValues: {
906
+ * test: []
907
+ * }
908
+ * });
909
+ * const { fields, append } = useFieldArray({
910
+ * control,
911
+ * name: "test"
912
+ * });
913
+ *
914
+ * return (
915
+ * <form onSubmit={handleSubmit(data => console.log(data))}>
916
+ * {fields.map((item, index) => (
917
+ * <input key={item.id} {...register(`test.${index}.firstName`)} />
918
+ * ))}
919
+ * <button type="button" onClick={() => append({ firstName: "bill" })}>
920
+ * append
921
+ * </button>
922
+ * <input type="submit" />
923
+ * </form>
924
+ * );
925
+ * }
926
+ * ```
927
+ */
928
+ function useFieldArray(props) {
467
929
  const methods = useFormContext();
468
930
  const { control = methods.control, name, shouldUnregister } = props;
469
931
  const [fields, setFields] = React.useState(control._getFieldArray(name));
@@ -474,6 +936,9 @@ const useFieldArray = (props) => {
474
936
  _name.current = name;
475
937
  _fieldIds.current = fields;
476
938
  control._names.array.add(name);
939
+ if (props.rules) {
940
+ control.register(name, props.rules);
941
+ }
477
942
  const callback = React.useCallback(({ values, name: fieldArrayName }) => {
478
943
  if (fieldArrayName === _name.current || !fieldArrayName) {
479
944
  const fieldValues = get(values, _name.current, []);
@@ -555,13 +1020,14 @@ const useFieldArray = (props) => {
555
1020
  }, false);
556
1021
  };
557
1022
  const update = (index, value) => {
1023
+ const updateValue = cloneObject(value);
558
1024
  const updatedFieldArrayValues = updateAt(control._getFieldArray(name), index, value);
559
1025
  ids.current = [...updatedFieldArrayValues].map((item, i) => !item || i === index ? generateId() : ids.current[i]);
560
1026
  updateValues(updatedFieldArrayValues);
561
1027
  setFields([...updatedFieldArrayValues]);
562
1028
  control._updateFieldArray(name, updatedFieldArrayValues, updateAt, {
563
1029
  argA: index,
564
- argB: value,
1030
+ argB: updateValue,
565
1031
  }, true, false);
566
1032
  };
567
1033
  const replace = (value) => {
@@ -575,15 +1041,33 @@ const useFieldArray = (props) => {
575
1041
  control._stateFlags.action = false;
576
1042
  isWatched(name, control._names) && control._subjects.state.next({});
577
1043
  if (_actioned.current) {
578
- control._executeSchema([name]).then((result) => {
579
- const error = get(result.errors, name);
580
- if (error && error.type && !get(control._formState.errors, name)) {
581
- set(control._formState.errors, name, error);
582
- control._subjects.state.next({
583
- errors: control._formState.errors,
1044
+ if (control._options.resolver) {
1045
+ control._executeSchema([name]).then((result) => {
1046
+ const error = get(result.errors, name);
1047
+ if (error && error.type && !get(control._formState.errors, name)) {
1048
+ set(control._formState.errors, name, error);
1049
+ control._subjects.state.next({
1050
+ errors: control._formState.errors,
1051
+ });
1052
+ }
1053
+ });
1054
+ }
1055
+ else {
1056
+ const field = get(control._fields, name);
1057
+ const validationModeBeforeSubmit = getValidationModes(control._options.mode);
1058
+ if ((!validationModeBeforeSubmit.isOnSubmit ||
1059
+ control._formState.isSubmitted) &&
1060
+ field &&
1061
+ field._f) {
1062
+ validateField(field, get(control._formValues, name), control._options.criteriaMode === VALIDATION_MODE.all, control._options.shouldUseNativeValidation, true).then((error) => {
1063
+ if (!isEmptyObject(error)) {
1064
+ control._subjects.state.next({
1065
+ errors: updateFieldArrayRootError(control._formState.errors, error, name),
1066
+ });
1067
+ }
584
1068
  });
585
1069
  }
586
- });
1070
+ }
587
1071
  }
588
1072
  control._subjects.watch.next({
589
1073
  name,
@@ -610,9 +1094,12 @@ const useFieldArray = (props) => {
610
1094
  insert: React.useCallback(insert$1, [updateValues, name, control]),
611
1095
  update: React.useCallback(update, [updateValues, name, control]),
612
1096
  replace: React.useCallback(replace, [updateValues, name, control]),
613
- fields: React.useMemo(() => fields.map((field, index) => (Object.assign(Object.assign({}, field), { id: ids.current[index] || generateId() }))), [fields]),
1097
+ fields: React.useMemo(() => fields.map((field, index) => ({
1098
+ ...field,
1099
+ id: ids.current[index] || generateId(),
1100
+ })), [fields]),
614
1101
  };
615
- };
1102
+ }
616
1103
 
617
1104
  function createSubject() {
618
1105
  let _observers = [];
@@ -675,24 +1162,10 @@ function deepEqual(object1, object2) {
675
1162
  return true;
676
1163
  }
677
1164
 
678
- var getValidationModes = (mode) => ({
679
- isOnSubmit: !mode || mode === VALIDATION_MODE.onSubmit,
680
- isOnBlur: mode === VALIDATION_MODE.onBlur,
681
- isOnChange: mode === VALIDATION_MODE.onChange,
682
- isOnAll: mode === VALIDATION_MODE.all,
683
- isOnTouch: mode === VALIDATION_MODE.onTouched,
684
- });
685
-
686
- var isBoolean = (value) => typeof value === 'boolean';
687
-
688
- var isFileInput = (element) => element.type === 'file';
689
-
690
1165
  var isHTMLElement = (value) => value instanceof HTMLElement;
691
1166
 
692
1167
  var isMultipleSelect = (element) => element.type === `select-multiple`;
693
1168
 
694
- var isRadioInput = (element) => element.type === 'radio';
695
-
696
1169
  var isRadioOrCheckbox = (ref) => isRadioInput(ref) || isCheckBoxInput(ref);
697
1170
 
698
1171
  var isWeb = typeof window !== 'undefined' &&
@@ -709,6 +1182,14 @@ function baseGet(object, updatePath) {
709
1182
  }
710
1183
  return object;
711
1184
  }
1185
+ function isEmptyArray(obj) {
1186
+ for (const key in obj) {
1187
+ if (!isUndefined(obj[key])) {
1188
+ return false;
1189
+ }
1190
+ }
1191
+ return true;
1192
+ }
712
1193
  function unset(object, path) {
713
1194
  const updatePath = isKey(path) ? [path] : stringToPath(path);
714
1195
  const childObject = updatePath.length == 1 ? object : baseGet(object, updatePath);
@@ -730,8 +1211,7 @@ function unset(object, path) {
730
1211
  objectRef = objectRef ? objectRef[item] : object[item];
731
1212
  if (currentPathsLength === index &&
732
1213
  ((isObject(objectRef) && isEmptyObject(objectRef)) ||
733
- (Array.isArray(objectRef) &&
734
- !objectRef.filter((data) => (isObject(data) && !isEmptyObject(data)) || isBoolean(data)).length))) {
1214
+ (Array.isArray(objectRef) && isEmptyArray(objectRef)))) {
735
1215
  previousObjRef ? delete previousObjRef[item] : delete object[item];
736
1216
  }
737
1217
  previousObjRef = objectRef;
@@ -766,7 +1246,7 @@ function getDirtyFieldsFromDefaultValues(data, formValues, dirtyFieldsFromValues
766
1246
  isPrimitive(dirtyFieldsFromValues[key])) {
767
1247
  dirtyFieldsFromValues[key] = Array.isArray(data[key])
768
1248
  ? markFieldsDirty(data[key], [])
769
- : Object.assign({}, markFieldsDirty(data[key]));
1249
+ : { ...markFieldsDirty(data[key]) };
770
1250
  }
771
1251
  else {
772
1252
  getDirtyFieldsFromDefaultValues(data[key], isNullOrUndefined(formValues) ? {} : formValues[key], dirtyFieldsFromValues[key]);
@@ -781,31 +1261,6 @@ function getDirtyFieldsFromDefaultValues(data, formValues, dirtyFieldsFromValues
781
1261
  }
782
1262
  var getDirtyFields = (defaultValues, formValues) => getDirtyFieldsFromDefaultValues(defaultValues, formValues, markFieldsDirty(formValues));
783
1263
 
784
- const defaultResult = {
785
- value: false,
786
- isValid: false,
787
- };
788
- const validResult = { value: true, isValid: true };
789
- var getCheckboxValue = (options) => {
790
- if (Array.isArray(options)) {
791
- if (options.length > 1) {
792
- const values = options
793
- .filter((option) => option && option.checked && !option.disabled)
794
- .map((option) => option.value);
795
- return { value: values, isValid: !!values.length };
796
- }
797
- return options[0].checked && !options[0].disabled
798
- ? // @ts-expect-error expected to work in the browser
799
- options[0].attributes && !isUndefined(options[0].attributes.value)
800
- ? isUndefined(options[0].value) || options[0].value === ''
801
- ? validResult
802
- : { value: options[0].value, isValid: true }
803
- : validResult
804
- : defaultResult;
805
- }
806
- return defaultResult;
807
- };
808
-
809
1264
  var getFieldValueAs = (value, { valueAsNumber, valueAsDate, setValueAs }) => isUndefined(value)
810
1265
  ? value
811
1266
  : valueAsNumber
@@ -818,19 +1273,6 @@ var getFieldValueAs = (value, { valueAsNumber, valueAsDate, setValueAs }) => isU
818
1273
  ? setValueAs(value)
819
1274
  : value;
820
1275
 
821
- const defaultReturn = {
822
- isValid: false,
823
- value: null,
824
- };
825
- var getRadioValue = (options) => Array.isArray(options)
826
- ? options.reduce((previous, option) => option && option.checked && !option.disabled
827
- ? {
828
- isValid: true,
829
- value: option.value,
830
- }
831
- : previous, defaultReturn)
832
- : defaultReturn;
833
-
834
1276
  function getFieldValue(_f) {
835
1277
  const ref = _f.ref;
836
1278
  if (_f.refs ? _f.refs.every((ref) => ref.disabled) : ref.disabled) {
@@ -865,8 +1307,6 @@ var getResolverOptions = (fieldsNames, _fields, criteriaMode, shouldUseNativeVal
865
1307
  };
866
1308
  };
867
1309
 
868
- var isRegex = (value) => value instanceof RegExp;
869
-
870
1310
  var getRuleValue = (rule) => isUndefined(rule)
871
1311
  ? undefined
872
1312
  : isRegex(rule)
@@ -933,171 +1373,16 @@ var skipValidation = (isBlurEvent, isTouched, isSubmitted, reValidateMode, mode)
933
1373
 
934
1374
  var unsetEmptyArray = (ref, name) => !compact(get(ref, name)).length && unset(ref, name);
935
1375
 
936
- var isMessage = (value) => isString(value) || React.isValidElement(value);
937
-
938
- function getValidateError(result, ref, type = 'validate') {
939
- if (isMessage(result) ||
940
- (Array.isArray(result) && result.every(isMessage)) ||
941
- (isBoolean(result) && !result)) {
942
- return {
943
- type,
944
- message: isMessage(result) ? result : '',
945
- ref,
946
- };
947
- }
948
- }
949
-
950
- var getValueAndMessage = (validationData) => isObject(validationData) && !isRegex(validationData)
951
- ? validationData
952
- : {
953
- value: validationData,
954
- message: '',
955
- };
956
-
957
- var validateField = async (field, inputValue, validateAllFieldCriteria, shouldUseNativeValidation) => {
958
- const { ref, refs, required, maxLength, minLength, min, max, pattern, validate, name, valueAsNumber, mount, disabled, } = field._f;
959
- if (!mount || disabled) {
960
- return {};
961
- }
962
- const inputRef = refs ? refs[0] : ref;
963
- const setCustomValidity = (message) => {
964
- if (shouldUseNativeValidation && inputRef.reportValidity) {
965
- inputRef.setCustomValidity(isBoolean(message) ? '' : message || ' ');
966
- inputRef.reportValidity();
967
- }
968
- };
969
- const error = {};
970
- const isRadio = isRadioInput(ref);
971
- const isCheckBox = isCheckBoxInput(ref);
972
- const isRadioOrCheckbox = isRadio || isCheckBox;
973
- const isEmpty = ((valueAsNumber || isFileInput(ref)) && !ref.value) ||
974
- inputValue === '' ||
975
- (Array.isArray(inputValue) && !inputValue.length);
976
- const appendErrorsCurry = appendErrors.bind(null, name, validateAllFieldCriteria, error);
977
- const getMinMaxMessage = (exceedMax, maxLengthMessage, minLengthMessage, maxType = INPUT_VALIDATION_RULES.maxLength, minType = INPUT_VALIDATION_RULES.minLength) => {
978
- const message = exceedMax ? maxLengthMessage : minLengthMessage;
979
- error[name] = Object.assign({ type: exceedMax ? maxType : minType, message,
980
- ref }, appendErrorsCurry(exceedMax ? maxType : minType, message));
981
- };
982
- if (required &&
983
- ((!isRadioOrCheckbox && (isEmpty || isNullOrUndefined(inputValue))) ||
984
- (isBoolean(inputValue) && !inputValue) ||
985
- (isCheckBox && !getCheckboxValue(refs).isValid) ||
986
- (isRadio && !getRadioValue(refs).isValid))) {
987
- const { value, message } = isMessage(required)
988
- ? { value: !!required, message: required }
989
- : getValueAndMessage(required);
990
- if (value) {
991
- error[name] = Object.assign({ type: INPUT_VALIDATION_RULES.required, message, ref: inputRef }, appendErrorsCurry(INPUT_VALIDATION_RULES.required, message));
992
- if (!validateAllFieldCriteria) {
993
- setCustomValidity(message);
994
- return error;
995
- }
996
- }
997
- }
998
- if (!isEmpty && (!isNullOrUndefined(min) || !isNullOrUndefined(max))) {
999
- let exceedMax;
1000
- let exceedMin;
1001
- const maxOutput = getValueAndMessage(max);
1002
- const minOutput = getValueAndMessage(min);
1003
- if (!isNaN(inputValue)) {
1004
- const valueNumber = ref.valueAsNumber || +inputValue;
1005
- if (!isNullOrUndefined(maxOutput.value)) {
1006
- exceedMax = valueNumber > maxOutput.value;
1007
- }
1008
- if (!isNullOrUndefined(minOutput.value)) {
1009
- exceedMin = valueNumber < minOutput.value;
1010
- }
1011
- }
1012
- else {
1013
- const valueDate = ref.valueAsDate || new Date(inputValue);
1014
- if (isString(maxOutput.value)) {
1015
- exceedMax = valueDate > new Date(maxOutput.value);
1016
- }
1017
- if (isString(minOutput.value)) {
1018
- exceedMin = valueDate < new Date(minOutput.value);
1019
- }
1020
- }
1021
- if (exceedMax || exceedMin) {
1022
- getMinMaxMessage(!!exceedMax, maxOutput.message, minOutput.message, INPUT_VALIDATION_RULES.max, INPUT_VALIDATION_RULES.min);
1023
- if (!validateAllFieldCriteria) {
1024
- setCustomValidity(error[name].message);
1025
- return error;
1026
- }
1027
- }
1028
- }
1029
- if ((maxLength || minLength) && !isEmpty && isString(inputValue)) {
1030
- const maxLengthOutput = getValueAndMessage(maxLength);
1031
- const minLengthOutput = getValueAndMessage(minLength);
1032
- const exceedMax = !isNullOrUndefined(maxLengthOutput.value) &&
1033
- inputValue.length > maxLengthOutput.value;
1034
- const exceedMin = !isNullOrUndefined(minLengthOutput.value) &&
1035
- inputValue.length < minLengthOutput.value;
1036
- if (exceedMax || exceedMin) {
1037
- getMinMaxMessage(exceedMax, maxLengthOutput.message, minLengthOutput.message);
1038
- if (!validateAllFieldCriteria) {
1039
- setCustomValidity(error[name].message);
1040
- return error;
1041
- }
1042
- }
1043
- }
1044
- if (pattern && !isEmpty && isString(inputValue)) {
1045
- const { value: patternValue, message } = getValueAndMessage(pattern);
1046
- if (isRegex(patternValue) && !inputValue.match(patternValue)) {
1047
- error[name] = Object.assign({ type: INPUT_VALIDATION_RULES.pattern, message,
1048
- ref }, appendErrorsCurry(INPUT_VALIDATION_RULES.pattern, message));
1049
- if (!validateAllFieldCriteria) {
1050
- setCustomValidity(message);
1051
- return error;
1052
- }
1053
- }
1054
- }
1055
- if (validate) {
1056
- if (isFunction(validate)) {
1057
- const result = await validate(inputValue);
1058
- const validateError = getValidateError(result, inputRef);
1059
- if (validateError) {
1060
- error[name] = Object.assign(Object.assign({}, validateError), appendErrorsCurry(INPUT_VALIDATION_RULES.validate, validateError.message));
1061
- if (!validateAllFieldCriteria) {
1062
- setCustomValidity(validateError.message);
1063
- return error;
1064
- }
1065
- }
1066
- }
1067
- else if (isObject(validate)) {
1068
- let validationResult = {};
1069
- for (const key in validate) {
1070
- if (!isEmptyObject(validationResult) && !validateAllFieldCriteria) {
1071
- break;
1072
- }
1073
- const validateError = getValidateError(await validate[key](inputValue), inputRef, key);
1074
- if (validateError) {
1075
- validationResult = Object.assign(Object.assign({}, validateError), appendErrorsCurry(key, validateError.message));
1076
- setCustomValidity(validateError.message);
1077
- if (validateAllFieldCriteria) {
1078
- error[name] = validationResult;
1079
- }
1080
- }
1081
- }
1082
- if (!isEmptyObject(validationResult)) {
1083
- error[name] = Object.assign({ ref: inputRef }, validationResult);
1084
- if (!validateAllFieldCriteria) {
1085
- return error;
1086
- }
1087
- }
1088
- }
1089
- }
1090
- setCustomValidity(true);
1091
- return error;
1092
- };
1093
-
1094
1376
  const defaultOptions = {
1095
1377
  mode: VALIDATION_MODE.onSubmit,
1096
1378
  reValidateMode: VALIDATION_MODE.onChange,
1097
1379
  shouldFocusError: true,
1098
1380
  };
1099
1381
  function createFormControl(props = {}) {
1100
- let _options = Object.assign(Object.assign({}, defaultOptions), props);
1382
+ let _options = {
1383
+ ...defaultOptions,
1384
+ ...props,
1385
+ };
1101
1386
  let _formState = {
1102
1387
  isDirty: false,
1103
1388
  isValidating: false,
@@ -1111,7 +1396,7 @@ function createFormControl(props = {}) {
1111
1396
  errors: {},
1112
1397
  };
1113
1398
  let _fields = {};
1114
- let _defaultValues = _options.defaultValues || {};
1399
+ let _defaultValues = cloneObject(_options.defaultValues) || {};
1115
1400
  let _formValues = _options.shouldUnregister
1116
1401
  ? {}
1117
1402
  : cloneObject(_defaultValues);
@@ -1164,21 +1449,22 @@ function createFormControl(props = {}) {
1164
1449
  }
1165
1450
  return isValid;
1166
1451
  };
1167
- const _updateFieldArray = (name, values = [], method, args, shouldSetValues = true, shouldUpdateFieldsAndErrors = true) => {
1452
+ const _updateFieldArray = (name, values = [], method, args, shouldSetValues = true, shouldUpdateFieldsAndState = true) => {
1168
1453
  if (args && method) {
1169
1454
  _stateFlags.action = true;
1170
- if (shouldUpdateFieldsAndErrors && Array.isArray(get(_fields, name))) {
1455
+ if (shouldUpdateFieldsAndState && Array.isArray(get(_fields, name))) {
1171
1456
  const fieldValues = method(get(_fields, name), args.argA, args.argB);
1172
1457
  shouldSetValues && set(_fields, name, fieldValues);
1173
1458
  }
1174
1459
  if (_proxyFormState.errors &&
1175
- shouldUpdateFieldsAndErrors &&
1460
+ shouldUpdateFieldsAndState &&
1176
1461
  Array.isArray(get(_formState.errors, name))) {
1177
1462
  const errors = method(get(_formState.errors, name), args.argA, args.argB);
1178
1463
  shouldSetValues && set(_formState.errors, name, errors);
1179
1464
  unsetEmptyArray(_formState.errors, name);
1180
1465
  }
1181
1466
  if (_proxyFormState.touchedFields &&
1467
+ shouldUpdateFieldsAndState &&
1182
1468
  Array.isArray(get(_formState.touchedFields, name))) {
1183
1469
  const touchedFields = method(get(_formState.touchedFields, name), args.argA, args.argB);
1184
1470
  shouldSetValues && set(_formState.touchedFields, name, touchedFields);
@@ -1264,12 +1550,21 @@ function createFormControl(props = {}) {
1264
1550
  !isEmptyObject(fieldState) ||
1265
1551
  shouldUpdateValid) &&
1266
1552
  !shouldSkipRender) {
1267
- const updatedFormState = Object.assign(Object.assign(Object.assign({}, fieldState), (shouldUpdateValid ? { isValid } : {})), { errors: _formState.errors, name });
1268
- _formState = Object.assign(Object.assign({}, _formState), updatedFormState);
1553
+ const updatedFormState = {
1554
+ ...fieldState,
1555
+ ...(shouldUpdateValid ? { isValid } : {}),
1556
+ errors: _formState.errors,
1557
+ name,
1558
+ };
1559
+ _formState = {
1560
+ ..._formState,
1561
+ ...updatedFormState,
1562
+ };
1269
1563
  _subjects.state.next(updatedFormState);
1270
1564
  }
1271
1565
  validateFields[name]--;
1272
- if (_proxyFormState.isValidating && !validateFields[name]) {
1566
+ if (_proxyFormState.isValidating &&
1567
+ !Object.values(validateFields).some((v) => v)) {
1273
1568
  _subjects.state.next({
1274
1569
  isValidating: false,
1275
1570
  });
@@ -1277,7 +1572,7 @@ function createFormControl(props = {}) {
1277
1572
  }
1278
1573
  };
1279
1574
  const _executeSchema = async (name) => _options.resolver
1280
- ? await _options.resolver(Object.assign({}, _formValues), _options.context, getResolverOptions(name || _names.mount, _fields, _options.criteriaMode, _options.shouldUseNativeValidation))
1575
+ ? await _options.resolver({ ..._formValues }, _options.context, getResolverOptions(name || _names.mount, _fields, _options.criteriaMode, _options.shouldUseNativeValidation))
1281
1576
  : {};
1282
1577
  const executeSchemaAndUpdateState = async (names) => {
1283
1578
  const { errors } = await _executeSchema();
@@ -1300,20 +1595,28 @@ function createFormControl(props = {}) {
1300
1595
  for (const name in fields) {
1301
1596
  const field = fields[name];
1302
1597
  if (field) {
1303
- const fieldReference = field._f;
1304
- const fieldValue = omit(field, '_f');
1305
- if (fieldReference) {
1306
- const fieldError = await validateField(field, get(_formValues, fieldReference.name), shouldDisplayAllAssociatedErrors, _options.shouldUseNativeValidation);
1307
- if (fieldError[fieldReference.name]) {
1598
+ const { _f, ...fieldValue } = field;
1599
+ if (_f) {
1600
+ const isFieldArrayRoot = _names.array.has(_f.name);
1601
+ const fieldError = await validateField(field, get(_formValues, _f.name), shouldDisplayAllAssociatedErrors, _options.shouldUseNativeValidation, isFieldArrayRoot);
1602
+ if (fieldError[_f.name]) {
1308
1603
  context.valid = false;
1309
1604
  if (shouldOnlyCheckValid) {
1310
1605
  break;
1311
1606
  }
1312
1607
  }
1313
1608
  if (!shouldOnlyCheckValid) {
1314
- fieldError[fieldReference.name]
1315
- ? set(_formState.errors, fieldReference.name, fieldError[fieldReference.name])
1316
- : unset(_formState.errors, fieldReference.name);
1609
+ if (get(fieldError, _f.name)) {
1610
+ if (isFieldArrayRoot) {
1611
+ updateFieldArrayRootError(_formState.errors, fieldError, _f.name);
1612
+ }
1613
+ else {
1614
+ set(_formState.errors, _f.name, fieldError[_f.name]);
1615
+ }
1616
+ }
1617
+ else {
1618
+ unset(_formState.errors, _f.name);
1619
+ }
1317
1620
  }
1318
1621
  }
1319
1622
  fieldValue &&
@@ -1336,13 +1639,15 @@ function createFormControl(props = {}) {
1336
1639
  const _getDirty = (name, data) => (name && data && set(_formValues, name, data),
1337
1640
  !deepEqual(getValues(), _defaultValues));
1338
1641
  const _getWatch = (names, defaultValue, isGlobal) => {
1339
- const fieldValues = Object.assign({}, (_stateFlags.mount
1340
- ? _formValues
1341
- : isUndefined(defaultValue)
1342
- ? _defaultValues
1343
- : isString(names)
1344
- ? { [names]: defaultValue }
1345
- : defaultValue));
1642
+ const fieldValues = {
1643
+ ...(_stateFlags.mount
1644
+ ? _formValues
1645
+ : isUndefined(defaultValue)
1646
+ ? _defaultValues
1647
+ : isString(names)
1648
+ ? { [names]: defaultValue }
1649
+ : defaultValue),
1650
+ };
1346
1651
  return generateWatchOutput(names, _names, fieldValues, isGlobal);
1347
1652
  };
1348
1653
  const _getFieldArray = (name) => compact(get(_stateFlags.mount ? _formValues : _defaultValues, name, props.shouldUnregister ? get(_defaultValues, name, []) : []));
@@ -1364,9 +1669,10 @@ function createFormControl(props = {}) {
1364
1669
  else if (fieldReference.refs) {
1365
1670
  if (isCheckBoxInput(fieldReference.ref)) {
1366
1671
  fieldReference.refs.length > 1
1367
- ? fieldReference.refs.forEach((checkboxRef) => (checkboxRef.checked = Array.isArray(fieldValue)
1368
- ? !!fieldValue.find((data) => data === checkboxRef.value)
1369
- : fieldValue === checkboxRef.value))
1672
+ ? fieldReference.refs.forEach((checkboxRef) => !checkboxRef.disabled &&
1673
+ (checkboxRef.checked = Array.isArray(fieldValue)
1674
+ ? !!fieldValue.find((data) => data === checkboxRef.value)
1675
+ : fieldValue === checkboxRef.value))
1370
1676
  : fieldReference.refs[0] &&
1371
1677
  (fieldReference.refs[0].checked = !!fieldValue);
1372
1678
  }
@@ -1467,7 +1773,7 @@ function createFormControl(props = {}) {
1467
1773
  });
1468
1774
  if (shouldSkipValidation) {
1469
1775
  return (shouldRender &&
1470
- _subjects.state.next(Object.assign({ name }, (watched ? {} : fieldState))));
1776
+ _subjects.state.next({ name, ...(watched ? {} : fieldState) }));
1471
1777
  }
1472
1778
  !isBlurEvent && watched && _subjects.state.next({});
1473
1779
  validateFields[name] = validateFields[name] ? +1 : 1;
@@ -1486,7 +1792,8 @@ function createFormControl(props = {}) {
1486
1792
  error = (await validateField(field, get(_formValues, name), shouldDisplayAllAssociatedErrors, _options.shouldUseNativeValidation))[name];
1487
1793
  isValid = await _updateValid(true);
1488
1794
  }
1489
- field._f.deps && trigger(field._f.deps);
1795
+ field._f.deps &&
1796
+ trigger(field._f.deps);
1490
1797
  shouldRenderByError(false, name, isValid, error, fieldState);
1491
1798
  }
1492
1799
  };
@@ -1514,17 +1821,25 @@ function createFormControl(props = {}) {
1514
1821
  else {
1515
1822
  validationResult = isValid = await executeBuildInValidation(_fields);
1516
1823
  }
1517
- _subjects.state.next(Object.assign(Object.assign(Object.assign({}, (!isString(name) ||
1518
- (_proxyFormState.isValid && isValid !== _formState.isValid)
1519
- ? {}
1520
- : { name })), (_options.resolver ? { isValid } : {})), { errors: _formState.errors, isValidating: false }));
1824
+ _subjects.state.next({
1825
+ ...(!isString(name) ||
1826
+ (_proxyFormState.isValid && isValid !== _formState.isValid)
1827
+ ? {}
1828
+ : { name }),
1829
+ ...(_options.resolver ? { isValid } : {}),
1830
+ errors: _formState.errors,
1831
+ isValidating: false,
1832
+ });
1521
1833
  options.shouldFocus &&
1522
1834
  !validationResult &&
1523
1835
  focusFieldBy(_fields, (key) => get(_formState.errors, key), name ? fieldNames : _names.mount);
1524
1836
  return validationResult;
1525
1837
  };
1526
1838
  const getValues = (fieldNames) => {
1527
- const values = Object.assign(Object.assign({}, _defaultValues), (_stateFlags.mount ? _formValues : {}));
1839
+ const values = {
1840
+ ..._defaultValues,
1841
+ ...(_stateFlags.mount ? _formValues : {}),
1842
+ };
1528
1843
  return isUndefined(fieldNames)
1529
1844
  ? values
1530
1845
  : isString(fieldNames)
@@ -1547,7 +1862,10 @@ function createFormControl(props = {}) {
1547
1862
  };
1548
1863
  const setError = (name, error, options) => {
1549
1864
  const ref = (get(_fields, name, { _f: {} })._f || {}).ref;
1550
- set(_formState.errors, name, Object.assign(Object.assign({}, error), { ref }));
1865
+ set(_formState.errors, name, {
1866
+ ...error,
1867
+ ref,
1868
+ });
1551
1869
  _subjects.state.next({
1552
1870
  name,
1553
1871
  errors: _formState.errors,
@@ -1578,14 +1896,22 @@ function createFormControl(props = {}) {
1578
1896
  }
1579
1897
  }
1580
1898
  _subjects.watch.next({});
1581
- _subjects.state.next(Object.assign(Object.assign({}, _formState), (!options.keepDirty ? {} : { isDirty: _getDirty() })));
1899
+ _subjects.state.next({
1900
+ ..._formState,
1901
+ ...(!options.keepDirty ? {} : { isDirty: _getDirty() }),
1902
+ });
1582
1903
  _proxyFormState.isValid && _updateValid();
1583
1904
  };
1584
1905
  const register = (name, options = {}) => {
1585
1906
  let field = get(_fields, name);
1586
1907
  const disabledIsDefined = isBoolean(options.disabled);
1587
1908
  set(_fields, name, {
1588
- _f: Object.assign(Object.assign(Object.assign({}, (field && field._f ? field._f : { ref: { name } })), { name, mount: true }), options),
1909
+ _f: {
1910
+ ...(field && field._f ? field._f : { ref: { name } }),
1911
+ name,
1912
+ mount: true,
1913
+ ...options,
1914
+ },
1589
1915
  });
1590
1916
  _names.mount.add(name);
1591
1917
  field
@@ -1594,17 +1920,22 @@ function createFormControl(props = {}) {
1594
1920
  ? undefined
1595
1921
  : get(_formValues, name, getFieldValue(field._f)))
1596
1922
  : updateValidAndValue(name, true, options.value);
1597
- return Object.assign(Object.assign(Object.assign({}, (disabledIsDefined ? { disabled: options.disabled } : {})), (_options.shouldUseNativeValidation
1598
- ? {
1599
- required: !!options.required,
1600
- min: getRuleValue(options.min),
1601
- max: getRuleValue(options.max),
1602
- minLength: getRuleValue(options.minLength),
1603
- maxLength: getRuleValue(options.maxLength),
1604
- pattern: getRuleValue(options.pattern),
1605
- }
1606
- : {})), { name,
1607
- onChange, onBlur: onChange, ref: (ref) => {
1923
+ return {
1924
+ ...(disabledIsDefined ? { disabled: options.disabled } : {}),
1925
+ ...(_options.shouldUseNativeValidation
1926
+ ? {
1927
+ required: !!options.required,
1928
+ min: getRuleValue(options.min),
1929
+ max: getRuleValue(options.max),
1930
+ minLength: getRuleValue(options.minLength),
1931
+ maxLength: getRuleValue(options.maxLength),
1932
+ pattern: getRuleValue(options.pattern),
1933
+ }
1934
+ : {}),
1935
+ name,
1936
+ onChange,
1937
+ onBlur: onChange,
1938
+ ref: (ref) => {
1608
1939
  if (ref) {
1609
1940
  register(name, options);
1610
1941
  field = get(_fields, name);
@@ -1621,12 +1952,15 @@ function createFormControl(props = {}) {
1621
1952
  return;
1622
1953
  }
1623
1954
  set(_fields, name, {
1624
- _f: Object.assign(Object.assign({}, field._f), (radioOrCheckbox
1625
- ? {
1626
- refs: refs.concat(fieldRef).filter(live),
1627
- ref: { type: fieldRef.type, name },
1628
- }
1629
- : { ref: fieldRef })),
1955
+ _f: {
1956
+ ...field._f,
1957
+ ...(radioOrCheckbox
1958
+ ? {
1959
+ refs: [...refs.filter(live), fieldRef],
1960
+ ref: { type: fieldRef.type, name },
1961
+ }
1962
+ : { ref: fieldRef }),
1963
+ },
1630
1964
  });
1631
1965
  updateValidAndValue(name, false, undefined, fieldRef);
1632
1966
  }
@@ -1639,7 +1973,8 @@ function createFormControl(props = {}) {
1639
1973
  !(isNameInFieldArray(_names.array, name) && _stateFlags.action) &&
1640
1974
  _names.unMount.add(name);
1641
1975
  }
1642
- } });
1976
+ },
1977
+ };
1643
1978
  };
1644
1979
  const handleSubmit = (onValid, onInvalid) => async (e) => {
1645
1980
  if (e) {
@@ -1647,9 +1982,7 @@ function createFormControl(props = {}) {
1647
1982
  e.persist && e.persist();
1648
1983
  }
1649
1984
  let hasNoPromiseError = true;
1650
- let fieldValues = _options.shouldUnregister
1651
- ? cloneObject(_formValues)
1652
- : Object.assign({}, _formValues);
1985
+ let fieldValues = cloneObject(_formValues);
1653
1986
  _subjects.state.next({
1654
1987
  isSubmitting: true,
1655
1988
  });
@@ -1671,7 +2004,9 @@ function createFormControl(props = {}) {
1671
2004
  await onValid(fieldValues, e);
1672
2005
  }
1673
2006
  else {
1674
- onInvalid && (await onInvalid(_formState.errors, e));
2007
+ if (onInvalid) {
2008
+ await onInvalid({ ..._formState.errors }, e);
2009
+ }
1675
2010
  _options.shouldFocusError &&
1676
2011
  focusFieldBy(_fields, (key) => get(_formState.errors, key), _names.mount);
1677
2012
  }
@@ -1713,7 +2048,7 @@ function createFormControl(props = {}) {
1713
2048
  unset(_formState.errors, name);
1714
2049
  _proxyFormState.isValid && _updateValid();
1715
2050
  }
1716
- _subjects.state.next(Object.assign({}, _formState));
2051
+ _subjects.state.next({ ..._formState });
1717
2052
  }
1718
2053
  };
1719
2054
  const reset = (formValues, keepStateOptions = {}) => {
@@ -1726,22 +2061,6 @@ function createFormControl(props = {}) {
1726
2061
  _defaultValues = updatedValues;
1727
2062
  }
1728
2063
  if (!keepStateOptions.keepValues) {
1729
- if (isWeb && isUndefined(formValues)) {
1730
- for (const name of _names.mount) {
1731
- const field = get(_fields, name);
1732
- if (field && field._f) {
1733
- const fieldReference = Array.isArray(field._f.refs)
1734
- ? field._f.refs[0]
1735
- : field._f.ref;
1736
- try {
1737
- isHTMLElement(fieldReference) &&
1738
- fieldReference.closest('form').reset();
1739
- break;
1740
- }
1741
- catch (_a) { }
1742
- }
1743
- }
1744
- }
1745
2064
  _formValues = props.shouldUnregister
1746
2065
  ? keepStateOptions.keepDefaultValues
1747
2066
  ? cloneObject(_defaultValues)
@@ -1780,7 +2099,10 @@ function createFormControl(props = {}) {
1780
2099
  dirtyFields: keepStateOptions.keepDirty
1781
2100
  ? _formState.dirtyFields
1782
2101
  : (keepStateOptions.keepDefaultValues && formValues
1783
- ? Object.entries(formValues).reduce((previous, [key, value]) => (Object.assign(Object.assign({}, previous), { [key]: value !== get(_defaultValues, key) })), {})
2102
+ ? Object.entries(formValues).reduce((previous, [key, value]) => ({
2103
+ ...previous,
2104
+ [key]: value !== get(_defaultValues, key),
2105
+ }), {})
1784
2106
  : {}),
1785
2107
  touchedFields: keepStateOptions.keepTouched
1786
2108
  ? _formState.touchedFields
@@ -1792,9 +2114,10 @@ function createFormControl(props = {}) {
1792
2114
  isSubmitSuccessful: false,
1793
2115
  });
1794
2116
  };
1795
- const setFocus = (name) => {
2117
+ const setFocus = (name, options = {}) => {
1796
2118
  const field = get(_fields, name)._f;
1797
- (field.ref.focus ? field.ref : field.refs[0]).focus();
2119
+ const fieldRef = field.refs ? field.refs[0] : field.ref;
2120
+ options.shouldSelect ? fieldRef.select() : fieldRef.focus();
1798
2121
  };
1799
2122
  return {
1800
2123
  control: {
@@ -1841,7 +2164,10 @@ function createFormControl(props = {}) {
1841
2164
  return _options;
1842
2165
  },
1843
2166
  set _options(value) {
1844
- _options = Object.assign(Object.assign({}, _options), value);
2167
+ _options = {
2168
+ ..._options,
2169
+ ...value,
2170
+ };
1845
2171
  },
1846
2172
  },
1847
2173
  trigger,
@@ -1860,6 +2186,35 @@ function createFormControl(props = {}) {
1860
2186
  };
1861
2187
  }
1862
2188
 
2189
+ /**
2190
+ * Custom hook to mange the entire form.
2191
+ *
2192
+ * @remarks
2193
+ * [API](https://react-hook-form.com/api/useform) • [Demo](https://codesandbox.io/s/react-hook-form-get-started-ts-5ksmm) • [Video](https://www.youtube.com/watch?v=RkXv4AXXC_4)
2194
+ *
2195
+ * @param props - form configuration and validation parameters.
2196
+ *
2197
+ * @returns methods - individual functions to manage the form state. {@link UseFormReturn}
2198
+ *
2199
+ * @example
2200
+ * ```tsx
2201
+ * function App() {
2202
+ * const { register, handleSubmit, watch, formState: { errors } } = useForm();
2203
+ * const onSubmit = data => console.log(data);
2204
+ *
2205
+ * console.log(watch("example"));
2206
+ *
2207
+ * return (
2208
+ * <form onSubmit={handleSubmit(onSubmit)}>
2209
+ * <input defaultValue="test" {...register("example")} />
2210
+ * <input {...register("exampleRequired", { required: true })} />
2211
+ * {errors.exampleRequired && <span>This field is required</span>}
2212
+ * <input type="submit" />
2213
+ * </form>
2214
+ * );
2215
+ * }
2216
+ * ```
2217
+ */
1863
2218
  function useForm(props = {}) {
1864
2219
  const _formControl = React.useRef();
1865
2220
  const [formState, updateFormState] = React.useState({
@@ -1878,13 +2233,19 @@ function useForm(props = {}) {
1878
2233
  _formControl.current.control._options = props;
1879
2234
  }
1880
2235
  else {
1881
- _formControl.current = Object.assign(Object.assign({}, createFormControl(props)), { formState });
2236
+ _formControl.current = {
2237
+ ...createFormControl(props),
2238
+ formState,
2239
+ };
1882
2240
  }
1883
2241
  const control = _formControl.current.control;
1884
2242
  const callback = React.useCallback((value) => {
1885
2243
  if (shouldRenderFormState(value, control._proxyFormState, true)) {
1886
- control._formState = Object.assign(Object.assign({}, control._formState), value);
1887
- updateFormState(Object.assign({}, control._formState));
2244
+ control._formState = {
2245
+ ...control._formState,
2246
+ ...value,
2247
+ };
2248
+ updateFormState({ ...control._formState });
1888
2249
  }
1889
2250
  }, [control]);
1890
2251
  useSubscribe({