react-hook-form 8.0.0-beta.2 → 8.0.0-beta.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 (50) hide show
  1. package/README.md +0 -11
  2. package/dist/constants.d.ts +1 -1
  3. package/dist/constants.d.ts.map +1 -1
  4. package/dist/fieldArray.d.ts +36 -0
  5. package/dist/fieldArray.d.ts.map +1 -0
  6. package/dist/index.cjs.js +1 -1
  7. package/dist/index.cjs.js.map +1 -1
  8. package/dist/index.esm.mjs +420 -200
  9. package/dist/index.esm.mjs.map +1 -1
  10. package/dist/index.umd.js +1 -1
  11. package/dist/index.umd.js.map +1 -1
  12. package/dist/logic/createFormControl.d.ts.map +1 -1
  13. package/dist/logic/getNodeParentName.d.ts.map +1 -1
  14. package/dist/logic/hasPromiseValidation.d.ts.map +1 -1
  15. package/dist/logic/index.d.ts +2 -3
  16. package/dist/logic/index.d.ts.map +1 -1
  17. package/dist/logic/{getFieldArrayParentNames.d.ts → isNameInFieldArray.d.ts} +2 -2
  18. package/dist/logic/isNameInFieldArray.d.ts.map +1 -0
  19. package/dist/logic/isWatched.d.ts.map +1 -1
  20. package/dist/logic/shouldRenderFormState.d.ts.map +1 -1
  21. package/dist/logic/skipValidation.d.ts +1 -4
  22. package/dist/logic/skipValidation.d.ts.map +1 -1
  23. package/dist/logic/updateFieldArrayRootError.d.ts.map +1 -1
  24. package/dist/logic/validateField.d.ts.map +1 -1
  25. package/dist/react-server.esm.mjs +265 -121
  26. package/dist/react-server.esm.mjs.map +1 -1
  27. package/dist/types/fieldArray.d.ts +25 -1
  28. package/dist/types/fieldArray.d.ts.map +1 -1
  29. package/dist/types/form.d.ts +49 -3
  30. package/dist/types/form.d.ts.map +1 -1
  31. package/dist/types/utils.d.ts +1 -1
  32. package/dist/types/utils.d.ts.map +1 -1
  33. package/dist/useController.d.ts.map +1 -1
  34. package/dist/useFieldArray.d.ts.map +1 -1
  35. package/dist/useForm.d.ts.map +1 -1
  36. package/dist/useFormContext.d.ts +1 -1
  37. package/dist/useFormContext.d.ts.map +1 -1
  38. package/dist/useIsomorphicLayoutEffect.d.ts.map +1 -1
  39. package/dist/utils/deepEqual.d.ts +1 -1
  40. package/dist/utils/deepEqual.d.ts.map +1 -1
  41. package/dist/utils/flatten.d.ts.map +1 -1
  42. package/dist/utils/get.d.ts.map +1 -1
  43. package/dist/utils/index.d.ts +2 -3
  44. package/dist/utils/index.d.ts.map +1 -1
  45. package/dist/utils/isKey.d.ts.map +1 -1
  46. package/dist/utils/set.d.ts.map +1 -1
  47. package/dist/utils/stringToPath.d.ts.map +1 -1
  48. package/dist/utils/unset.d.ts.map +1 -1
  49. package/package.json +23 -19
  50. package/dist/logic/getFieldArrayParentNames.d.ts.map +0 -1
@@ -18,15 +18,9 @@ var getEventValue = (event) => isObject(event) && event.target
18
18
  : event.target.value
19
19
  : event;
20
20
 
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
- };
21
+ var isNameInFieldArray = (names, name) => name
22
+ .split('.')
23
+ .some((part, index, arr) => !isNaN(Number(part)) && names.has(arr.slice(0, index).join('.')));
30
24
 
