react-hook-form 8.0.0-beta.0 → 8.0.0-beta.2

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 (55) hide show
  1. package/README.md +16 -29
  2. package/dist/constants.d.ts +5 -0
  3. package/dist/constants.d.ts.map +1 -1
  4. package/dist/form.d.ts.map +1 -1
  5. package/dist/index.cjs.js +1 -1
  6. package/dist/index.cjs.js.map +1 -1
  7. package/dist/index.esm.mjs +541 -217
  8. package/dist/index.esm.mjs.map +1 -1
  9. package/dist/index.umd.js +1 -1
  10. package/dist/index.umd.js.map +1 -1
  11. package/dist/logic/createFormControl.d.ts +13 -0
  12. package/dist/logic/createFormControl.d.ts.map +1 -1
  13. package/dist/logic/getDirtyFields.d.ts.map +1 -1
  14. package/dist/logic/{isNameInFieldArray.d.ts → getFieldArrayParentNames.d.ts} +2 -2
  15. package/dist/logic/getFieldArrayParentNames.d.ts.map +1 -0
  16. package/dist/logic/getProxyFormState.d.ts.map +1 -1
  17. package/dist/logic/getResolverOptions.d.ts +2 -2
  18. package/dist/logic/hasValidation.d.ts +1 -1
  19. package/dist/logic/shouldRenderFormState.d.ts.map +1 -1
  20. package/dist/logic/updateFieldArrayRootError.d.ts.map +1 -1
  21. package/dist/logic/validateField.d.ts.map +1 -1
  22. package/dist/react-server.esm.mjs +362 -92
  23. package/dist/react-server.esm.mjs.map +1 -1
  24. package/dist/types/errors.d.ts +1 -0
  25. package/dist/types/errors.d.ts.map +1 -1
  26. package/dist/types/fieldArray.d.ts +1 -2
  27. package/dist/types/fieldArray.d.ts.map +1 -1
  28. package/dist/types/form.d.ts +14 -41
  29. package/dist/types/form.d.ts.map +1 -1
  30. package/dist/types/index.d.ts +1 -0
  31. package/dist/types/index.d.ts.map +1 -1
  32. package/dist/types/path/eager.d.ts +1 -1
  33. package/dist/types/path/eager.d.ts.map +1 -1
  34. package/dist/types/utils.d.ts +5 -4
  35. package/dist/types/utils.d.ts.map +1 -1
  36. package/dist/types/validator.d.ts +13 -1
  37. package/dist/types/validator.d.ts.map +1 -1
  38. package/dist/types/watch.d.ts +32 -0
  39. package/dist/types/watch.d.ts.map +1 -0
  40. package/dist/useController.d.ts.map +1 -1
  41. package/dist/useFieldArray.d.ts +2 -2
  42. package/dist/useFieldArray.d.ts.map +1 -1
  43. package/dist/useForm.d.ts.map +1 -1
  44. package/dist/useFormContext.d.ts.map +1 -1
  45. package/dist/useFormControlContext.d.ts +12 -0
  46. package/dist/useFormControlContext.d.ts.map +1 -0
  47. package/dist/useFormState.d.ts.map +1 -1
  48. package/dist/useWatch.d.ts.map +1 -1
  49. package/dist/utils/deepEqual.d.ts +1 -1
  50. package/dist/utils/deepEqual.d.ts.map +1 -1
  51. package/dist/utils/unset.d.ts.map +1 -1
  52. package/dist/watch.d.ts +7 -17
  53. package/dist/watch.d.ts.map +1 -1
  54. package/package.json +29 -29
  55. package/dist/logic/isNameInFieldArray.d.ts.map +0 -1
@@ -18,9 +18,15 @@ var getEventValue = (event) => isObject(event) && event.target
18
18
  : event.target.value
19
19
  : event;
20
20
 
21
- var getNodeParentName = (name) => name.substring(0, name.search(/\.\d+(\.|$)/)) || name;
22
-
23
- var isNameInFieldArray = (names, name) => names.has(getNodeParentName(name));
21
+ var getFieldArrayParentNames = (names, name) => {
22
+ const parts = name.split('.');
23
+ const matches = [];
24
+ let prefix = parts[0];
25
+ for (let i = 1; i < parts.length; prefix += '.' + parts[i++]) {
26
+ !isNaN(+parts[i]) && names.has(prefix) && matches.push(prefix);
27
+ }
28
+ return matches;
29
+ };
24
30
 
25
31
  var isPlainObject = (tempObject) => {
26
32
  const prototypeCopy = tempObject.constructor && tempObject.constructor.prototype;
@@ -64,7 +70,10 @@ var get = (object, path, defaultValue) => {
64
70
  if (!path || !isObject(object)) {
65
71
  return defaultValue;
66
72
  }
67
- const result = (isKey(path) ? [path] : stringToPath(path)).reduce((result, key) => isNullOrUndefined(result) ? result : result[key], object);
73
+ const paths = isKey(path) ? [path] : stringToPath(path);
74
+ const result = paths.reduce((result, key) => {
75
+ return isNullOrUndefined(result) ? undefined : result[key];
76
+ }, object);
68
77
  return isUndefined(result) || result === object
69
78
  ? isUndefined(object[path])
70
79
  ? defaultValue
@@ -103,6 +112,9 @@ const EVENTS = {
103
112
  BLUR: 'blur',
104
113
  FOCUS_OUT: 'focusout',
105
114
  CHANGE: 'change',
115
+ SUBMIT: 'submit',
116
+ TRIGGER: 'trigger',
117
+ VALID: 'valid',
106
118
  };
107
119
  const VALIDATION_MODE = {
108
120
  onBlur: 'onBlur',
@@ -120,80 +132,22 @@ const INPUT_VALIDATION_RULES = {
120
132
  required: 'required',
121
133
  validate: 'validate',
122
134
  };
135
+ const FORM_ERROR_TYPE = 'form';
136
+ const ROOT_ERROR_TYPE = 'root';
123
137
 
124
- const HookFormContext = React.createContext(null);
125
- HookFormContext.displayName = 'HookFormContext';
126
138
  /**
127
- * 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}.
128
- *
129
- * @remarks
130
- * [API](https://react-hook-form.com/docs/useformcontext) • [Demo](https://codesandbox.io/s/react-hook-form-v7-form-context-ytudi)
131
- *
132
- * @returns return all useForm methods
133
- *
134
- * @example
135
- * ```tsx
136
- * function App() {
137
- * const methods = useForm();
138
- * const onSubmit = data => console.log(data);
139
- *
140
- * return (
141
- * <FormProvider {...methods} >
142
- * <form onSubmit={methods.handleSubmit(onSubmit)}>
143
- * <NestedInput />
144
- * <input type="submit" />
145
- * </form>
146
- * </FormProvider>
147
- * );
148
- * }
149
- *
150
- * function NestedInput() {
151
- * const { register } = useFormContext(); // retrieve all hook methods
152
- * return <input {...register("test")} />;
153
- * }
154
- * ```
139
+ * Separate context for `control` to prevent unnecessary rerenders.
140
+ * Internal hooks that only need control use this instead of full form context.
155
141
  */
156
- const useFormContext = () => React.useContext(HookFormContext);
142
+ const HookFormControlContext = React.createContext(null);
143
+ HookFormControlContext.displayName = 'HookFormControlContext';
157
144
  /**
158
- * A provider component that propagates the `useForm` methods to all children components via [React Context](https://react.dev/reference/react/useContext) API. To be used with {@link useFormContext}.
159
- *
160
- * @remarks
161
- * [API](https://react-hook-form.com/docs/useformcontext) • [Demo](https://codesandbox.io/s/react-hook-form-v7-form-context-ytudi)
162
- *
163
- * @param props - all useForm methods
164
- *
165
- * @example
166
- * ```tsx
167
- * function App() {
168
- * const methods = useForm();
169
- * const onSubmit = data => console.log(data);
170
- *
171
- * return (
172
- * <FormProvider {...methods} >
173
- * <form onSubmit={methods.handleSubmit(onSubmit)}>
174
- * <NestedInput />
175
- * <input type="submit" />
176
- * </form>
177
- * </FormProvider>
178
- * );
179
- * }
180
- *
181
- * function NestedInput() {
182
- * const { register } = useFormContext(); // retrieve all hook methods
183
- * return <input {...register("test")} />;
184
- * }
185
- * ```
145
+ * @internal Internal hook to access only control from context.
186
146
  */
187
- const FormProvider = (props) => {
188
- const { children, ...data } = props;
189
- return (React.createElement(HookFormContext.Provider, { value: data }, children));
190
- };
147
+ const useFormControlContext = () => React.useContext(HookFormControlContext);
191
148
 
192
149
  var getProxyFormState = (formState, control, localProxyFormState, isRoot = true) => {
193
- const result = {
194
- ...formState,
195
- defaultValues: control._defaultValues,
196
- };
150
+ const result = {};
197
151
  for (const key in formState) {
198
152
  Object.defineProperty(result, key, {
199
153
  get: () => {
@@ -242,9 +196,12 @@ const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? React.useLayou
242
196
  * ```
243
197
  */
244
198
  function useFormState(props) {
245
- const methods = useFormContext();
246
- const { control = methods.control, disabled, name, exact } = props || {};
247
- const [formState, updateFormState] = React.useState(control._formState);
199
+ const formControl = useFormControlContext();
200
+ const { control = formControl, disabled, name, exact } = props || {};
201
+ const [formState, updateFormState] = React.useState(() => ({
202
+ ...control._formState,
203
+ defaultValues: control._defaultValues,
204
+ }));
248
205
  const _localProxyFormState = React.useRef({
249
206
  isDirty: false,
250
207
  isLoading: false,
@@ -264,6 +221,7 @@ function useFormState(props) {
264
221
  updateFormState({
265
222
  ...control._formState,
266
223
  ...formState,
224
+ defaultValues: control._defaultValues,
267
225
  });
268
226
  },
269
227
  }), [name, disabled, exact]);
@@ -290,34 +248,37 @@ var generateWatchOutput = (names, _names, formValues, isGlobal, defaultValue) =>
290
248
 
291
249
  var isPrimitive = (value) => isNullOrUndefined(value) || !isObjectType(value);
292
250
 
293
- function deepEqual(object1, object2, _internal_visited = new WeakSet()) {
251
+ function deepEqual(object1, object2, visited = new WeakSet()) {
252
+ if (object1 === object2) {
253
+ return true;
254
+ }
294
255
  if (isPrimitive(object1) || isPrimitive(object2)) {
295
256
  return Object.is(object1, object2);
296
257
  }
297
258
  if (isDateObject(object1) && isDateObject(object2)) {
298
- return object1.getTime() === object2.getTime();
259
+ return Object.is(object1.getTime(), object2.getTime());
299
260
  }
300
261
  const keys1 = Object.keys(object1);
301
262
  const keys2 = Object.keys(object2);
302
263
  if (keys1.length !== keys2.length) {
303
264
  return false;
304
265
  }
305
- if (_internal_visited.has(object1) || _internal_visited.has(object2)) {
266
+ if (visited.has(object1) || visited.has(object2)) {
306
267
  return true;
307
268
  }
308
- _internal_visited.add(object1);
309
- _internal_visited.add(object2);
269
+ visited.add(object1);
270
+ visited.add(object2);
310
271
  for (const key of keys1) {
311
272
  const val1 = object1[key];
312
- if (!keys2.includes(key)) {
273
+ if (!(key in object2)) {
313
274
  return false;
314
275
  }
315
276
  if (key !== 'ref') {
316
277
  const val2 = object2[key];
317
278
  if ((isDateObject(val1) && isDateObject(val2)) ||
318
- (isObject(val1) && isObject(val2)) ||
319
- (Array.isArray(val1) && Array.isArray(val2))
320
- ? !deepEqual(val1, val2, _internal_visited)
279
+ ((isObject(val1) || Array.isArray(val1)) &&
280
+ (isObject(val2) || Array.isArray(val2)))
281
+ ? !deepEqual(val1, val2, visited)
321
282
  : !Object.is(val1, val2)) {
322
283
  return false;
323
284
  }
@@ -343,8 +304,8 @@ function deepEqual(object1, object2, _internal_visited = new WeakSet()) {
343
304
  * ```
344
305
  */
345
306
  function useWatch(props) {
346
- const methods = useFormContext();
347
- const { control = methods.control, name, defaultValue, disabled, exact, compute, } = props || {};
307
+ const formControl = useFormControlContext();
308
+ const { control = formControl, name, defaultValue, disabled, exact, compute, } = props || {};
348
309
  const _defaultValue = React.useRef(defaultValue);
349
310
  const _compute = React.useRef(compute);
350
311
  const _computeFormValues = React.useRef(undefined);
@@ -437,9 +398,10 @@ function useWatch(props) {
437
398
  * ```
438
399
  */
439
400
  function useController(props) {
440
- const methods = useFormContext();
441
- const { name, disabled, control = methods.control, shouldUnregister, defaultValue, exact = true, } = props;
442
- const isArrayField = isNameInFieldArray(control._names.array, name);
401
+ const formControl = useFormControlContext();
402
+ const { name, disabled, control = formControl, shouldUnregister, defaultValue, exact = true, } = props;
403
+ const isArrayField = !!getFieldArrayParentNames(control._names.array, name)
404
+ .length;
443
405
  const defaultValueMemo = React.useMemo(() => get(control._formValues, name, get(control._defaultValues, name, defaultValue)), [control, name, defaultValue]);
444
406
  const value = useWatch({
445
407
  control,
@@ -453,7 +415,6 @@ function useController(props) {
453
415
  exact,
454
416
  });
455
417
  const _props = React.useRef(props);
456
- const _previousNameRef = React.useRef(undefined);
457
418
  const _registerProps = React.useRef(control.register(name, {
458
419
  ...props.rules,
459
420
  value,
@@ -498,13 +459,8 @@ function useController(props) {
498
459
  }), [name, control._formValues]);
499
460
  const ref = React.useCallback((elm) => {
500
461
  const field = get(control._fields, name);
501
- if (field && elm) {
502
- field._f.ref = {
503
- focus: () => elm.focus && elm.focus(),
504
- select: () => elm.select && elm.select(),
505
- setCustomValidity: (message) => elm.setCustomValidity(message),
506
- reportValidity: () => elm.reportValidity(),
507
- };
462
+ if (field && field._f && elm) {
463
+ field._f.ref = elm;
508
464
  }
509
465
  }, [control._fields, name]);
510
466
  const field = React.useMemo(() => ({
@@ -519,10 +475,6 @@ function useController(props) {
519
475
  }), [name, disabled, formState.disabled, onChange, onBlur, ref, value]);
520
476
  React.useEffect(() => {
521
477
  const _shouldUnregisterField = control._options.shouldUnregister || shouldUnregister;
522
- const previousName = _previousNameRef.current;
523
- if (previousName && previousName !== name && !isArrayField) {
524
- control.unregister(previousName);
525
- }
526
478
  control.register(name, {
527
479
  ..._props.current.rules,
528
480
  ...(isBoolean(_props.current.disabled)
@@ -537,14 +489,13 @@ function useController(props) {
537
489
  };
538
490
  updateMounted(name, true);
539
491
  if (_shouldUnregisterField) {
540
- const value = cloneObject(get(control._options.defaultValues, name, _props.current.defaultValue));
492
+ const value = cloneObject(get(control._defaultValues, name, get(control._options.defaultValues, name, _props.current.defaultValue)));
541
493
  set(control._defaultValues, name, value);
542
494
  if (isUndefined(get(control._formValues, name))) {
543
495
  set(control._formValues, name, value);
544
496
  }
545
497
  }
546
498
  !isArrayField && control.register(name);
547
- _previousNameRef.current = name;
548
499
  return () => {
549
500
  (isArrayField
550
501
  ? _shouldUnregisterField && !control._state.action
@@ -626,6 +577,112 @@ const flatten = (obj) => {
626
577
  return output;
627
578
  };
628
579
 
580
+ const HookFormContext = React.createContext(null);
581
+ HookFormContext.displayName = 'HookFormContext';
582
+ /**
583
+ * 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}.
584
+ *
585
+ * @remarks
586
+ * [API](https://react-hook-form.com/docs/useformcontext) • [Demo](https://codesandbox.io/s/react-hook-form-v7-form-context-ytudi)
587
+ *
588
+ * @returns return all useForm methods
589
+ *
590
+ * @example
591
+ * ```tsx
592
+ * function App() {
593
+ * const methods = useForm();
594
+ * const onSubmit = data => console.log(data);
595
+ *
596
+ * return (
597
+ * <FormProvider {...methods} >
598
+ * <form onSubmit={methods.handleSubmit(onSubmit)}>
599
+ * <NestedInput />
600
+ * <input type="submit" />
601
+ * </form>
602
+ * </FormProvider>
603
+ * );
604
+ * }
605
+ *
606
+ * function NestedInput() {
607
+ * const { register } = useFormContext(); // retrieve all hook methods
608
+ * return <input {...register("test")} />;
609
+ * }
610
+ * ```
611
+ */
612
+ const useFormContext = () => React.useContext(HookFormContext);
613
+ /**
614
+ * A provider component that propagates the `useForm` methods to all children components via [React Context](https://react.dev/reference/react/useContext) API. To be used with {@link useFormContext}.
615
+ *
616
+ * @remarks
617
+ * [API](https://react-hook-form.com/docs/useformcontext) • [Demo](https://codesandbox.io/s/react-hook-form-v7-form-context-ytudi)
618
+ *
619
+ * @param props - all useForm methods
620
+ *
621
+ * @example
622
+ * ```tsx
623
+ * function App() {
624
+ * const methods = useForm();
625
+ * const onSubmit = data => console.log(data);
626
+ *
627
+ * return (
628
+ * <FormProvider {...methods} >
629
+ * <form onSubmit={methods.handleSubmit(onSubmit)}>
630
+ * <NestedInput />
631
+ * <input type="submit" />
632
+ * </form>
633
+ * </FormProvider>
634
+ * );
635
+ * }
636
+ *
637
+ * function NestedInput() {
638
+ * const { register } = useFormContext(); // retrieve all hook methods
639
+ * return <input {...register("test")} />;
640
+ * }
641
+ * ```
642
+ */
643
+ const FormProvider = (props) => {
644
+ const { children, watch, getValues, getFieldState, setError, clearErrors, setValue, setValues, trigger, formState, resetField, reset, handleSubmit, unregister, control, register, setFocus, subscribe, } = props;
645
+ const memoizedValue = React.useMemo(() => ({
646
+ watch,
647
+ getValues,
648
+ getFieldState,
649
+ setError,
650
+ clearErrors,
651
+ setValue,
652
+ setValues,
653
+ trigger,
654
+ formState,
655
+ resetField,
656
+ reset,
657
+ handleSubmit,
658
+ unregister,
659
+ control,
660
+ register,
661
+ setFocus,
662
+ subscribe,
663
+ }), [
664
+ clearErrors,
665
+ control,
666
+ formState,
667
+ getFieldState,
668
+ getValues,
669
+ handleSubmit,
670
+ register,
671
+ reset,
672
+ resetField,
673
+ setError,
674
+ setFocus,
675
+ setValue,
676
+ setValues,
677
+ subscribe,
678
+ trigger,
679
+ unregister,
680
+ watch,
681
+ ]);
682
+ return (React.createElement(HookFormContext.Provider, { value: memoizedValue },
683
+ React.createElement(HookFormControlContext.Provider, { value: memoizedValue.control }, children)));
684
+ };
685
+
629
686
  const POST_REQUEST = 'post';
630
687
  /**
631
688
  * Form component to manage submission.
@@ -653,7 +710,7 @@ function Form(props) {
653
710
  const methods = useFormContext();
654
711
  const [mounted, setMounted] = React.useState(false);
655
712
  const { control = methods.control, onSubmit, children, action, method = POST_REQUEST, headers, encType, onError, render, onSuccess, validateStatus, ...rest } = props;
656
- const submit = async (event) => {
713
+ const submit = React.useCallback(async (event) => {
657
714
  let hasError = false;
658
715
  let type = '';
659
716
  await control.handleSubmit(async (data) => {
@@ -662,8 +719,8 @@ function Form(props) {
662
719
  try {
663
720
  formDataJson = JSON.stringify(data);
664
721
  }
665
- catch (_a) { }
666
- const flattenFormValues = flatten(control._formValues);
722
+ catch { }
723
+ const flattenFormValues = flatten(data);
667
724
  for (const key in flattenFormValues) {
668
725
  formData.append(key, flattenFormValues[key]);
669
726
  }
@@ -710,15 +767,25 @@ function Form(props) {
710
767
  }
711
768
  }
712
769
  })(event);
713
- if (hasError && props.control) {
714
- props.control._subjects.state.next({
770
+ if (hasError && control) {
771
+ control._subjects.state.next({
715
772
  isSubmitSuccessful: false,
716
773
  });
717
- props.control.setError('root.server', {
774
+ control.setError('root.server', {
718
775
  type,
719
776
  });
720
777
  }
721
- };
778
+ }, [
779
+ control,
780
+ onSubmit,
781
+ method,
782
+ action,
783
+ headers,
784
+ encType,
785
+ validateStatus,
786
+ onError,
787
+ onSuccess,
788
+ ]);
722
789
  React.useEffect(() => {
723
790
  setMounted(true);
724
791
  }, []);
@@ -816,7 +883,12 @@ function baseGet(object, updatePath) {
816
883
  const length = updatePath.slice(0, -1).length;
817
884
  let index = 0;
818
885
  while (index < length) {
819
- object = isUndefined(object) ? index++ : object[updatePath[index++]];
886
+ if (isNullOrUndefined(object)) {
887
+ object = undefined;
888
+ break;
889
+ }
890
+ object = object[updatePath[index]];
891
+ index++;
820
892
  }
821
893
  return object;
822
894
  }
@@ -829,6 +901,10 @@ function isEmptyArray(obj) {
829
901
  return true;
830
902
  }
831
903
  function unset(object, path) {
904
+ if (isString(path) && Object.prototype.hasOwnProperty.call(object, path)) {
905
+ delete object[path];
906
+ return object;
907
+ }
832
908
  const paths = Array.isArray(path)
833
909
  ? path
834
910
  : isKey(path)
@@ -873,6 +949,29 @@ function markFieldsDirty(data, fields = {}) {
873
949
  }
874
950
  return fields;
875
951
  }
952
+ function pruneDirtyFields(value) {
953
+ if (value === false) {
954
+ return undefined;
955
+ }
956
+ if (value === true) {
957
+ return true;
958
+ }
959
+ if (Array.isArray(value)) {
960
+ const result = value.map((value) => pruneDirtyFields(value));
961
+ return (result.some((value) => value !== undefined) ? result : undefined);
962
+ }
963
+ if (isObject(value)) {
964
+ const result = {};
965
+ for (const key in value) {
966
+ const pruned = pruneDirtyFields(value[key]);
967
+ if (!isUndefined(pruned)) {
968
+ result[key] = pruned;
969
+ }
970
+ }
971
+ return (Object.keys(result).length ? result : undefined);
972
+ }
973
+ return undefined;
974
+ }
876
975
  function getDirtyFields(data, formValues, dirtyFieldsFromValues) {
877
976
  if (!dirtyFieldsFromValues) {
878
977
  dirtyFieldsFromValues = markFieldsDirty(formValues);
@@ -892,7 +991,7 @@ function getDirtyFields(data, formValues, dirtyFieldsFromValues) {
892
991
  dirtyFieldsFromValues[key] = !deepEqual(value, formValue);
893
992
  }
894
993
  }
895
- return dirtyFieldsFromValues;
994
+ return pruneDirtyFields(dirtyFieldsFromValues) || {};
896
995
  }
897
996
 
898
997
  const defaultResult = {
@@ -1088,7 +1187,8 @@ var shouldRenderFormState = (formStateData, _proxyFormState, updateFormState, is
1088
1187
  updateFormState(formStateData);
1089
1188
  const { name, ...formState } = formStateData;
1090
1189
  return (isEmptyObject(formState) ||
1091
- Object.keys(formState).length >= Object.keys(_proxyFormState).length ||
1190
+ (isRoot &&
1191
+ Object.keys(formState).length >= Object.keys(_proxyFormState).length) ||
1092
1192
  Object.keys(formState).find((key) => _proxyFormState[key] ===
1093
1193
  (!isRoot || VALIDATION_MODE.all)));
1094
1194
  };
@@ -1122,7 +1222,7 @@ var unsetEmptyArray = (ref, name) => !compact(get(ref, name)).length && unset(re
1122
1222
 
1123
1223
  var updateFieldArrayRootError = (errors, error, name) => {
1124
1224
  const fieldArrayErrors = convertToArrayPayload(get(errors, name));
1125
- set(fieldArrayErrors, 'root', error[name]);
1225
+ set(fieldArrayErrors, ROOT_ERROR_TYPE, error[name]);
1126
1226
  set(errors, name, fieldArrayErrors);
1127
1227
  return errors;
1128
1228
  };
@@ -1168,7 +1268,8 @@ var validateField = async (field, disabledFieldNames, formValues, validateAllFie
1168
1268
  isUndefined(inputValue)) ||
1169
1269
  (isHTMLElement(ref) && ref.value === '') ||
1170
1270
  inputValue === '' ||
1171
- (Array.isArray(inputValue) && !inputValue.length);
1271
+ (Array.isArray(inputValue) && !inputValue.length) ||
1272
+ (valueAsNumber && typeof inputValue === 'number' && isNaN(inputValue));
1172
1273
  const appendErrorsCurry = appendErrors.bind(null, name, validateAllFieldCriteria, error);
1173
1274
  const getMinMaxMessage = (exceedMax, maxLengthMessage, minLengthMessage, maxType = INPUT_VALIDATION_RULES.maxLength, minType = INPUT_VALIDATION_RULES.minLength) => {
1174
1275
  const message = exceedMax ? maxLengthMessage : minLengthMessage;
@@ -1330,24 +1431,27 @@ const defaultOptions = {
1330
1431
  reValidateMode: VALIDATION_MODE.onChange,
1331
1432
  shouldFocusError: true,
1332
1433
  };
1434
+ const DEFAULT_FORM_STATE = {
1435
+ submitCount: 0,
1436
+ isDirty: false,
1437
+ isReady: false,
1438
+ isValidating: false,
1439
+ isSubmitted: false,
1440
+ isSubmitting: false,
1441
+ isSubmitSuccessful: false,
1442
+ isValid: false,
1443
+ touchedFields: {},
1444
+ dirtyFields: {},
1445
+ validatingFields: {},
1446
+ };
1333
1447
  function createFormControl(props = {}) {
1334
1448
  let _options = {
1335
1449
  ...defaultOptions,
1336
1450
  ...props,
1337
1451
  };
1338
1452
  let _formState = {
1339
- submitCount: 0,
1340
- isDirty: false,
1341
- isReady: false,
1453
+ ...cloneObject(DEFAULT_FORM_STATE),
1342
1454
  isLoading: isFunction(_options.defaultValues),
1343
- isValidating: false,
1344
- isSubmitted: false,
1345
- isSubmitting: false,
1346
- isSubmitSuccessful: false,
1347
- isValid: false,
1348
- touchedFields: {},
1349
- dirtyFields: {},
1350
- validatingFields: {},
1351
1455
  errors: _options.errors || {},
1352
1456
  disabled: _options.disabled || false,
1353
1457
  };
@@ -1362,6 +1466,7 @@ function createFormControl(props = {}) {
1362
1466
  action: false,
1363
1467
  mount: false,
1364
1468
  watch: false,
1469
+ keepIsValid: false,
1365
1470
  };
1366
1471
  let _names = {
1367
1472
  mount: new Set(),
@@ -1369,10 +1474,11 @@ function createFormControl(props = {}) {
1369
1474
  unMount: new Set(),
1370
1475
  array: new Set(),
1371
1476
  watch: new Set(),
1477
+ registerName: new Set(),
1372
1478
  };
1373
1479
  let delayErrorCallback;
1374
1480
  let timer = 0;
1375
- const _proxyFormState = {
1481
+ const defaultProxyFormState = {
1376
1482
  isDirty: false,
1377
1483
  dirtyFields: false,
1378
1484
  validatingFields: false,
@@ -1381,6 +1487,9 @@ function createFormControl(props = {}) {
1381
1487
  isValid: false,
1382
1488
  errors: false,
1383
1489
  };
1490
+ const _proxyFormState = {
1491
+ ...defaultProxyFormState,
1492
+ };
1384
1493
  let _proxySubscribeFormState = {
1385
1494
  ..._proxyFormState,
1386
1495
  };
@@ -1394,13 +1503,25 @@ function createFormControl(props = {}) {
1394
1503
  timer = setTimeout(callback, wait);
1395
1504
  };
1396
1505
  const _setValid = async (shouldUpdateValid) => {
1506
+ if (_state.keepIsValid) {
1507
+ return;
1508
+ }
1397
1509
  if (!_options.disabled &&
1398
1510
  (_proxyFormState.isValid ||
1399
1511
  _proxySubscribeFormState.isValid ||
1400
1512
  shouldUpdateValid)) {
1401
- const isValid = _options.resolver
1402
- ? isEmptyObject((await _runSchema()).errors)
1403
- : await executeBuiltInValidation(_fields, true);
1513
+ let isValid;
1514
+ if (_options.resolver) {
1515
+ isValid = isEmptyObject((await _runSchema()).errors);
1516
+ _updateIsValidating();
1517
+ }
1518
+ else {
1519
+ isValid = await executeBuiltInValidation({
1520
+ fields: _fields,
1521
+ onlyCheckValid: true,
1522
+ eventType: EVENTS.VALID,
1523
+ });
1524
+ }
1404
1525
  if (isValid !== _formState.isValid) {
1405
1526
  _subjects.state.next({
1406
1527
  isValid,
@@ -1427,6 +1548,9 @@ function createFormControl(props = {}) {
1427
1548
  });
1428
1549
  }
1429
1550
  };
1551
+ const _updateDirtyFields = () => {
1552
+ _formState.dirtyFields = getDirtyFields(_defaultValues, _formValues);
1553
+ };
1430
1554
  const _setFieldArray = (name, values = [], method, args, shouldSetValues = true, shouldUpdateFieldsAndState = true) => {
1431
1555
  if (args && method && !_options.disabled) {
1432
1556
  _state.action = true;
@@ -1448,7 +1572,7 @@ function createFormControl(props = {}) {
1448
1572
  shouldSetValues && set(_formState.touchedFields, name, touchedFields);
1449
1573
  }
1450
1574
  if (_proxyFormState.dirtyFields || _proxySubscribeFormState.dirtyFields) {
1451
- _formState.dirtyFields = getDirtyFields(_defaultValues, _formValues);
1575
+ _updateDirtyFields();
1452
1576
  }
1453
1577
  _subjects.state.next({
1454
1578
  name,
@@ -1475,16 +1599,53 @@ function createFormControl(props = {}) {
1475
1599
  isValid: false,
1476
1600
  });
1477
1601
  };
1602
+ const hasExplicitNullIntermediate = (name) => {
1603
+ const segments = isKey(name) ? [name] : stringToPath(name);
1604
+ let formValues = _formValues;
1605
+ let defaultValues = _defaultValues;
1606
+ for (let i = 0; i < segments.length - 1; i++) {
1607
+ const key = segments[i];
1608
+ formValues = isNullOrUndefined(formValues) ? formValues : formValues[key];
1609
+ defaultValues = isNullOrUndefined(defaultValues)
1610
+ ? defaultValues
1611
+ : defaultValues[key];
1612
+ if (formValues === null && defaultValues !== null) {
1613
+ return true;
1614
+ }
1615
+ }
1616
+ return false;
1617
+ };
1478
1618
  const updateValidAndValue = (name, shouldSkipSetValueAs, value, ref) => {
1479
1619
  const field = get(_fields, name);
1480
1620
  if (field) {
1621
+ if (hasExplicitNullIntermediate(name)) {
1622
+ return;
1623
+ }
1624
+ const wasUnsetInFormValues = isUndefined(get(_formValues, name));
1481
1625
  const defaultValue = get(_formValues, name, isUndefined(value) ? get(_defaultValues, name) : value);
1482
1626
  isUndefined(defaultValue) ||
1483
1627
  (ref && ref.defaultChecked) ||
1484
1628
  shouldSkipSetValueAs
1485
1629
  ? set(_formValues, name, shouldSkipSetValueAs ? defaultValue : getFieldValue(field._f))
1486
1630
  : setFieldValue(name, defaultValue);
1487
- _state.mount && !_state.action && _setValid();
1631
+ if (_state.mount && !_state.action) {
1632
+ _setValid();
1633
+ // Re-registering a field after a prior unregister puts its key back
1634
+ // into _formValues, which can flip isDirty back to false (#13397).
1635
+ // Only run when we are currently dirty, otherwise an initial register
1636
+ // for a field with no defaultValue would flip isDirty to true. Reset
1637
+ // paths repopulate _formValues before re-register, so the key is
1638
+ // present then and this branch is skipped (preserves keepDirty).
1639
+ if (wasUnsetInFormValues &&
1640
+ _formState.isDirty &&
1641
+ (_proxyFormState.isDirty || _proxySubscribeFormState.isDirty)) {
1642
+ const isDirty = _getDirty();
1643
+ if (!isDirty) {
1644
+ _formState.isDirty = false;
1645
+ _subjects.state.next({ ..._formState });
1646
+ }
1647
+ }
1648
+ }
1488
1649
  }
1489
1650
  };
1490
1651
  const updateTouchAndDirty = (name, fieldValue, isBlurEvent, shouldDirty, shouldRender) => {
@@ -1502,9 +1663,14 @@ function createFormControl(props = {}) {
1502
1663
  }
1503
1664
  const isCurrentFieldPristine = deepEqual(get(_defaultValues, name), fieldValue);
1504
1665
  isPreviousDirty = !!get(_formState.dirtyFields, name);
1505
- isCurrentFieldPristine
1506
- ? unset(_formState.dirtyFields, name)
1507
- : set(_formState.dirtyFields, name, true);
1666
+ if (isCurrentFieldPristine !== _formState.isDirty) {
1667
+ _formState.dirtyFields = getDirtyFields(_defaultValues, _formValues);
1668
+ }
1669
+ else {
1670
+ isCurrentFieldPristine
1671
+ ? unset(_formState.dirtyFields, name)
1672
+ : set(_formState.dirtyFields, name, true);
1673
+ }
1508
1674
  output.dirtyFields = _formState.dirtyFields;
1509
1675
  shouldUpdateField =
1510
1676
  shouldUpdateField ||
@@ -1562,17 +1728,18 @@ function createFormControl(props = {}) {
1562
1728
  };
1563
1729
  const _runSchema = async (name) => {
1564
1730
  _updateIsValidating(name, true);
1565
- const result = await _options.resolver(_formValues, _options.context, getResolverOptions(name || _names.mount, _fields, _options.criteriaMode, _options.shouldUseNativeValidation));
1566
- _updateIsValidating(name);
1567
- return result;
1731
+ return await _options.resolver(_formValues, _options.context, getResolverOptions(name || _names.mount, _fields, _options.criteriaMode, _options.shouldUseNativeValidation));
1568
1732
  };
1569
1733
  const executeSchemaAndUpdateState = async (names) => {
1570
1734
  const { errors } = await _runSchema(names);
1735
+ _updateIsValidating(names);
1571
1736
  if (names) {
1572
1737
  for (const name of names) {
1573
1738
  const error = get(errors, name);
1574
1739
  error
1575
- ? set(_formState.errors, name, error)
1740
+ ? _names.array.has(name) && isObject(error)
1741
+ ? updateFieldArrayRootError(_formState.errors, { [name]: error }, name)
1742
+ : set(_formState.errors, name, error)
1576
1743
  : unset(_formState.errors, name);
1577
1744
  }
1578
1745
  }
@@ -1581,9 +1748,55 @@ function createFormControl(props = {}) {
1581
1748
  }
1582
1749
  return errors;
1583
1750
  };
1584
- const executeBuiltInValidation = async (fields, shouldOnlyCheckValid, context = {
1751
+ const validateForm = async ({ name, eventType, }) => {
1752
+ if (props.validate) {
1753
+ const result = await props.validate({
1754
+ formValues: _formValues,
1755
+ formState: _formState,
1756
+ name,
1757
+ eventType,
1758
+ });
1759
+ if (isObject(result)) {
1760
+ for (const key in result) {
1761
+ const error = result[key];
1762
+ if (error) {
1763
+ setError(`${FORM_ERROR_TYPE}.${key}`, {
1764
+ message: isString(error.message) ? error.message : '',
1765
+ type: error.type || INPUT_VALIDATION_RULES.validate,
1766
+ });
1767
+ }
1768
+ }
1769
+ }
1770
+ else if (isString(result) || !result) {
1771
+ setError(FORM_ERROR_TYPE, {
1772
+ message: result || '',
1773
+ type: INPUT_VALIDATION_RULES.validate,
1774
+ });
1775
+ }
1776
+ else {
1777
+ clearErrors(FORM_ERROR_TYPE);
1778
+ }
1779
+ return result;
1780
+ }
1781
+ return true;
1782
+ };
1783
+ const executeBuiltInValidation = async ({ fields, onlyCheckValid, name, eventType, context = {
1585
1784
  valid: true,
1586
- }) => {
1785
+ runRootValidation: false,
1786
+ }, }) => {
1787
+ if (props.validate) {
1788
+ context.runRootValidation = true;
1789
+ const result = await validateForm({
1790
+ name,
1791
+ eventType,
1792
+ });
1793
+ if (!result) {
1794
+ context.valid = false;
1795
+ if (onlyCheckValid) {
1796
+ return context.valid;
1797
+ }
1798
+ }
1799
+ }
1587
1800
  for (const name in fields) {
1588
1801
  const field = fields[name];
1589
1802
  if (field) {
@@ -1591,28 +1804,41 @@ function createFormControl(props = {}) {
1591
1804
  if (_f) {
1592
1805
  const isFieldArrayRoot = _names.array.has(_f.name);
1593
1806
  const isPromiseFunction = field._f && hasPromiseValidation(field._f);
1594
- if (isPromiseFunction && _proxyFormState.validatingFields) {
1807
+ const shouldTrackIsValidatingState = _proxyFormState.validatingFields ||
1808
+ _proxyFormState.isValidating ||
1809
+ _proxySubscribeFormState.validatingFields ||
1810
+ _proxySubscribeFormState.isValidating;
1811
+ if (isPromiseFunction && shouldTrackIsValidatingState) {
1595
1812
  _updateIsValidating([_f.name], true);
1596
1813
  }
1597
- const fieldError = await validateField(field, _names.disabled, _formValues, shouldDisplayAllAssociatedErrors, _options.shouldUseNativeValidation && !shouldOnlyCheckValid, isFieldArrayRoot);
1598
- if (isPromiseFunction && _proxyFormState.validatingFields) {
1814
+ const fieldError = await validateField(field, _names.disabled, _formValues, shouldDisplayAllAssociatedErrors, _options.shouldUseNativeValidation && !onlyCheckValid, isFieldArrayRoot);
1815
+ if (isPromiseFunction && shouldTrackIsValidatingState) {
1599
1816
  _updateIsValidating([_f.name]);
1600
1817
  }
1601
1818
  if (fieldError[_f.name]) {
1602
1819
  context.valid = false;
1603
- if (shouldOnlyCheckValid) {
1820
+ if (onlyCheckValid) {
1604
1821
  break;
1605
1822
  }
1606
1823
  }
1607
- !shouldOnlyCheckValid &&
1824
+ !onlyCheckValid &&
1608
1825
  (get(fieldError, _f.name)
1609
1826
  ? isFieldArrayRoot
1610
1827
  ? updateFieldArrayRootError(_formState.errors, fieldError, _f.name)
1611
1828
  : set(_formState.errors, _f.name, fieldError[_f.name])
1612
1829
  : unset(_formState.errors, _f.name));
1830
+ if (props.shouldUseNativeValidation && fieldError[_f.name]) {
1831
+ break;
1832
+ }
1613
1833
  }
1614
1834
  !isEmptyObject(fieldValue) &&
1615
- (await executeBuiltInValidation(fieldValue, shouldOnlyCheckValid, context));
1835
+ (await executeBuiltInValidation({
1836
+ context,
1837
+ onlyCheckValid,
1838
+ fields: fieldValue,
1839
+ name: name,
1840
+ eventType,
1841
+ }));
1616
1842
  }
1617
1843
  }
1618
1844
  return context.valid;
@@ -1692,7 +1918,7 @@ function createFormControl(props = {}) {
1692
1918
  updateTouchAndDirty(name, fieldValue, options.shouldTouch, options.shouldDirty, true);
1693
1919
  options.shouldValidate && trigger(name);
1694
1920
  };
1695
- const setValues = (name, value, options) => {
1921
+ const setFieldValues = (name, value, options) => {
1696
1922
  for (const fieldKey in value) {
1697
1923
  if (!value.hasOwnProperty(fieldKey)) {
1698
1924
  return;
@@ -1704,7 +1930,7 @@ function createFormControl(props = {}) {
1704
1930
  isObject(fieldValue) ||
1705
1931
  (field && !field._f)) &&
1706
1932
  !isDateObject(fieldValue)
1707
- ? setValues(fieldName, fieldValue, options)
1933
+ ? setFieldValues(fieldName, fieldValue, options)
1708
1934
  : setFieldValue(fieldName, fieldValue, options);
1709
1935
  }
1710
1936
  };
@@ -1712,7 +1938,11 @@ function createFormControl(props = {}) {
1712
1938
  const field = get(_fields, name);
1713
1939
  const isFieldArray = _names.array.has(name);
1714
1940
  const cloneValue = cloneObject(value);
1715
- set(_formValues, name, cloneValue);
1941
+ const previousValue = get(_formValues, name);
1942
+ const isValueUnchanged = deepEqual(previousValue, cloneValue);
1943
+ if (!isValueUnchanged) {
1944
+ set(_formValues, name, cloneValue);
1945
+ }
1716
1946
  if (isFieldArray) {
1717
1947
  _subjects.array.next({
1718
1948
  name,
@@ -1723,23 +1953,53 @@ function createFormControl(props = {}) {
1723
1953
  _proxySubscribeFormState.isDirty ||
1724
1954
  _proxySubscribeFormState.dirtyFields) &&
1725
1955
  options.shouldDirty) {
1956
+ _updateDirtyFields();
1726
1957
  _subjects.state.next({
1727
1958
  name,
1728
- dirtyFields: getDirtyFields(_defaultValues, _formValues),
1959
+ dirtyFields: _formState.dirtyFields,
1729
1960
  isDirty: _getDirty(name, cloneValue),
1730
1961
  });
1731
1962
  }
1732
1963
  }
1733
1964
  else {
1734
- field && !field._f && !isNullOrUndefined(cloneValue)
1735
- ? setValues(name, cloneValue, options)
1736
- : setFieldValue(name, cloneValue, options);
1965
+ const isEmpty = (Array.isArray(cloneValue) && !cloneValue.length) ||
1966
+ isEmptyObject(cloneValue);
1967
+ if (!field || field._f || isNullOrUndefined(cloneValue) || isEmpty) {
1968
+ setFieldValue(name, cloneValue, options);
1969
+ }
1970
+ else {
1971
+ setFieldValues(name, cloneValue, options);
1972
+ }
1973
+ }
1974
+ if (!isValueUnchanged) {
1975
+ const watched = isWatched(name, _names);
1976
+ const values = cloneObject(_formValues);
1977
+ if (!isFieldArray) {
1978
+ for (const arrayName of getFieldArrayParentNames(_names.array, name)) {
1979
+ _subjects.array.next({ name: arrayName, values });
1980
+ }
1981
+ }
1982
+ _subjects.state.next({
1983
+ ...(watched && _formState),
1984
+ name: _state.mount || watched ? name : undefined,
1985
+ values,
1986
+ });
1987
+ }
1988
+ };
1989
+ const setValues = (formValues) => {
1990
+ const updatedFormValues = isFunction(formValues)
1991
+ ? formValues(_formValues)
1992
+ : formValues;
1993
+ if (!deepEqual(_formValues, updatedFormValues)) {
1994
+ _formValues = {
1995
+ ..._formValues,
1996
+ ...updatedFormValues,
1997
+ };
1998
+ for (const fieldName of _names.mount) {
1999
+ setValue(fieldName, get(updatedFormValues, fieldName));
2000
+ }
2001
+ _subjects.state.next({ ..._formState, values: _formValues });
1737
2002
  }
1738
- isWatched(name, _names) && _subjects.state.next({ ..._formState, name });
1739
- _subjects.state.next({
1740
- name: _state.mount ? name : undefined,
1741
- values: cloneObject(_formValues),
1742
- });
1743
2003
  };
1744
2004
  const onChange = async (event) => {
1745
2005
  _state.mount = true;
@@ -1763,6 +2023,7 @@ function createFormControl(props = {}) {
1763
2023
  : getEventValue(event);
1764
2024
  const isBlurEvent = event.type === EVENTS.BLUR || event.type === EVENTS.FOCUS_OUT;
1765
2025
  const shouldSkipValidation = (!hasValidation(field._f) &&
2026
+ !props.validate &&
1766
2027
  !_options.resolver &&
1767
2028
  !get(_formState.errors, name) &&
1768
2029
  !field._f.deps) ||
@@ -1800,9 +2061,16 @@ function createFormControl(props = {}) {
1800
2061
  return (shouldRender &&
1801
2062
  _subjects.state.next({ name, ...(watched ? {} : fieldState) }));
1802
2063
  }
2064
+ if (!_options.resolver && props.validate) {
2065
+ await validateForm({
2066
+ name: name,
2067
+ eventType: event.type,
2068
+ });
2069
+ }
1803
2070
  !isBlurEvent && watched && _subjects.state.next({ ..._formState });
1804
2071
  if (_options.resolver) {
1805
2072
  const { errors } = await _runSchema([name]);
2073
+ _updateIsValidating([name]);
1806
2074
  _updateIsFieldValueUpdated(fieldValue);
1807
2075
  if (isFieldValueUpdated) {
1808
2076
  const previousErrorLookupResult = schemaErrorLookup(_formState.errors, _fields, name);
@@ -1823,7 +2091,12 @@ function createFormControl(props = {}) {
1823
2091
  }
1824
2092
  else if (_proxyFormState.isValid ||
1825
2093
  _proxySubscribeFormState.isValid) {
1826
- isValid = await executeBuiltInValidation(_fields, true);
2094
+ isValid = await executeBuiltInValidation({
2095
+ fields: _fields,
2096
+ onlyCheckValid: true,
2097
+ name: name,
2098
+ eventType: event.type,
2099
+ });
1827
2100
  }
1828
2101
  }
1829
2102
  }
@@ -1856,12 +2129,19 @@ function createFormControl(props = {}) {
1856
2129
  else if (name) {
1857
2130
  validationResult = (await Promise.all(fieldNames.map(async (fieldName) => {
1858
2131
  const field = get(_fields, fieldName);
1859
- return await executeBuiltInValidation(field && field._f ? { [fieldName]: field } : field);
2132
+ return await executeBuiltInValidation({
2133
+ fields: field && field._f ? { [fieldName]: field } : field,
2134
+ eventType: EVENTS.TRIGGER,
2135
+ });
1860
2136
  }))).every(Boolean);
1861
2137
  !(!validationResult && !_formState.isValid) && _setValid();
1862
2138
  }
1863
2139
  else {
1864
- validationResult = isValid = await executeBuiltInValidation(_fields);
2140
+ validationResult = isValid = await executeBuiltInValidation({
2141
+ fields: _fields,
2142
+ name,
2143
+ eventType: EVENTS.TRIGGER,
2144
+ });
1865
2145
  }
1866
2146
  _subjects.state.next({
1867
2147
  ...(!isString(name) ||
@@ -1898,11 +2178,24 @@ function createFormControl(props = {}) {
1898
2178
  isTouched: !!get((formState || _formState).touchedFields, name),
1899
2179
  });
1900
2180
  const clearErrors = (name) => {
1901
- name &&
1902
- convertToArrayPayload(name).forEach((inputName) => unset(_formState.errors, inputName));
1903
- _subjects.state.next({
1904
- errors: name ? _formState.errors : {},
1905
- });
2181
+ const names = name ? convertToArrayPayload(name) : undefined;
2182
+ names?.forEach((inputName) => unset(_formState.errors, inputName));
2183
+ if (names) {
2184
+ // Emit for each cleared field with the field name so that
2185
+ // shouldSubscribeByName can filter and avoid broad re-renders
2186
+ names.forEach((inputName) => {
2187
+ _subjects.state.next({
2188
+ name: inputName,
2189
+ errors: _formState.errors,
2190
+ });
2191
+ });
2192
+ }
2193
+ else {
2194
+ // Clear all errors - emit without name to notify all subscribers
2195
+ _subjects.state.next({
2196
+ errors: {},
2197
+ });
2198
+ }
1906
2199
  };
1907
2200
  const setError = (name, error, options) => {
1908
2201
  const ref = (get(_fields, name, { _f: {} })._f || {}).ref;
@@ -1921,18 +2214,14 @@ function createFormControl(props = {}) {
1921
2214
  });
1922
2215
  options && options.shouldFocus && ref && ref.focus && ref.focus();
1923
2216
  };
1924
- const watch = (name, defaultValue) => isFunction(name)
1925
- ? _subjects.state.subscribe({
1926
- next: (payload) => 'values' in payload &&
1927
- name(_getWatch(undefined, defaultValue), payload),
1928
- })
1929
- : _getWatch(name, defaultValue, true);
2217
+ const watch = (name, defaultValue) => _getWatch(name, defaultValue, true);
1930
2218
  const _subscribe = (props) => _subjects.state.subscribe({
1931
2219
  next: (formState) => {
1932
2220
  if (shouldSubscribeByName(props.name, formState.name, props.exact) &&
1933
2221
  shouldRenderFormState(formState, props.formState || _proxyFormState, _setFormState, props.reRenderRoot)) {
2222
+ const snapshot = { ..._formValues };
1934
2223
  props.callback({
1935
- values: { ..._formValues },
2224
+ values: snapshot,
1936
2225
  ..._formState,
1937
2226
  ...formState,
1938
2227
  defaultValues: _defaultValues,
@@ -1948,7 +2237,10 @@ function createFormControl(props = {}) {
1948
2237
  };
1949
2238
  return _subscribe({
1950
2239
  ...props,
1951
- formState: _proxySubscribeFormState,
2240
+ formState: {
2241
+ ...defaultProxyFormState,
2242
+ ...props.formState,
2243
+ },
1952
2244
  });
1953
2245
  };
1954
2246
  const unregister = (name, options = {}) => {
@@ -1981,12 +2273,17 @@ function createFormControl(props = {}) {
1981
2273
  if ((isBoolean(disabled) && _state.mount) ||
1982
2274
  !!disabled ||
1983
2275
  _names.disabled.has(name)) {
2276
+ const wasDisabled = _names.disabled.has(name);
2277
+ const isDisabled = !!disabled;
2278
+ const disabledStateChanged = wasDisabled !== isDisabled;
1984
2279
  disabled ? _names.disabled.add(name) : _names.disabled.delete(name);
2280
+ disabledStateChanged && _state.mount && !_state.action && _setValid();
1985
2281
  }
1986
2282
  };
1987
2283
  const register = (name, options = {}) => {
1988
2284
  let field = get(_fields, name);
1989
2285
  const disabledIsDefined = isBoolean(options.disabled) || isBoolean(_options.disabled);
2286
+ const shouldRevalidateRemount = !_names.registerName.has(name) && field && field._f && !field._f.mount;
1990
2287
  set(_fields, name, {
1991
2288
  ...(field || {}),
1992
2289
  _f: {
@@ -1997,7 +2294,7 @@ function createFormControl(props = {}) {
1997
2294
  },
1998
2295
  });
1999
2296
  _names.mount.add(name);
2000
- if (field) {
2297
+ if (field && !shouldRevalidateRemount) {
2001
2298
  _setDisabledField({
2002
2299
  disabled: isBoolean(options.disabled)
2003
2300
  ? options.disabled
@@ -2027,7 +2324,9 @@ function createFormControl(props = {}) {
2027
2324
  onBlur: onChange,
2028
2325
  ref: (ref) => {
2029
2326
  if (ref) {
2327
+ _names.registerName.add(name);
2030
2328
  register(name, options);
2329
+ _names.registerName.delete(name);
2031
2330
  field = get(_fields, name);
2032
2331
  const fieldRef = isUndefined(ref.value)
2033
2332
  ? ref.querySelectorAll
@@ -2064,13 +2363,15 @@ function createFormControl(props = {}) {
2064
2363
  field._f.mount = false;
2065
2364
  }
2066
2365
  (_options.shouldUnregister || options.shouldUnregister) &&
2067
- !(isNameInFieldArray(_names.array, name) && _state.action) &&
2366
+ !(getFieldArrayParentNames(_names.array, name).length &&
2367
+ _state.action) &&
2068
2368
  _names.unMount.add(name);
2069
2369
  }
2070
2370
  },
2071
2371
  };
2072
2372
  };
2073
2373
  const _focusError = () => _options.shouldFocusError &&
2374
+ !_options.shouldUseNativeValidation &&
2074
2375
  iterateFieldsByAction(_fields, _focusInput, _names.mount);
2075
2376
  const _disableForm = (disabled) => {
2076
2377
  if (isBoolean(disabled)) {
@@ -2101,18 +2402,22 @@ function createFormControl(props = {}) {
2101
2402
  });
2102
2403
  if (_options.resolver) {
2103
2404
  const { errors, values } = await _runSchema();
2405
+ _updateIsValidating();
2104
2406
  _formState.errors = errors;
2105
2407
  fieldValues = cloneObject(values);
2106
2408
  }
2107
2409
  else {
2108
- await executeBuiltInValidation(_fields);
2410
+ await executeBuiltInValidation({
2411
+ fields: _fields,
2412
+ eventType: EVENTS.SUBMIT,
2413
+ });
2109
2414
  }
2110
2415
  if (_names.disabled.size) {
2111
2416
  for (const name of _names.disabled) {
2112
2417
  unset(fieldValues, name);
2113
2418
  }
2114
2419
  }
2115
- unset(_formState.errors, 'root');
2420
+ unset(_formState.errors, ROOT_ERROR_TYPE);
2116
2421
  if (isEmptyObject(_formState.errors)) {
2117
2422
  _subjects.state.next({
2118
2423
  errors: {},
@@ -2182,9 +2487,15 @@ function createFormControl(props = {}) {
2182
2487
  ...Object.keys(getDirtyFields(_defaultValues, _formValues)),
2183
2488
  ]);
2184
2489
  for (const fieldName of Array.from(fieldsToCheck)) {
2185
- get(_formState.dirtyFields, fieldName)
2186
- ? set(values, fieldName, get(_formValues, fieldName))
2187
- : setValue(fieldName, get(values, fieldName));
2490
+ const isDirty = get(_formState.dirtyFields, fieldName);
2491
+ const existingValue = get(_formValues, fieldName);
2492
+ const newValue = get(values, fieldName);
2493
+ if (isDirty && !isUndefined(existingValue)) {
2494
+ set(values, fieldName, existingValue);
2495
+ }
2496
+ else if (!isDirty && !isUndefined(newValue)) {
2497
+ setValue(fieldName, newValue);
2498
+ }
2188
2499
  }
2189
2500
  }
2190
2501
  else {
@@ -2230,6 +2541,7 @@ function createFormControl(props = {}) {
2230
2541
  mount: keepStateOptions.keepDirtyValues ? _names.mount : new Set(),
2231
2542
  unMount: new Set(),
2232
2543
  array: new Set(),
2544
+ registerName: new Set(),
2233
2545
  disabled: new Set(),
2234
2546
  watch: new Set(),
2235
2547
  watchAll: false,
@@ -2241,6 +2553,7 @@ function createFormControl(props = {}) {
2241
2553
  !!keepStateOptions.keepDirtyValues ||
2242
2554
  (!_options.shouldUnregister && !isEmptyObject(values));
2243
2555
  _state.watch = !!_options.shouldUnregister;
2556
+ _state.keepIsValid = !!keepStateOptions.keepIsValid;
2244
2557
  _state.action = false;
2245
2558
  // Clear errors synchronously to prevent validation errors on subsequent submissions
2246
2559
  // This fixes the issue where form.reset() causes validation errors on subsequent
@@ -2285,7 +2598,7 @@ function createFormControl(props = {}) {
2285
2598
  };
2286
2599
  const reset = (formValues, keepStateOptions) => _reset(isFunction(formValues)
2287
2600
  ? formValues(_formValues)
2288
- : formValues, keepStateOptions);
2601
+ : formValues, { ..._options.resetOptions, ...keepStateOptions });
2289
2602
  const setFocus = (name, options = {}) => {
2290
2603
  const field = get(_fields, name);
2291
2604
  const fieldReference = field && field._f;
@@ -2294,10 +2607,14 @@ function createFormControl(props = {}) {
2294
2607
  ? fieldReference.refs[0]
2295
2608
  : fieldReference.ref;
2296
2609
  if (fieldRef.focus) {
2297
- fieldRef.focus();
2298
- options.shouldSelect &&
2299
- isFunction(fieldRef.select) &&
2300
- fieldRef.select();
2610
+ // Use setTimeout to ensure focus happens after any pending state updates
2611
+ // This fixes the issue where setFocus doesn't work immediately after setError
2612
+ setTimeout(() => {
2613
+ fieldRef.focus();
2614
+ options.shouldSelect &&
2615
+ isFunction(fieldRef.select) &&
2616
+ fieldRef.select();
2617
+ });
2301
2618
  }
2302
2619
  }
2303
2620
  };
@@ -2323,6 +2640,7 @@ function createFormControl(props = {}) {
2323
2640
  setError,
2324
2641
  _subscribe,
2325
2642
  _runSchema,
2643
+ _updateIsValidating,
2326
2644
  _focusError,
2327
2645
  _getWatch,
2328
2646
  _getDirty,
@@ -2377,6 +2695,7 @@ function createFormControl(props = {}) {
2377
2695
  handleSubmit,
2378
2696
  watch,
2379
2697
  setValue,
2698
+ setValues,
2380
2699
  getValues,
2381
2700
  reset,
2382
2701
  resetField,
@@ -2398,7 +2717,7 @@ var generateId = () => {
2398
2717
  }
2399
2718
  const d = typeof performance === 'undefined' ? Date.now() : performance.now() * 1000;
2400
2719
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
2401
- const r = (Math.random() * 16 + d) % 16 | 0;
2720
+ const r = ((Math.random() * 16 + d) % 16) | 0;
2402
2721
  return (c == 'x' ? r : (r & 0x3) | 0x8).toString(16);
2403
2722
  });
2404
2723
  };
@@ -2487,7 +2806,7 @@ var updateAt = (fieldValues, index, value) => {
2487
2806
  * return (
2488
2807
  * <form onSubmit={handleSubmit(data => console.log(data))}>
2489
2808
  * {fields.map((item, index) => (
2490
- * <input key={item.id} {...register(`test.${index}.firstName`)} />
2809
+ * <input key={item.key} {...register(`test.${index}.firstName`)} />
2491
2810
  * ))}
2492
2811
  * <button type="button" onClick={() => append({ firstName: "bill" })}>
2493
2812
  * append
@@ -2499,8 +2818,8 @@ var updateAt = (fieldValues, index, value) => {
2499
2818
  * ```
2500
2819
  */
2501
2820
  function useFieldArray(props) {
2502
- const methods = useFormContext();
2503
- const { control = methods.control, name, keyName = 'key', shouldUnregister, rules, } = props;
2821
+ const formControl = useFormControlContext();
2822
+ const { control = formControl, name, shouldUnregister, rules } = props;
2504
2823
  const [fields, setFields] = React.useState(control._getFieldArray(name));
2505
2824
  const ids = React.useRef(control._getFieldArray(name).map(generateId));
2506
2825
  const _actioned = React.useRef(false);
@@ -2614,12 +2933,14 @@ function useFieldArray(props) {
2614
2933
  control._subjects.state.next({
2615
2934
  ...control._formState,
2616
2935
  });
2936
+ const validationModes = getValidationModes(control._options.mode);
2617
2937
  if (_actioned.current &&
2618
- (!getValidationModes(control._options.mode).isOnSubmit ||
2619
- control._formState.isSubmitted) &&
2620
- !getValidationModes(control._options.reValidateMode).isOnSubmit) {
2938
+ (!validationModes.isOnSubmit || control._formState.isSubmitted) &&
2939
+ !getValidationModes(control._options.reValidateMode).isOnSubmit &&
2940
+ !validationModes.isOnBlur) {
2621
2941
  if (control._options.resolver) {
2622
2942
  control._runSchema([name]).then((result) => {
2943
+ control._updateIsValidating([name]);
2623
2944
  const error = get(result.errors, name);
2624
2945
  const existingError = get(control._formState.errors, name);
2625
2946
  if (existingError
@@ -2671,17 +2992,24 @@ function useFieldArray(props) {
2671
2992
  React.useEffect(() => {
2672
2993
  !get(control._formValues, name) && control._setFieldArray(name);
2673
2994
  return () => {
2995
+ const shouldKeepFieldArrayValues = !(control._options.shouldUnregister || shouldUnregister);
2674
2996
  const updateMounted = (name, value) => {
2675
2997
  const field = get(control._fields, name);
2676
2998
  if (field && field._f) {
2677
2999
  field._f.mount = value;
2678
3000
  }
2679
3001
  };
2680
- control._options.shouldUnregister || shouldUnregister
2681
- ? control.unregister(name)
2682
- : updateMounted(name, false);
3002
+ if (_actioned.current && shouldKeepFieldArrayValues) {
3003
+ control._subjects.state.next({
3004
+ name,
3005
+ values: cloneObject(control._formValues),
3006
+ });
3007
+ }
3008
+ shouldKeepFieldArrayValues
3009
+ ? updateMounted(name, false)
3010
+ : control.unregister(name);
2683
3011
  };
2684
- }, [name, control, keyName, shouldUnregister]);
3012
+ }, [name, control, shouldUnregister]);
2685
3013
  return {
2686
3014
  swap: React.useCallback(swap, [updateValues, name, control]),
2687
3015
  move: React.useCallback(move, [updateValues, name, control]),
@@ -2693,8 +3021,8 @@ function useFieldArray(props) {
2693
3021
  replace: React.useCallback(replace, [updateValues, name, control]),
2694
3022
  fields: React.useMemo(() => fields.map((field, index) => ({
2695
3023
  ...field,
2696
- [keyName]: ids.current[index] || generateId(),
2697
- })), [fields, keyName]),
3024
+ key: ids.current[index] || generateId(),
3025
+ })), [fields]),
2698
3026
  };
2699
3027
  }
2700
3028
 
@@ -2741,25 +3069,15 @@ function updateMethodsReference(_formControl) {
2741
3069
  function useForm(props = {}) {
2742
3070
  const _formControl = React.useRef(undefined);
2743
3071
  const _values = React.useRef(undefined);
2744
- const [formState, updateFormState] = React.useState({
2745
- isDirty: false,
2746
- isValidating: false,
3072
+ const [formState, updateFormState] = React.useState(() => ({
3073
+ ...cloneObject(DEFAULT_FORM_STATE),
2747
3074
  isLoading: isFunction(props.defaultValues),
2748
- isSubmitted: false,
2749
- isSubmitting: false,
2750
- isSubmitSuccessful: false,
2751
- isValid: false,
2752
- submitCount: 0,
2753
- dirtyFields: {},
2754
- touchedFields: {},
2755
- validatingFields: {},
2756
3075
  errors: props.errors || {},
2757
3076
  disabled: props.disabled || false,
2758
- isReady: false,
2759
3077
  defaultValues: isFunction(props.defaultValues)
2760
3078
  ? undefined
2761
3079
  : props.defaultValues,
2762
- });
3080
+ }));
2763
3081
  if (!_formControl.current) {
2764
3082
  if (props.formControl) {
2765
3083
  _formControl.current = {
@@ -2783,7 +3101,10 @@ function useForm(props = {}) {
2783
3101
  useIsomorphicLayoutEffect(() => {
2784
3102
  const sub = control._subscribe({
2785
3103
  formState: control._proxyFormState,
2786
- callback: () => updateFormState({ ...control._formState }),
3104
+ callback: () => updateFormState({
3105
+ ...control._formState,
3106
+ defaultValues: control._defaultValues,
3107
+ }),
2787
3108
  reRenderRoot: true,
2788
3109
  });
2789
3110
  updateFormState((data) => ({
@@ -2825,14 +3146,13 @@ function useForm(props = {}) {
2825
3146
  }
2826
3147
  }, [control, formState.isDirty]);
2827
3148
  React.useEffect(() => {
2828
- var _a;
2829
3149
  if (props.values && !deepEqual(props.values, _values.current)) {
2830
3150
  updateMethodsReference(_formControl);
2831
3151
  control._reset(props.values, {
2832
3152
  keepFieldsRef: true,
2833
3153
  ...control._options.resetOptions,
2834
3154
  });
2835
- if (!((_a = control._options.resetOptions) === null || _a === void 0 ? void 0 : _a.keepIsValid)) {
3155
+ if (!control._options.resetOptions?.keepIsValid) {
2836
3156
  control._setValid();
2837
3157
  }
2838
3158
  _values.current = props.values;
@@ -2872,7 +3192,11 @@ function useForm(props = {}) {
2872
3192
  * Watch component that subscribes to form field changes and re-renders when watched fields update.
2873
3193
  *
2874
3194
  * @param control - The form control object from useForm
2875
- * @param names - Array of field names to watch for changes
3195
+ * @param name - Can be field name, array of field names, or undefined to watch the entire form
3196
+ * @param disabled - Disable subscription
3197
+ * @param exact - Whether to watch exact field names or not
3198
+ * @param defaultValue - The default value to use if the field is not yet set
3199
+ * @param compute - Function to compute derived values from watched fields
2876
3200
  * @param render - The function that receives watched values and returns ReactNode
2877
3201
  * @returns The result of calling render function with watched values
2878
3202
  *
@@ -2890,7 +3214,7 @@ function useForm(props = {}) {
2890
3214
  * />
2891
3215
  * ```
2892
3216
  */
2893
- const Watch = ({ control, names, render, }) => render(useWatch({ control, name: names }));
3217
+ const Watch = (props) => props.render(useWatch(props));
2894
3218
 
2895
3219
  export { Controller, Form, FormProvider, FormStateSubscribe, Watch, appendErrors, createFormControl, get, set, useController, useFieldArray, useForm, useFormContext, useFormState, useWatch };
2896
3220
  //# sourceMappingURL=index.esm.mjs.map