31
25
  var isPlainObject = (tempObject) => {
32
26
  const prototypeCopy = tempObject.constructor && tempObject.constructor.prototype;
@@ -58,19 +52,49 @@ function cloneObject(data) {
58
52
  return copy;
59
53
  }
60
54
 
61
- var isKey = (value) => /^\w*$/.test(value);
55
+ const EVENTS = {
56
+ BLUR: 'blur',
57
+ FOCUS_OUT: 'focusout',
58
+ CHANGE: 'change',
59
+ SUBMIT: 'submit',
60
+ TRIGGER: 'trigger',
61
+ VALID: 'valid',
62
+ };
63
+ const VALIDATION_MODE = {
64
+ onBlur: 'onBlur',
65
+ onChange: 'onChange',
66
+ onSubmit: 'onSubmit',
67
+ onTouched: 'onTouched',
68
+ all: 'all',
69
+ };
70
+ const INPUT_VALIDATION_RULES = {
71
+ max: 'max',
72
+ min: 'min',
73
+ maxLength: 'maxLength',
74
+ minLength: 'minLength',
75
+ pattern: 'pattern',
76
+ required: 'required',
77
+ validate: 'validate',
78
+ };
79
+ const ROOT_ERROR_TYPE = 'root';
80
+ const PROTOTYPE_KEYWORDS = ['__proto__', 'constructor', 'prototype'];
81
+
82
+ const IS_KEY_RE = /^\w*$/;
83
+ var isKey = (value) => IS_KEY_RE.test(value);
62
84
 
63
85
  var isUndefined = (val) => val === undefined;
64
86
 
65
- var compact = (value) => Array.isArray(value) ? value.filter(Boolean) : [];
66
-
67
- var stringToPath = (input) => compact(input.replace(/["|']|\]/g, '').split(/\.|\[/));
87
+ const FIELD_PATH_RE = /[.[\]'"]/;
88
+ var stringToPath = (input) => input.split(FIELD_PATH_RE).filter(Boolean);
68
89
 
69
90
  var get = (object, path, defaultValue) => {
70
91
  if (!path || !isObject(object)) {
71
92
  return defaultValue;
72
93
  }
73
94
  const paths = isKey(path) ? [path] : stringToPath(path);
95
+ if (paths.some((key) => PROTOTYPE_KEYWORDS.includes(key))) {
96
+ return defaultValue;
97
+ }
74
98
  const result = paths.reduce((result, key) => {
75
99
  return isNullOrUndefined(result) ? undefined : result[key];
76
100
  }, object);
@@ -100,7 +124,7 @@ var set = (object, path, value) => {
100
124
  ? []
101
125
  : {};
102
126
  }
103
- if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
127
+ if (PROTOTYPE_KEYWORDS.includes(key)) {
104
128
  return;
105
129
  }
106
130
  object[key] = newValue;
@@ -108,33 +132,6 @@ var set = (object, path, value) => {
108
132
  }
109
133
  };
110
134
 
111
- const EVENTS = {
112
- BLUR: 'blur',
113
- FOCUS_OUT: 'focusout',
114
- CHANGE: 'change',
115
- SUBMIT: 'submit',
116
- TRIGGER: 'trigger',
117
- VALID: 'valid',
118
- };
119
- const VALIDATION_MODE = {
120
- onBlur: 'onBlur',
121
- onChange: 'onChange',
122
- onSubmit: 'onSubmit',
123
- onTouched: 'onTouched',
124
- all: 'all',
125
- };
126
- const INPUT_VALIDATION_RULES = {
127
- max: 'max',
128
- min: 'min',
129
- maxLength: 'maxLength',
130
- minLength: 'minLength',
131
- pattern: 'pattern',
132
- required: 'required',
133
- validate: 'validate',
134
- };
135
- const FORM_ERROR_TYPE = 'form';
136
- const ROOT_ERROR_TYPE = 'root';
137
-
138
135
  /**
139
136
  * Separate context for `control` to prevent unnecessary rerenders.
140
137
  * Internal hooks that only need control use this instead of full form context.
@@ -163,7 +160,9 @@ var getProxyFormState = (formState, control, localProxyFormState, isRoot = true)
163
160
  return result;
164
161
  };
165
162
 
166
- const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;
163
+ const useIsomorphicLayoutEffect = isWeb
164
+ ? React.useLayoutEffect
165
+ : React.useEffect;
167
166
 
168
167
  /**
169
168
  * 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.
@@ -248,7 +247,8 @@ var generateWatchOutput = (names, _names, formValues, isGlobal, defaultValue) =>
248
247
 
249
248
  var isPrimitive = (value) => isNullOrUndefined(value) || !isObjectType(value);
250
249
 
251
- function deepEqual(object1, object2, visited = new WeakSet()) {
250
+ const isEmptyObjectWithCustomPrototype = (object, keys) => keys.length === 0 && !Array.isArray(object) && !isPlainObject(object);
251
+ function deepEqual(object1, object2, visited = new WeakMap()) {
252
252
  if (object1 === object2) {
253
253
  return true;
254
254
  }
@@ -263,11 +263,25 @@ function deepEqual(object1, object2, visited = new WeakSet()) {
263
263
  if (keys1.length !== keys2.length) {
264
264
  return false;
265
265
  }
266
- if (visited.has(object1) || visited.has(object2)) {
266
+ if (isEmptyObjectWithCustomPrototype(object1, keys1) ||
267
+ isEmptyObjectWithCustomPrototype(object2, keys2)) {
268
+ return Object.is(object1, object2);
269
+ }
270
+ if (!keys1.length && Array.isArray(object1) !== Array.isArray(object2)) {
271
+ return false;
272
+ }
273
+ const visitedPairs = visited.get(object1);
274
+ if (visitedPairs && visitedPairs.has(object2)) {
267
275
  return true;
268
276
  }
269
- visited.add(object1);
270
- visited.add(object2);
277
+ if (visitedPairs) {
278
+ visitedPairs.add(object2);
279
+ }
280
+ else {
281
+ const ws = new WeakSet();
282
+ ws.add(object2);
283
+ visited.set(object1, ws);
284
+ }
271
285
  for (const key of keys1) {
272
286
  const val1 = object1[key];
273
287
  if (!(key in object2)) {
@@ -400,8 +414,7 @@ function useWatch(props) {
400
414
  function useController(props) {
401
415
  const formControl = useFormControlContext();
402
416
  const { name, disabled, control = formControl, shouldUnregister, defaultValue, exact = true, } = props;
403
- const isArrayField = !!getFieldArrayParentNames(control._names.array, name)
404
- .length;
417
+ const isArrayField = isNameInFieldArray(control._names.array, name);
405
418
  const defaultValueMemo = React.useMemo(() => get(control._formValues, name, get(control._defaultValues, name, defaultValue)), [control, name, defaultValue]);
406
419
  const value = useWatch({
407
420
  control,
@@ -415,6 +428,7 @@ function useController(props) {
415
428
  exact,
416
429
  });
417
430
  const _props = React.useRef(props);
431
+ const _proxyRef = React.useRef(null);
418
432
  const _registerProps = React.useRef(control.register(name, {
419
433
  ...props.rules,
420
434
  value,
@@ -443,13 +457,22 @@ function useController(props) {
443
457
  get: () => get(formState.errors, name),
444
458
  },
445
459
  }), [formState, name]);
446
- const onChange = React.useCallback((event) => _registerProps.current.onChange({
447
- target: {
448
- value: getEventValue(event),
449
- name: name,
450
- },
451
- type: EVENTS.CHANGE,
452
- }), [name]);
460
+ const onChange = React.useCallback((event) => {
461
+ const value = getEventValue(event);
462
+ if (!get(control._fields, name)) {
463
+ _registerProps.current = control.register(name, {
464
+ ..._props.current.rules,
465
+ value,
466
+ });
467
+ }
468
+ return _registerProps.current.onChange({
469
+ target: {
470
+ value: getEventValue(event),
471
+ name: name,
472
+ },
473
+ type: EVENTS.CHANGE,
474
+ });
475
+ }, [name, control]);
453
476
  const onBlur = React.useCallback(() => _registerProps.current.onBlur({
454
477
  target: {
455
478
  value: get(control._formValues, name),
@@ -458,9 +481,12 @@ function useController(props) {
458
481
  type: EVENTS.BLUR,
459
482
  }), [name, control._formValues]);
460
483
  const ref = React.useCallback((elm) => {
484
+ if (elm) {
485
+ _proxyRef.current = elm;
486
+ }
461
487
  const field = get(control._fields, name);
462
488
  if (field && field._f && elm) {
463
- field._f.ref = elm;
489
+ field._f.ref = _proxyRef.current;
464
490
  }
465
491
  }, [control._fields, name]);
466
492
  const field = React.useMemo(() => ({
@@ -489,13 +515,21 @@ function useController(props) {
489
515
  };
490
516
  updateMounted(name, true);
491
517
  if (_shouldUnregisterField) {
492
- const value = cloneObject(get(control._defaultValues, name, get(control._options.defaultValues, name, _props.current.defaultValue)));
518
+ const value = cloneObject(get(shouldUnregister
519
+ ? control._defaultValues
520
+ : control._options.values || control._defaultValues, name, get(control._options.defaultValues, name, _props.current.defaultValue)));
493
521
  set(control._defaultValues, name, value);
494
522
  if (isUndefined(get(control._formValues, name))) {
495
523
  set(control._formValues, name, value);
496
524
  }
497
525
  }
498
526
  !isArrayField && control.register(name);
527
+ if (_proxyRef.current) {
528
+ const field = get(control._fields, name);
529
+ if (field && field._f) {
530
+ field._f.ref = _proxyRef.current;
531
+ }
532
+ }
499
533
  return () => {
500
534
  (isArrayField
501
535
  ? _shouldUnregisterField && !control._state.action
@@ -564,7 +598,9 @@ const Controller = (props) => props.render(useController(props));
564
598
  const flatten = (obj) => {
565
599
  const output = {};
566
600
  for (const key of Object.keys(obj)) {
567
- if (isObjectType(obj[key]) && obj[key] !== null) {
601
+ if (isObjectType(obj[key]) &&
602
+ obj[key] !== null &&
603
+ !isDateObject(obj[key])) {
568
604
  const nested = flatten(obj[key]);
569
605
  for (const nestedKey of Object.keys(nested)) {
570
606
  output[`${key}.${nestedKey}`] = nested[nestedKey];
@@ -640,8 +676,7 @@ const useFormContext = () => React.useContext(HookFormContext);
640
676
  * }
641
677
  * ```
642
678
  */
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;
679
+ const FormProvider = ({ children, watch, getValues, getFieldState, setError, clearErrors, setValue, setValues, trigger, formState, resetField, reset, handleSubmit, unregister, control, register, setFocus, subscribe, }) => {
645
680
  const memoizedValue = React.useMemo(() => ({
646
681
  watch,
647
682
  getValues,
@@ -806,6 +841,8 @@ var appendErrors = (name, validateAllFieldCriteria, errors, type, message) => va
806
841
  }
807
842
  : {};
808
843
 
844
+ var compact = (value) => Array.isArray(value) ? value.filter(Boolean) : [];
845
+
809
846
  var convertToArrayPayload = (value) => (Array.isArray(value) ? value : [value]);
810
847
 
811
848
  var createSubject = () => {
@@ -910,6 +947,9 @@ function unset(object, path) {
910
947
  : isKey(path)
911
948
  ? [path]
912
949
  : stringToPath(path);
950
+ if (paths.some((segment) => PROTOTYPE_KEYWORDS.includes(String(segment)))) {
951
+ return object;
952
+ }
913
953
  const childObject = paths.length === 1 ? object : baseGet(object, paths);
914
954
  const index = paths.length - 1;
915
955
  const key = paths[index];
@@ -1098,12 +1138,22 @@ var getValidationModes = (mode) => ({
1098
1138
  });
1099
1139
 
1100
1140
  const ASYNC_FUNCTION = 'AsyncFunction';
1101
- var hasPromiseValidation = (fieldReference) => !!fieldReference &&
1102
- !!fieldReference.validate &&
1103
- !!((isFunction(fieldReference.validate) &&
1104
- fieldReference.validate.constructor.name === ASYNC_FUNCTION) ||
1105
- (isObject(fieldReference.validate) &&
1106
- Object.values(fieldReference.validate).find((validateFunction) => validateFunction.constructor.name === ASYNC_FUNCTION)));
1141
+ var hasPromiseValidation = (fieldReference) => {
1142
+ if (!fieldReference || !fieldReference.validate)
1143
+ return false;
1144
+ if (isFunction(fieldReference.validate)) {
1145
+ return fieldReference.validate.constructor.name === ASYNC_FUNCTION;
1146
+ }
1147
+ if (isObject(fieldReference.validate)) {
1148
+ for (const key in fieldReference.validate) {
1149
+ if (fieldReference.validate[key].constructor
1150
+ .name === ASYNC_FUNCTION) {
1151
+ return true;
1152
+ }
1153
+ }
1154
+ }
1155
+ return false;
1156
+ };
1107
1157
 
1108
1158
  var hasValidation = (options) => options.mount &&
1109
1159
  (options.required ||
@@ -1114,11 +1164,17 @@ var hasValidation = (options) => options.mount &&
1114
1164
  options.pattern ||
1115
1165
  options.validate);
1116
1166
 
1117
- var isWatched = (name, _names, isBlurEvent) => !isBlurEvent &&
1118
- (_names.watchAll ||
1119
- _names.watch.has(name) ||
1120
- [..._names.watch].some((watchName) => name.startsWith(watchName) &&
1121
- /^\.\w+/.test(name.slice(watchName.length))));
1167
+ var isWatched = (name, _names, isBlurEvent) => {
1168
+ if (isBlurEvent)
1169
+ return false;
1170
+ if (_names.watchAll || _names.watch.has(name))
1171
+ return true;
1172
+ for (const watchName of _names.watch) {
1173
+ if (name.startsWith(watchName) && name.charAt(watchName.length) === '.')
1174
+ return true;
1175
+ }
1176
+ return false;
1177
+ };
1122
1178
 
1123
1179
  const iterateFieldsByAction = (fields, action, fieldsNames, abortEarly) => {
1124
1180
  for (const key of fieldsNames || Object.keys(fields)) {
@@ -1186,10 +1242,10 @@ function schemaErrorLookup(errors, _fields, name) {
1186
1242
  var shouldRenderFormState = (formStateData, _proxyFormState, updateFormState, isRoot) => {
1187
1243
  updateFormState(formStateData);
1188
1244
  const { name, ...formState } = formStateData;
1189
- return (isEmptyObject(formState) ||
1190
- (isRoot &&
1191
- Object.keys(formState).length >= Object.keys(_proxyFormState).length) ||
1192
- Object.keys(formState).find((key) => _proxyFormState[key] ===
1245
+ const keys = Object.keys(formState);
1246
+ return (!keys.length ||
1247
+ (isRoot && keys.length >= Object.keys(_proxyFormState).length) ||
1248
+ keys.find((key) => _proxyFormState[key] ===
1193
1249
  (!isRoot || VALIDATION_MODE.all)));
1194
1250
  };
1195
1251
 
@@ -1198,7 +1254,7 @@ var shouldSubscribeByName = (name, signalName, exact) => !name ||
1198
1254
  name === signalName ||
1199
1255
  convertToArrayPayload(name).some((currentName) => currentName &&
1200
1256
  (exact
1201
- ? currentName === signalName
1257
+ ? currentName === signalName || currentName.startsWith(signalName + '.')
1202
1258
  : currentName.startsWith(signalName) ||
1203
1259
  signalName.startsWith(currentName)));
1204
1260
 
@@ -1221,7 +1277,8 @@ var skipValidation = (isBlurEvent, isTouched, isSubmitted, reValidateMode, mode)
1221
1277
  var unsetEmptyArray = (ref, name) => !compact(get(ref, name)).length && unset(ref, name);
1222
1278
 
1223
1279
  var updateFieldArrayRootError = (errors, error, name) => {
1224
- const fieldArrayErrors = convertToArrayPayload(get(errors, name));
1280
+ const existingErrors = get(errors, name);
1281
+ const fieldArrayErrors = Array.isArray(existingErrors) ? existingErrors : [];
1225
1282
  set(fieldArrayErrors, ROOT_ERROR_TYPE, error[name]);
1226
1283
  set(errors, name, fieldArrayErrors);
1227
1284
  return errors;
@@ -1255,7 +1312,13 @@ var validateField = async (field, disabledFieldNames, formValues, validateAllFie
1255
1312
  const inputRef = refs ? refs[0] : ref;
1256
1313
  const setCustomValidity = (message) => {
1257
1314
  if (shouldUseNativeValidation && inputRef.reportValidity) {
1258
- inputRef.setCustomValidity(isBoolean(message) ? '' : message || '');
1315
+ const validityMessage = isBoolean(message) ? '' : message || '';
1316
+ if (refs) {
1317
+ refs.forEach((ref) => ref.setCustomValidity(validityMessage));
1318
+ }
1319
+ else {
1320
+ inputRef.setCustomValidity(validityMessage);
1321
+ }
1259
1322
  inputRef.reportValidity();
1260
1323
  }
1261
1324
  };
@@ -1268,8 +1331,7 @@ var validateField = async (field, disabledFieldNames, formValues, validateAllFie
1268
1331
  isUndefined(inputValue)) ||
1269
1332
  (isHTMLElement(ref) && ref.value === '') ||
1270
1333
  inputValue === '' ||
1271
- (Array.isArray(inputValue) && !inputValue.length) ||
1272
- (valueAsNumber && typeof inputValue === 'number' && isNaN(inputValue));
1334
+ (Array.isArray(inputValue) && !inputValue.length);
1273
1335
  const appendErrorsCurry = appendErrors.bind(null, name, validateAllFieldCriteria, error);
1274
1336
  const getMinMaxMessage = (exceedMax, maxLengthMessage, minLengthMessage, maxType = INPUT_VALIDATION_RULES.maxLength, minType = INPUT_VALIDATION_RULES.minLength) => {
1275
1337
  const message = exceedMax ? maxLengthMessage : minLengthMessage;
@@ -1431,6 +1493,7 @@ const defaultOptions = {
1431
1493
  reValidateMode: VALIDATION_MODE.onChange,
1432
1494
  shouldFocusError: true,
1433
1495
  };
1496
+ const FORM_ERROR_TYPE = 'form';
1434
1497
  const DEFAULT_FORM_STATE = {
1435
1498
  submitCount: 0,
1436
1499
  isDirty: false,
@@ -1478,6 +1541,9 @@ function createFormControl(props = {}) {
1478
1541
  };
1479
1542
  let delayErrorCallback;
1480
1543
  let timer = 0;
1544
+ let _valuesSubscriberCount = 0;
1545
+ let _validationModeBeforeSubmit = getValidationModes(_options.mode);
1546
+ let _validationModeAfterSubmit = getValidationModes(_options.reValidateMode);
1481
1547
  const defaultProxyFormState = {
1482
1548
  isDirty: false,
1483
1549
  dirtyFields: false,
@@ -1588,6 +1654,7 @@ function createFormControl(props = {}) {
1588
1654
  };
1589
1655
  const updateErrors = (name, error) => {
1590
1656
  set(_formState.errors, name, error);
1657
+ _formState.errors = { ..._formState.errors };
1591
1658
  _subjects.state.next({
1592
1659
  errors: _formState.errors,
1593
1660
  });
@@ -1630,12 +1697,6 @@ function createFormControl(props = {}) {
1630
1697
  : setFieldValue(name, defaultValue);
1631
1698
  if (_state.mount && !_state.action) {
1632
1699
  _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
1700
  if (wasUnsetInFormValues &&
1640
1701
  _formState.isDirty &&
1641
1702
  (_proxyFormState.isDirty || _proxySubscribeFormState.isDirty)) {
@@ -1645,6 +1706,12 @@ function createFormControl(props = {}) {
1645
1706
  _subjects.state.next({ ..._formState });
1646
1707
  }
1647
1708
  }
1709
+ if (props.shouldUnregister &&
1710
+ wasUnsetInFormValues &&
1711
+ !isUndefined(get(_formValues, name)) &&
1712
+ isWatched(name, _names)) {
1713
+ _state.watch = true;
1714
+ }
1648
1715
  }
1649
1716
  }
1650
1717
  };
@@ -1656,12 +1723,13 @@ function createFormControl(props = {}) {
1656
1723
  };
1657
1724
  if (!_options.disabled) {
1658
1725
  if (!isBlurEvent || shouldDirty) {
1726
+ const isCurrentFieldPristine = deepEqual(get(_defaultValues, name), fieldValue);
1659
1727
  if (_proxyFormState.isDirty || _proxySubscribeFormState.isDirty) {
1660
1728
  isPreviousDirty = _formState.isDirty;
1661
- _formState.isDirty = output.isDirty = _getDirty();
1729
+ _formState.isDirty = output.isDirty =
1730
+ !isCurrentFieldPristine || _getDirty();
1662
1731
  shouldUpdateField = isPreviousDirty !== output.isDirty;
1663
1732
  }
1664
- const isCurrentFieldPristine = deepEqual(get(_defaultValues, name), fieldValue);
1665
1733
  isPreviousDirty = !!get(_formState.dirtyFields, name);
1666
1734
  if (isCurrentFieldPristine !== _formState.isDirty) {
1667
1735
  _formState.dirtyFields = getDirtyFields(_defaultValues, _formValues);
@@ -1709,6 +1777,7 @@ function createFormControl(props = {}) {
1709
1777
  error
1710
1778
  ? set(_formState.errors, name, error)
1711
1779
  : unset(_formState.errors, name);
1780
+ _formState.errors = { ..._formState.errors };
1712
1781
  }
1713
1782
  if ((error ? !deepEqual(previousFieldError, error) : previousFieldError) ||
1714
1783
  !isEmptyObject(fieldState) ||
@@ -1737,11 +1806,14 @@ function createFormControl(props = {}) {
1737
1806
  for (const name of names) {
1738
1807
  const error = get(errors, name);
1739
1808
  error
1740
- ? _names.array.has(name) && isObject(error)
1809
+ ? _names.array.has(name) &&
1810
+ isObject(error) &&
1811
+ !Object.keys(error).some((key) => !Number.isNaN(Number(key)))
1741
1812
  ? updateFieldArrayRootError(_formState.errors, { [name]: error }, name)
1742
1813
  : set(_formState.errors, name, error)
1743
1814
  : unset(_formState.errors, name);
1744
1815
  }
1816
+ _formState.errors = { ..._formState.errors };
1745
1817
  }
1746
1818
  else {
1747
1819
  _formState.errors = errors;
@@ -1856,7 +1928,7 @@ function createFormControl(props = {}) {
1856
1928
  };
1857
1929
  const _getDirty = (name, data) => !_options.disabled &&
1858
1930
  (name && data && set(_formValues, name, data),
1859
- !deepEqual(getValues(), _defaultValues));
1931
+ !deepEqual(_state.mount ? _formValues : _defaultValues, _defaultValues));
1860
1932
  const _getWatch = (names, defaultValue, isGlobal) => generateWatchOutput(names, _names, {
1861
1933
  ...(_state.mount
1862
1934
  ? _formValues
@@ -1867,7 +1939,7 @@ function createFormControl(props = {}) {
1867
1939
  : defaultValue),
1868
1940
  }, isGlobal, defaultValue);
1869
1941
  const _getFieldArray = (name) => compact(get(_state.mount ? _formValues : _defaultValues, name, _options.shouldUnregister ? get(_defaultValues, name, []) : []));
1870
- const setFieldValue = (name, value, options = {}) => {
1942
+ const setFieldValue = (name, value, options = {}, skipClone = false, skipRender = false) => {
1871
1943
  const field = get(_fields, name);
1872
1944
  let fieldValue = value;
1873
1945
  if (field) {
@@ -1905,20 +1977,20 @@ function createFormControl(props = {}) {
1905
1977
  }
1906
1978
  else {
1907
1979
  fieldReference.ref.value = fieldValue;
1908
- if (!fieldReference.ref.type) {
1980
+ if (!fieldReference.ref.type && !skipRender) {
1909
1981
  _subjects.state.next({
1910
1982
  name,
1911
- values: cloneObject(_formValues),
1983
+ values: skipClone ? _formValues : cloneObject(_formValues),
1912
1984
  });
1913
1985
  }
1914
1986
  }
1915
1987
  }
1916
1988
  }
1917
1989
  (options.shouldDirty || options.shouldTouch) &&
1918
- updateTouchAndDirty(name, fieldValue, options.shouldTouch, options.shouldDirty, true);
1990
+ updateTouchAndDirty(name, fieldValue, options.shouldTouch, options.shouldDirty, !skipRender);
1919
1991
  options.shouldValidate && trigger(name);
1920
1992
  };
1921
- const setFieldValues = (name, value, options) => {
1993
+ const setFieldValues = (name, value, options, skipClone = false, skipRender = false) => {
1922
1994
  for (const fieldKey in value) {
1923
1995
  if (!value.hasOwnProperty(fieldKey)) {
1924
1996
  return;
@@ -1930,14 +2002,14 @@ function createFormControl(props = {}) {
1930
2002
  isObject(fieldValue) ||
1931
2003
  (field && !field._f)) &&
1932
2004
  !isDateObject(fieldValue)
1933
- ? setFieldValues(fieldName, fieldValue, options)
1934
- : setFieldValue(fieldName, fieldValue, options);
2005
+ ? setFieldValues(fieldName, fieldValue, options, skipClone, skipRender)
2006
+ : setFieldValue(fieldName, fieldValue, options, skipClone, skipRender);
1935
2007
  }
1936
2008
  };
1937
- const setValue = (name, value, options = {}) => {
2009
+ const _setValue = (name, value, options, skipClone, skipStateEmit = false) => {
1938
2010
  const field = get(_fields, name);
1939
2011
  const isFieldArray = _names.array.has(name);
1940
- const cloneValue = cloneObject(value);
2012
+ const cloneValue = skipClone ? value : cloneObject(value);
1941
2013
  const previousValue = get(_formValues, name);
1942
2014
  const isValueUnchanged = deepEqual(previousValue, cloneValue);
1943
2015
  if (!isValueUnchanged) {
@@ -1946,7 +2018,7 @@ function createFormControl(props = {}) {
1946
2018
  if (isFieldArray) {
1947
2019
  _subjects.array.next({
1948
2020
  name,
1949
- values: cloneObject(_formValues),
2021
+ values: skipClone ? _formValues : cloneObject(_formValues),
1950
2022
  });
1951
2023
  if ((_proxyFormState.isDirty ||
1952
2024
  _proxyFormState.dirtyFields ||
@@ -1954,31 +2026,28 @@ function createFormControl(props = {}) {
1954
2026
  _proxySubscribeFormState.dirtyFields) &&
1955
2027
  options.shouldDirty) {
1956
2028
  _updateDirtyFields();
1957
- _subjects.state.next({
1958
- name,
1959
- dirtyFields: _formState.dirtyFields,
1960
- isDirty: _getDirty(name, cloneValue),
1961
- });
2029
+ if (!skipStateEmit) {
2030
+ _subjects.state.next({
2031
+ name,
2032
+ dirtyFields: _formState.dirtyFields,
2033
+ isDirty: _getDirty(name, cloneValue),
2034
+ });
2035
+ }
1962
2036
  }
1963
2037
  }
1964
2038
  else {
1965
2039
  const isEmpty = (Array.isArray(cloneValue) && !cloneValue.length) ||
1966
2040
  isEmptyObject(cloneValue);
1967
2041
  if (!field || field._f || isNullOrUndefined(cloneValue) || isEmpty) {
1968
- setFieldValue(name, cloneValue, options);
2042
+ setFieldValue(name, cloneValue, options, skipClone, skipStateEmit);
1969
2043
  }
1970
2044
  else {
1971
- setFieldValues(name, cloneValue, options);
2045
+ setFieldValues(name, cloneValue, options, skipClone, skipStateEmit);
1972
2046
  }
1973
2047
  }
1974
- if (!isValueUnchanged) {
2048
+ if (!isValueUnchanged && !skipStateEmit) {
1975
2049
  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
- }
2050
+ const values = skipClone ? _formValues : cloneObject(_formValues);
1982
2051
  _subjects.state.next({
1983
2052
  ...(watched && _formState),
1984
2053
  name: _state.mount || watched ? name : undefined,
@@ -1986,7 +2055,8 @@ function createFormControl(props = {}) {
1986
2055
  });
1987
2056
  }
1988
2057
  };
1989
- const setValues = (formValues) => {
2058
+ const setValue = (name, value, options = {}) => _setValue(name, value, options, false);
2059
+ const setValues = (formValues, options = {}) => {
1990
2060
  const updatedFormValues = isFunction(formValues)
1991
2061
  ? formValues(_formValues)
1992
2062
  : formValues;
@@ -1995,10 +2065,21 @@ function createFormControl(props = {}) {
1995
2065
  ..._formValues,
1996
2066
  ...updatedFormValues,
1997
2067
  };
2068
+ const flattenedUpdates = flatten(updatedFormValues);
1998
2069
  for (const fieldName of _names.mount) {
1999
- setValue(fieldName, get(updatedFormValues, fieldName));
2070
+ if (fieldName in flattenedUpdates) {
2071
+ _setValue(fieldName, flattenedUpdates[fieldName], options, true, true);
2072
+ }
2073
+ }
2074
+ _subjects.state.next({
2075
+ ..._formState,
2076
+ name: undefined,
2077
+ type: undefined,
2078
+ ...(_valuesSubscriberCount ? { values: _formValues } : {}),
2079
+ });
2080
+ if (options.shouldValidate) {
2081
+ _setValid();
2000
2082
  }
2001
- _subjects.state.next({ ..._formState, values: _formValues });
2002
2083
  }
2003
2084
  };
2004
2085
  const onChange = async (event) => {
@@ -2013,8 +2094,6 @@ function createFormControl(props = {}) {
2013
2094
  (isDateObject(fieldValue) && isNaN(fieldValue.getTime())) ||
2014
2095
  deepEqual(fieldValue, get(_formValues, name, fieldValue));
2015
2096
  };
2016
- const validationModeBeforeSubmit = getValidationModes(_options.mode);
2017
- const validationModeAfterSubmit = getValidationModes(_options.reValidateMode);
2018
2097
  if (field) {
2019
2098
  let error;
2020
2099
  let isValid;
@@ -2022,12 +2101,13 @@ function createFormControl(props = {}) {
2022
2101
  ? getFieldValue(field._f)
2023
2102
  : getEventValue(event);
2024
2103
  const isBlurEvent = event.type === EVENTS.BLUR || event.type === EVENTS.FOCUS_OUT;
2025
- const shouldSkipValidation = (!hasValidation(field._f) &&
2104
+ const hasNoValidationEffect = !hasValidation(field._f) &&
2026
2105
  !props.validate &&
2027
2106
  !_options.resolver &&
2028
2107
  !get(_formState.errors, name) &&
2029
- !field._f.deps) ||
2030
- skipValidation(isBlurEvent, get(_formState.touchedFields, name), _formState.isSubmitted, validationModeAfterSubmit, validationModeBeforeSubmit);
2108
+ !field._f.deps;
2109
+ const shouldSkipValidation = hasNoValidationEffect ||
2110
+ skipValidation(isBlurEvent, get(_formState.touchedFields, name), _formState.isSubmitted, _validationModeAfterSubmit, _validationModeBeforeSubmit);
2031
2111
  const watched = isWatched(name, _names, isBlurEvent);
2032
2112
  set(_formValues, name, fieldValue);
2033
2113
  if (isBlurEvent) {
@@ -2045,10 +2125,13 @@ function createFormControl(props = {}) {
2045
2125
  _subjects.state.next({
2046
2126
  name,
2047
2127
  type: event.type,
2048
- values: cloneObject(_formValues),
2128
+ ...(_valuesSubscriberCount
2129
+ ? { values: cloneObject(_formValues) }
2130
+ : {}),
2049
2131
  });
2050
2132
  if (shouldSkipValidation) {
2051
- if (_proxyFormState.isValid || _proxySubscribeFormState.isValid) {
2133
+ if ((!hasNoValidationEffect || !_formState.isValid) &&
2134
+ (_proxyFormState.isValid || _proxySubscribeFormState.isValid)) {
2052
2135
  if (_options.mode === 'onBlur') {
2053
2136
  if (isBlurEvent) {
2054
2137
  _setValid();
@@ -2072,13 +2155,15 @@ function createFormControl(props = {}) {
2072
2155
  const { errors } = await _runSchema([name]);
2073
2156
  _updateIsValidating([name]);
2074
2157
  _updateIsFieldValueUpdated(fieldValue);
2075
- if (isFieldValueUpdated) {
2076
- const previousErrorLookupResult = schemaErrorLookup(_formState.errors, _fields, name);
2077
- const errorLookupResult = schemaErrorLookup(errors, _fields, previousErrorLookupResult.name || name);
2078
- error = errorLookupResult.error;
2079
- name = errorLookupResult.name;
2080
- isValid = isEmptyObject(errors);
2158
+ if (!isFieldValueUpdated) {
2159
+ !isEmptyObject(fieldState) && _subjects.state.next(fieldState);
2160
+ return;
2081
2161
  }
2162
+ const previousErrorLookupResult = schemaErrorLookup(_formState.errors, _fields, name);
2163
+ const errorLookupResult = schemaErrorLookup(errors, _fields, previousErrorLookupResult.name || name);
2164
+ error = errorLookupResult.error;
2165
+ name = errorLookupResult.name;
2166
+ isValid = isEmptyObject(errors);
2082
2167
  }
2083
2168
  else {
2084
2169
  _updateIsValidating([name], true);
@@ -2181,8 +2266,6 @@ function createFormControl(props = {}) {
2181
2266
  const names = name ? convertToArrayPayload(name) : undefined;
2182
2267
  names?.forEach((inputName) => unset(_formState.errors, inputName));
2183
2268
  if (names) {
2184
- // Emit for each cleared field with the field name so that
2185
- // shouldSubscribeByName can filter and avoid broad re-renders
2186
2269
  names.forEach((inputName) => {
2187
2270
  _subjects.state.next({
2188
2271
  name: inputName,
@@ -2191,7 +2274,6 @@ function createFormControl(props = {}) {
2191
2274
  });
2192
2275
  }
2193
2276
  else {
2194
- // Clear all errors - emit without name to notify all subscribers
2195
2277
  _subjects.state.next({
2196
2278
  errors: {},
2197
2279
  });
@@ -2200,7 +2282,6 @@ function createFormControl(props = {}) {
2200
2282
  const setError = (name, error, options) => {
2201
2283
  const ref = (get(_fields, name, { _f: {} })._f || {}).ref;
2202
2284
  const currentError = get(_formState.errors, name) || {};
2203
- // Don't override existing error messages elsewhere in the object tree.
2204
2285
  const { ref: currentRef, message, type, ...restOfErrorTree } = currentError;
2205
2286
  set(_formState.errors, name, {
2206
2287
  ...restOfErrorTree,
@@ -2214,21 +2295,59 @@ function createFormControl(props = {}) {
2214
2295
  });
2215
2296
  options && options.shouldFocus && ref && ref.focus && ref.focus();
2216
2297
  };
2217
- const watch = (name, defaultValue) => _getWatch(name, defaultValue, true);
2218
- const _subscribe = (props) => _subjects.state.subscribe({
2219
- next: (formState) => {
2220
- if (shouldSubscribeByName(props.name, formState.name, props.exact) &&
2221
- shouldRenderFormState(formState, props.formState || _proxyFormState, _setFormState, props.reRenderRoot)) {
2222
- const snapshot = { ..._formValues };
2223
- props.callback({
2224
- values: snapshot,
2225
- ..._formState,
2226
- ...formState,
2227
- defaultValues: _defaultValues,
2228
- });
2298
+ const watch = (name, defaultValue) => {
2299
+ if (isFunction(name)) {
2300
+ _valuesSubscriberCount++;
2301
+ const { unsubscribe } = _subjects.state.subscribe({
2302
+ next: (payload) => 'values' in payload &&
2303
+ name(payload.values || _getWatch(undefined, defaultValue), payload),
2304
+ });
2305
+ let called = false;
2306
+ return {
2307
+ unsubscribe: () => {
2308
+ if (called) {
2309
+ return;
2310
+ }
2311
+ called = true;
2312
+ _valuesSubscriberCount--;
2313
+ unsubscribe();
2314
+ },
2315
+ };
2316
+ }
2317
+ return _getWatch(name, defaultValue, true);
2318
+ };
2319
+ const _subscribe = (props) => {
2320
+ const needsValues = !!props.formState?.values;
2321
+ if (needsValues) {
2322
+ _valuesSubscriberCount++;
2323
+ }
2324
+ const { unsubscribe } = _subjects.state.subscribe({
2325
+ next: (formState) => {
2326
+ if (shouldSubscribeByName(props.name, formState.name, props.exact) &&
2327
+ shouldRenderFormState(formState, props.formState || _proxyFormState, _setFormState, props.reRenderRoot)) {
2328
+ const snapshot = { ..._formValues };
2329
+ props.callback({
2330
+ values: snapshot,
2331
+ ..._formState,
2332
+ ...formState,
2333
+ defaultValues: _defaultValues,
2334
+ });
2335
+ }
2336
+ },
2337
+ });
2338
+ if (!needsValues) {
2339
+ return unsubscribe;
2340
+ }
2341
+ let called = false;
2342
+ return () => {
2343
+ if (called) {
2344
+ return;
2229
2345
  }
2230
- },
2231
- }).unsubscribe;
2346
+ called = true;
2347
+ _valuesSubscriberCount--;
2348
+ unsubscribe();
2349
+ };
2350
+ };
2232
2351
  const subscribe = (props) => {
2233
2352
  _state.mount = true;
2234
2353
  _proxySubscribeFormState = {
@@ -2363,8 +2482,7 @@ function createFormControl(props = {}) {
2363
2482
  field._f.mount = false;
2364
2483
  }
2365
2484
  (_options.shouldUnregister || options.shouldUnregister) &&
2366
- !(getFieldArrayParentNames(_names.array, name).length &&
2367
- _state.action) &&
2485
+ !(isNameInFieldArray(_names.array, name) && _state.action) &&
2368
2486
  _names.unMount.add(name);
2369
2487
  }
2370
2488
  },
@@ -2476,7 +2594,7 @@ function createFormControl(props = {}) {
2476
2594
  const updatedValues = formValues ? cloneObject(formValues) : _defaultValues;
2477
2595
  const cloneUpdatedValues = cloneObject(updatedValues);
2478
2596
  const isEmptyResetValues = isEmptyObject(formValues);
2479
- const values = isEmptyResetValues ? _defaultValues : cloneUpdatedValues;
2597
+ const values = cloneUpdatedValues;
2480
2598
  if (!keepStateOptions.keepDefaultValues) {
2481
2599
  _defaultValues = updatedValues;
2482
2600
  }
@@ -2525,15 +2643,25 @@ function createFormControl(props = {}) {
2525
2643
  _fields = {};
2526
2644
  }
2527
2645
  }
2528
- _formValues = _options.shouldUnregister
2529
- ? keepStateOptions.keepDefaultValues
2646
+ if (_options.shouldUnregister) {
2647
+ _formValues = keepStateOptions.keepDefaultValues
2530
2648
  ? cloneObject(_defaultValues)
2531
- : {}
2532
- : cloneObject(values);
2649
+ : {};
2650
+ if (keepStateOptions.keepFieldsRef) {
2651
+ for (const fieldName of _names.mount) {
2652
+ set(_formValues, fieldName, get(values, fieldName));
2653
+ }
2654
+ }
2655
+ }
2656
+ else {
2657
+ _formValues = cloneObject(values);
2658
+ }
2533
2659
  _subjects.array.next({
2534
2660
  values: { ...values },
2535
2661
  });
2536
2662
  _subjects.state.next({
2663
+ name: undefined,
2664
+ type: undefined,
2537
2665
  values: { ...values },
2538
2666
  });
2539
2667
  }
@@ -2555,9 +2683,6 @@ function createFormControl(props = {}) {
2555
2683
  _state.watch = !!_options.shouldUnregister;
2556
2684
  _state.keepIsValid = !!keepStateOptions.keepIsValid;
2557
2685
  _state.action = false;
2558
- // Clear errors synchronously to prevent validation errors on subsequent submissions
2559
- // This fixes the issue where form.reset() causes validation errors on subsequent
2560
- // submissions in Next.js 16 with Server Actions
2561
2686
  if (!keepStateOptions.keepErrors) {
2562
2687
  _formState.errors = {};
2563
2688
  }
@@ -2569,8 +2694,10 @@ function createFormControl(props = {}) {
2569
2694
  ? false
2570
2695
  : keepStateOptions.keepDirty
2571
2696
  ? _formState.isDirty
2572
- : !!(keepStateOptions.keepDefaultValues &&
2573
- !deepEqual(formValues, _defaultValues)),
2697
+ : keepStateOptions.keepValues
2698
+ ? _getDirty()
2699
+ : !!(keepStateOptions.keepDefaultValues &&
2700
+ !deepEqual(formValues, _defaultValues)),
2574
2701
  isSubmitted: keepStateOptions.keepIsSubmitted
2575
2702
  ? _formState.isSubmitted
2576
2703
  : false,
@@ -2607,8 +2734,6 @@ function createFormControl(props = {}) {
2607
2734
  ? fieldReference.refs[0]
2608
2735
  : fieldReference.ref;
2609
2736
  if (fieldRef.focus) {
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
2737
  setTimeout(() => {
2613
2738
  fieldRef.focus();
2614
2739
  options.shouldSelect &&
@@ -2619,9 +2744,15 @@ function createFormControl(props = {}) {
2619
2744
  }
2620
2745
  };
2621
2746
  const _setFormState = (updatedFormState) => {
2747
+ // `name`, `type`, and `values` describe the event that produced this
2748
+ // update, not the form's persisted state (they aren't part of
2749
+ // `FormState`). Merging them in would leak a stale `name`/`type` from
2750
+ // one event into a later, unrelated notification that doesn't specify
2751
+ // its own.
2752
+ const { name, type, values, ...formState } = updatedFormState;
2622
2753
  _formState = {
2623
2754
  ..._formState,
2624
- ...updatedFormState,
2755
+ ...formState,
2625
2756
  };
2626
2757
  };
2627
2758
  const _resetDefaultValues = () => isFunction(_options.defaultValues) &&
@@ -2631,6 +2762,21 @@ function createFormControl(props = {}) {
2631
2762
  isLoading: false,
2632
2763
  });
2633
2764
  });
2765
+ const resetDefaultValues = (values, options = {}) => {
2766
+ _defaultValues = cloneObject(values);
2767
+ if (!options.keepDirty) {
2768
+ const newDirtyFields = getDirtyFields(_defaultValues, _formValues);
2769
+ _formState.dirtyFields = newDirtyFields;
2770
+ _formState.isDirty = !isEmptyObject(newDirtyFields);
2771
+ }
2772
+ if (!options.keepIsValid) {
2773
+ _setValid();
2774
+ }
2775
+ _subjects.state.next({
2776
+ ..._formState,
2777
+ defaultValues: _defaultValues,
2778
+ });
2779
+ };
2634
2780
  const methods = {
2635
2781
  control: {
2636
2782
  register,
@@ -2687,6 +2833,8 @@ function createFormControl(props = {}) {
2687
2833
  ..._options,
2688
2834
  ...value,
2689
2835
  };
2836
+ _validationModeBeforeSubmit = getValidationModes(_options.mode);
2837
+ _validationModeAfterSubmit = getValidationModes(_options.reValidateMode);
2690
2838
  },
2691
2839
  },
2692
2840
  subscribe,
@@ -2699,6 +2847,7 @@ function createFormControl(props = {}) {
2699
2847
  getValues,
2700
2848
  reset,
2701
2849
  resetField,
2850
+ resetDefaultValues,
2702
2851
  clearErrors,
2703
2852
  unregister,
2704
2853
  setError,
@@ -2819,30 +2968,45 @@ var updateAt = (fieldValues, index, value) => {
2819
2968
  */
2820
2969
  function useFieldArray(props) {
2821
2970
  const formControl = useFormControlContext();
2822
- const { control = formControl, name, shouldUnregister, rules } = props;
2971
+ const { control = formControl, name, shouldUnregister, rules, disabled, } = props;
2823
2972
  const [fields, setFields] = React.useState(control._getFieldArray(name));
2824
2973
  const ids = React.useRef(control._getFieldArray(name).map(generateId));
2825
2974
  const _actioned = React.useRef(false);
2826
- control._names.array.add(name);
2827
- React.useMemo(() => rules &&
2975
+ if (!disabled) {
2976
+ control._names.array.add(name);
2977
+ }
2978
+ React.useMemo(() => !disabled &&
2979
+ rules &&
2828
2980
  fields.length >= 0 &&
2829
- control.register(name, rules), [control, name, fields.length, rules]);
2830
- useIsomorphicLayoutEffect(() => control._subjects.array.subscribe({
2831
- next: ({ values, name: fieldArrayName, }) => {
2832
- if (fieldArrayName === name || !fieldArrayName) {
2833
- const fieldValues = get(values, name);
2834
- if (Array.isArray(fieldValues)) {
2835
- setFields(fieldValues);
2836
- ids.current = fieldValues.map(generateId);
2981
+ control.register(name, rules), [control, name, fields.length, rules, disabled]);
2982
+ useIsomorphicLayoutEffect(() => {
2983
+ if (disabled) {
2984
+ return;
2985
+ }
2986
+ return control._subjects.array.subscribe({
2987
+ next: ({ values, name: fieldArrayName, }) => {
2988
+ if (fieldArrayName === name || !fieldArrayName) {
2989
+ const fieldValues = get(values, name);
2990
+ if (Array.isArray(fieldValues)) {
2991
+ setFields(fieldValues);
2992
+ ids.current = fieldValues.map(generateId);
2993
+ }
2994
+ else if (!fieldArrayName) {
2995
+ setFields([]);
2996
+ ids.current = [];
2997
+ }
2837
2998
  }
2838
- }
2839
- },
2840
- }).unsubscribe, [control, name]);
2999
+ },
3000
+ }).unsubscribe;
3001
+ }, [control, name, disabled]);
2841
3002
  const updateValues = React.useCallback((updatedFieldArrayValues) => {
2842
3003
  _actioned.current = true;
2843
3004
  control._setFieldArray(name, updatedFieldArrayValues);
2844
3005
  }, [control, name]);
2845
3006
  const append = (value, options) => {
3007
+ if (disabled) {
3008
+ return;
3009
+ }
2846
3010
  const appendValue = convertToArrayPayload(cloneObject(value));
2847
3011
  const updatedFieldArrayValues = appendAt(control._getFieldArray(name), appendValue);
2848
3012
  control._names.focus = getFocusFieldName(name, updatedFieldArrayValues.length - 1, options);
@@ -2854,6 +3018,9 @@ function useFieldArray(props) {
2854
3018
  });
2855
3019
  };
2856
3020
  const prepend = (value, options) => {
3021
+ if (disabled) {
3022
+ return;
3023
+ }
2857
3024
  const prependValue = convertToArrayPayload(cloneObject(value));
2858
3025
  const updatedFieldArrayValues = prependAt(control._getFieldArray(name), prependValue);
2859
3026
  control._names.focus = getFocusFieldName(name, 0, options);
@@ -2865,6 +3032,9 @@ function useFieldArray(props) {
2865
3032
  });
2866
3033
  };
2867
3034
  const remove = (index) => {
3035
+ if (disabled) {
3036
+ return;
3037
+ }
2868
3038
  const updatedFieldArrayValues = removeArrayAt(control._getFieldArray(name), index);
2869
3039
  ids.current = removeArrayAt(ids.current, index);
2870
3040
  updateValues(updatedFieldArrayValues);
@@ -2876,6 +3046,9 @@ function useFieldArray(props) {
2876
3046
  });
2877
3047
  };
2878
3048
  const insert$1 = (index, value, options) => {
3049
+ if (disabled) {
3050
+ return;
3051
+ }
2879
3052
  const insertValue = convertToArrayPayload(cloneObject(value));
2880
3053
  const updatedFieldArrayValues = insert(control._getFieldArray(name), index, insertValue);
2881
3054
  control._names.focus = getFocusFieldName(name, index, options);
@@ -2888,6 +3061,9 @@ function useFieldArray(props) {
2888
3061
  });
2889
3062
  };
2890
3063
  const swap = (indexA, indexB) => {
3064
+ if (disabled) {
3065
+ return;
3066
+ }
2891
3067
  const updatedFieldArrayValues = control._getFieldArray(name);
2892
3068
  swapArrayAt(updatedFieldArrayValues, indexA, indexB);
2893
3069
  swapArrayAt(ids.current, indexA, indexB);
@@ -2899,6 +3075,9 @@ function useFieldArray(props) {
2899
3075
  }, false);
2900
3076
  };
2901
3077
  const move = (from, to) => {
3078
+ if (disabled) {
3079
+ return;
3080
+ }
2902
3081
  const updatedFieldArrayValues = control._getFieldArray(name);
2903
3082
  moveArrayAt(updatedFieldArrayValues, from, to);
2904
3083
  moveArrayAt(ids.current, from, to);
@@ -2910,6 +3089,9 @@ function useFieldArray(props) {
2910
3089
  }, false);
2911
3090
  };
2912
3091
  const update = (index, value) => {
3092
+ if (disabled) {
3093
+ return;
3094
+ }
2913
3095
  const updateValue = cloneObject(value);
2914
3096
  const updatedFieldArrayValues = updateAt(control._getFieldArray(name), index, updateValue);
2915
3097
  ids.current = [...updatedFieldArrayValues].map((item, i) => !item || i === index ? generateId() : ids.current[i]);
@@ -2921,6 +3103,9 @@ function useFieldArray(props) {
2921
3103
  }, true, false);
2922
3104
  };
2923
3105
  const replace = (value) => {
3106
+ if (disabled) {
3107
+ return;
3108
+ }
2924
3109
  const updatedFieldArrayValues = convertToArrayPayload(cloneObject(value));
2925
3110
  ids.current = updatedFieldArrayValues.map(generateId);
2926
3111
  updateValues([...updatedFieldArrayValues]);
@@ -2928,6 +3113,9 @@ function useFieldArray(props) {
2928
3113
  control._setFieldArray(name, [...updatedFieldArrayValues], (data) => data, {}, true, false);
2929
3114
  };
2930
3115
  React.useEffect(() => {
3116
+ if (disabled) {
3117
+ return;
3118
+ }
2931
3119
  control._state.action = false;
2932
3120
  isWatched(name, control._names) &&
2933
3121
  control._subjects.state.next({
@@ -2943,15 +3131,24 @@ function useFieldArray(props) {
2943
3131
  control._updateIsValidating([name]);
2944
3132
  const error = get(result.errors, name);
2945
3133
  const existingError = get(control._formState.errors, name);
3134
+ const existingErrorType = existingError && (existingError.type || existingError.root?.type);
3135
+ const existingErrorMessage = existingError &&
3136
+ (existingError.message || existingError.root?.message);
2946
3137
  if (existingError
2947
- ? (!error && existingError.type) ||
3138
+ ? (!error && existingErrorType) ||
2948
3139
  (error &&
2949
- (existingError.type !== error.type ||
2950
- existingError.message !== error.message))
3140
+ (existingErrorType !== error.type ||
3141
+ existingErrorMessage !== error.message))
2951
3142
  : error && error.type) {
2952
- error
2953
- ? set(control._formState.errors, name, error)
2954
- : unset(control._formState.errors, name);
3143
+ if (error) {
3144
+ isObject(error) &&
3145
+ !Object.keys(error).some((key) => !Number.isNaN(+key))
3146
+ ? updateFieldArrayRootError(control._formState.errors, { [name]: error }, name)
3147
+ : set(control._formState.errors, name, error);
3148
+ }
3149
+ else {
3150
+ unset(control._formState.errors, name);
3151
+ }
2955
3152
  control._subjects.state.next({
2956
3153
  errors: control._formState.errors,
2957
3154
  });
@@ -2971,10 +3168,14 @@ function useFieldArray(props) {
2971
3168
  }
2972
3169
  }
2973
3170
  }
2974
- control._subjects.state.next({
2975
- name,
2976
- values: cloneObject(control._formValues),
2977
- });
3171
+ // External updates that change `fields` (e.g. reset() or setValue() on
3172
+ // the array) already notify subscribers with the up-to-date values
3173
+ // themselves, so only re-broadcast here for genuine array method calls.
3174
+ _actioned.current &&
3175
+ control._subjects.state.next({
3176
+ name,
3177
+ values: cloneObject(control._formValues),
3178
+ });
2978
3179
  control._names.focus &&
2979
3180
  iterateFieldsByAction(control._fields, (ref, key) => {
2980
3181
  if (control._names.focus &&
@@ -2988,10 +3189,15 @@ function useFieldArray(props) {
2988
3189
  control._names.focus = '';
2989
3190
  control._setValid();
2990
3191
  _actioned.current = false;
2991
- }, [fields, name, control]);
3192
+ }, [fields, name, control, disabled]);
2992
3193
  React.useEffect(() => {
2993
- !get(control._formValues, name) && control._setFieldArray(name);
3194
+ if (!disabled) {
3195
+ !get(control._formValues, name) && control._setFieldArray(name);
3196
+ }
2994
3197
  return () => {
3198
+ if (disabled) {
3199
+ return;
3200
+ }
2995
3201
  const shouldKeepFieldArrayValues = !(control._options.shouldUnregister || shouldUnregister);
2996
3202
  const updateMounted = (name, value) => {
2997
3203
  const field = get(control._fields, name);
@@ -3009,20 +3215,31 @@ function useFieldArray(props) {
3009
3215
  ? updateMounted(name, false)
3010
3216
  : control.unregister(name);
3011
3217
  };
3012
- }, [name, control, shouldUnregister]);
3218
+ }, [name, control, shouldUnregister, disabled]);
3013
3219
  return {
3014
- swap: React.useCallback(swap, [updateValues, name, control]),
3015
- move: React.useCallback(move, [updateValues, name, control]),
3016
- prepend: React.useCallback(prepend, [updateValues, name, control]),
3017
- append: React.useCallback(append, [updateValues, name, control]),
3018
- remove: React.useCallback(remove, [updateValues, name, control]),
3019
- insert: React.useCallback(insert$1, [updateValues, name, control]),
3020
- update: React.useCallback(update, [updateValues, name, control]),
3021
- replace: React.useCallback(replace, [updateValues, name, control]),
3220
+ swap: React.useCallback(swap, [updateValues, name, control, disabled]),
3221
+ move: React.useCallback(move, [updateValues, name, control, disabled]),
3222
+ prepend: React.useCallback(prepend, [
3223
+ updateValues,
3224
+ name,
3225
+ control,
3226
+ disabled,
3227
+ ]),
3228
+ append: React.useCallback(append, [updateValues, name, control, disabled]),
3229
+ remove: React.useCallback(remove, [updateValues, name, control, disabled]),
3230
+ insert: React.useCallback(insert$1, [updateValues, name, control, disabled]),
3231
+ update: React.useCallback(update, [updateValues, name, control, disabled]),
3232
+ replace: React.useCallback(replace, [
3233
+ updateValues,
3234
+ name,
3235
+ control,
3236
+ disabled,
3237
+ ]),
3022
3238
  fields: React.useMemo(() => fields.map((field, index) => ({
3023
3239
  ...field,
3240
+ ...(isBoolean(disabled) ? { disabled } : {}),
3024
3241
  key: ids.current[index] || generateId(),
3025
- })), [fields]),
3242
+ })), [fields, disabled]),
3026
3243
  };
3027
3244
  }
3028
3245
 
@@ -3069,6 +3286,7 @@ function updateMethodsReference(_formControl) {
3069
3286
  function useForm(props = {}) {
3070
3287
  const _formControl = React.useRef(undefined);
3071
3288
  const _values = React.useRef(undefined);
3289
+ const _formControlProp = React.useRef(props.formControl);
3072
3290
  const [formState, updateFormState] = React.useState(() => ({
3073
3291
  ...cloneObject(DEFAULT_FORM_STATE),
3074
3292
  isLoading: isFunction(props.defaultValues),
@@ -3078,7 +3296,9 @@ function useForm(props = {}) {
3078
3296
  ? undefined
3079
3297
  : props.defaultValues,
3080
3298
  }));
3081
- if (!_formControl.current) {
3299
+ if (!_formControl.current ||
3300
+ (props.formControl && _formControlProp.current !== props.formControl)) {
3301
+ _formControlProp.current = props.formControl;
3082
3302
  if (props.formControl) {
3083
3303
  _formControl.current = {
3084
3304
  ...props.formControl,