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
@@ -31,8 +31,8 @@ const INPUT_VALIDATION_RULES = {
31
31
  required: 'required',
32
32
  validate: 'validate',
33
33
  };
34
- const FORM_ERROR_TYPE = 'form';
35
34
  const ROOT_ERROR_TYPE = 'root';
35
+ const PROTOTYPE_KEYWORDS = ['__proto__', 'constructor', 'prototype'];
36
36
 
37
37
  var isDateObject = (value) => value instanceof Date;
38
38
 
@@ -108,7 +108,8 @@ var createSubject = () => {
108
108
 
109
109
  var isPrimitive = (value) => isNullOrUndefined(value) || !isObjectType(value);
110
110
 
111
- function deepEqual(object1, object2, visited = new WeakSet()) {
111
+ const isEmptyObjectWithCustomPrototype = (object, keys) => keys.length === 0 && !Array.isArray(object) && !isPlainObject(object);
112
+ function deepEqual(object1, object2, visited = new WeakMap()) {
112
113
  if (object1 === object2) {
113
114
  return true;
114
115
  }
@@ -123,11 +124,25 @@ function deepEqual(object1, object2, visited = new WeakSet()) {
123
124
  if (keys1.length !== keys2.length) {
124
125
  return false;
125
126
  }
126
- if (visited.has(object1) || visited.has(object2)) {
127
+ if (isEmptyObjectWithCustomPrototype(object1, keys1) ||
128
+ isEmptyObjectWithCustomPrototype(object2, keys2)) {
129
+ return Object.is(object1, object2);
130
+ }
131
+ if (!keys1.length && Array.isArray(object1) !== Array.isArray(object2)) {
132
+ return false;
133
+ }
134
+ const visitedPairs = visited.get(object1);
135
+ if (visitedPairs && visitedPairs.has(object2)) {
127
136
  return true;
128
137
  }
129
- visited.add(object1);
130
- visited.add(object2);
138
+ if (visitedPairs) {
139
+ visitedPairs.add(object2);
140
+ }
141
+ else {
142
+ const ws = new WeakSet();
143
+ ws.add(object2);
144
+ visited.set(object1, ws);
145
+ }
131
146
  for (const key of keys1) {
132
147
  const val1 = object1[key];
133
148
  if (!(key in object2)) {
@@ -167,17 +182,40 @@ function extractFormValues(fieldsState, formValues) {
167
182
  return values;
168
183
  }
169
184
 
170
- var isKey = (value) => /^\w*$/.test(value);
185
+ const flatten = (obj) => {
186
+ const output = {};
187
+ for (const key of Object.keys(obj)) {
188
+ if (isObjectType(obj[key]) &&
189
+ obj[key] !== null &&
190
+ !isDateObject(obj[key])) {
191
+ const nested = flatten(obj[key]);
192
+ for (const nestedKey of Object.keys(nested)) {
193
+ output[`${key}.${nestedKey}`] = nested[nestedKey];
194
+ }
195
+ }
196
+ else {
197
+ output[key] = obj[key];
198
+ }
199
+ }
200
+ return output;
201
+ };
202
+
203
+ const IS_KEY_RE = /^\w*$/;
204
+ var isKey = (value) => IS_KEY_RE.test(value);
171
205
 
172
206
  var isUndefined = (val) => val === undefined;
173
207
 
174
- var stringToPath = (input) => compact(input.replace(/["|']|\]/g, '').split(/\.|\[/));
208
+ const FIELD_PATH_RE = /[.[\]'"]/;
209
+ var stringToPath = (input) => input.split(FIELD_PATH_RE).filter(Boolean);
175
210
 
176
211
  var get = (object, path, defaultValue) => {
177
212
  if (!path || !isObject(object)) {
178
213
  return defaultValue;
179
214
  }
180
215
  const paths = isKey(path) ? [path] : stringToPath(path);
216
+ if (paths.some((key) => PROTOTYPE_KEYWORDS.includes(key))) {
217
+ return defaultValue;
218
+ }
181
219
  const result = paths.reduce((result, key) => {
182
220
  return isNullOrUndefined(result) ? undefined : result[key];
183
221
  }, object);
@@ -234,7 +272,7 @@ var set = (object, path, value) => {
234
272
  ? []
235
273
  : {};
236
274
  }
237
- if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
275
+ if (PROTOTYPE_KEYWORDS.includes(key)) {
238
276
  return;
239
277
  }
240
278
  object[key] = newValue;
@@ -273,6 +311,9 @@ function unset(object, path) {
273
311
  : isKey(path)
274
312
  ? [path]
275
313
  : stringToPath(path);
314
+ if (paths.some((segment) => PROTOTYPE_KEYWORDS.includes(String(segment)))) {
315
+ return object;
316
+ }
276
317
  const childObject = paths.length === 1 ? object : baseGet(object, paths);
277
318
  const index = paths.length - 1;
278
319
  const key = paths[index];
@@ -376,16 +417,6 @@ var getEventValue = (event) => isObject(event) && event.target
376
417
  : event.target.value
377
418
  : event;
378
419
 
379
- var getFieldArrayParentNames = (names, name) => {
380
- const parts = name.split('.');
381
- const matches = [];
382
- let prefix = parts[0];
383
- for (let i = 1; i < parts.length; prefix += '.' + parts[i++]) {
384
- !isNaN(+parts[i]) && names.has(prefix) && matches.push(prefix);
385
- }
386
- return matches;
387
- };
388
-
389
420
  const defaultResult = {
390
421
  value: false,
391
422
  isValid: false,
@@ -490,12 +521,22 @@ var getValidationModes = (mode) => ({
490
521
  });
491
522
 
492
523
  const ASYNC_FUNCTION = 'AsyncFunction';
493
- var hasPromiseValidation = (fieldReference) => !!fieldReference &&
494
- !!fieldReference.validate &&
495
- !!((isFunction(fieldReference.validate) &&
496
- fieldReference.validate.constructor.name === ASYNC_FUNCTION) ||
497
- (isObject(fieldReference.validate) &&
498
- Object.values(fieldReference.validate).find((validateFunction) => validateFunction.constructor.name === ASYNC_FUNCTION)));
524
+ var hasPromiseValidation = (fieldReference) => {
525
+ if (!fieldReference || !fieldReference.validate)
526
+ return false;
527
+ if (isFunction(fieldReference.validate)) {
528
+ return fieldReference.validate.constructor.name === ASYNC_FUNCTION;
529
+ }
530
+ if (isObject(fieldReference.validate)) {
531
+ for (const key in fieldReference.validate) {
532
+ if (fieldReference.validate[key].constructor
533
+ .name === ASYNC_FUNCTION) {
534
+ return true;
535
+ }
536
+ }
537
+ }
538
+ return false;
539
+ };
499
540
 
500
541
  var hasValidation = (options) => options.mount &&
501
542
  (options.required ||
@@ -506,11 +547,21 @@ var hasValidation = (options) => options.mount &&
506
547
  options.pattern ||
507
548
  options.validate);
508
549
 
509
- var isWatched = (name, _names, isBlurEvent) => !isBlurEvent &&
510
- (_names.watchAll ||
511
- _names.watch.has(name) ||
512
- [..._names.watch].some((watchName) => name.startsWith(watchName) &&
513
- /^\.\w+/.test(name.slice(watchName.length))));
550
+ var isNameInFieldArray = (names, name) => name
551
+ .split('.')
552
+ .some((part, index, arr) => !isNaN(Number(part)) && names.has(arr.slice(0, index).join('.')));
553
+
554
+ var isWatched = (name, _names, isBlurEvent) => {
555
+ if (isBlurEvent)
556
+ return false;
557
+ if (_names.watchAll || _names.watch.has(name))
558
+ return true;
559
+ for (const watchName of _names.watch) {
560
+ if (name.startsWith(watchName) && name.charAt(watchName.length) === '.')
561
+ return true;
562
+ }
563
+ return false;
564
+ };
514
565
 
515
566
  const iterateFieldsByAction = (fields, action, fieldsNames, abortEarly) => {
516
567
  for (const key of fieldsNames || Object.keys(fields)) {
@@ -578,10 +629,10 @@ function schemaErrorLookup(errors, _fields, name) {
578
629
  var shouldRenderFormState = (formStateData, _proxyFormState, updateFormState, isRoot) => {
579
630
  updateFormState(formStateData);
580
631
  const { name, ...formState } = formStateData;
581
- return (isEmptyObject(formState) ||
582
- (isRoot &&
583
- Object.keys(formState).length >= Object.keys(_proxyFormState).length) ||
584
- Object.keys(formState).find((key) => _proxyFormState[key] ===
632
+ const keys = Object.keys(formState);
633
+ return (!keys.length ||
634
+ (isRoot && keys.length >= Object.keys(_proxyFormState).length) ||
635
+ keys.find((key) => _proxyFormState[key] ===
585
636
  (!isRoot || VALIDATION_MODE.all)));
586
637
  };
587
638
 
@@ -590,7 +641,7 @@ var shouldSubscribeByName = (name, signalName, exact) => !name ||
590
641
  name === signalName ||
591
642
  convertToArrayPayload(name).some((currentName) => currentName &&
592
643
  (exact
593
- ? currentName === signalName
644
+ ? currentName === signalName || currentName.startsWith(signalName + '.')
594
645
  : currentName.startsWith(signalName) ||
595
646
  signalName.startsWith(currentName)));
596
647
 
@@ -613,7 +664,8 @@ var skipValidation = (isBlurEvent, isTouched, isSubmitted, reValidateMode, mode)
613
664
  var unsetEmptyArray = (ref, name) => !compact(get(ref, name)).length && unset(ref, name);
614
665
 
615
666
  var updateFieldArrayRootError = (errors, error, name) => {
616
- const fieldArrayErrors = convertToArrayPayload(get(errors, name));
667
+ const existingErrors = get(errors, name);
668
+ const fieldArrayErrors = Array.isArray(existingErrors) ? existingErrors : [];
617
669
  set(fieldArrayErrors, ROOT_ERROR_TYPE, error[name]);
618
670
  set(errors, name, fieldArrayErrors);
619
671
  return errors;
@@ -647,7 +699,13 @@ var validateField = async (field, disabledFieldNames, formValues, validateAllFie
647
699
  const inputRef = refs ? refs[0] : ref;
648
700
  const setCustomValidity = (message) => {
649
701
  if (shouldUseNativeValidation && inputRef.reportValidity) {
650
- inputRef.setCustomValidity(isBoolean(message) ? '' : message || '');
702
+ const validityMessage = isBoolean(message) ? '' : message || '';
703
+ if (refs) {
704
+ refs.forEach((ref) => ref.setCustomValidity(validityMessage));
705
+ }
706
+ else {
707
+ inputRef.setCustomValidity(validityMessage);
708
+ }
651
709
  inputRef.reportValidity();
652
710
  }
653
711
  };
@@ -660,8 +718,7 @@ var validateField = async (field, disabledFieldNames, formValues, validateAllFie
660
718
  isUndefined(inputValue)) ||
661
719
  (isHTMLElement(ref) && ref.value === '') ||
662
720
  inputValue === '' ||
663
- (Array.isArray(inputValue) && !inputValue.length) ||
664
- (valueAsNumber && typeof inputValue === 'number' && isNaN(inputValue));
721
+ (Array.isArray(inputValue) && !inputValue.length);
665
722
  const appendErrorsCurry = appendErrors.bind(null, name, validateAllFieldCriteria, error);
666
723
  const getMinMaxMessage = (exceedMax, maxLengthMessage, minLengthMessage, maxType = INPUT_VALIDATION_RULES.maxLength, minType = INPUT_VALIDATION_RULES.minLength) => {
667
724
  const message = exceedMax ? maxLengthMessage : minLengthMessage;
@@ -823,6 +880,7 @@ const defaultOptions = {
823
880
  reValidateMode: VALIDATION_MODE.onChange,
824
881
  shouldFocusError: true,
825
882
  };
883
+ const FORM_ERROR_TYPE = 'form';
826
884
  const DEFAULT_FORM_STATE = {
827
885
  submitCount: 0,
828
886
  isDirty: false,
@@ -870,6 +928,9 @@ function createFormControl(props = {}) {
870
928
  };
871
929
  let delayErrorCallback;
872
930
  let timer = 0;
931
+ let _valuesSubscriberCount = 0;
932
+ let _validationModeBeforeSubmit = getValidationModes(_options.mode);
933
+ let _validationModeAfterSubmit = getValidationModes(_options.reValidateMode);
873
934
  const defaultProxyFormState = {
874
935
  isDirty: false,
875
936
  dirtyFields: false,
@@ -980,6 +1041,7 @@ function createFormControl(props = {}) {
980
1041
  };
981
1042
  const updateErrors = (name, error) => {
982
1043
  set(_formState.errors, name, error);
1044
+ _formState.errors = { ..._formState.errors };
983
1045
  _subjects.state.next({
984
1046
  errors: _formState.errors,
985
1047
  });
@@ -1022,12 +1084,6 @@ function createFormControl(props = {}) {
1022
1084
  : setFieldValue(name, defaultValue);
1023
1085
  if (_state.mount && !_state.action) {
1024
1086
  _setValid();
1025
- // Re-registering a field after a prior unregister puts its key back
1026
- // into _formValues, which can flip isDirty back to false (#13397).
1027
- // Only run when we are currently dirty, otherwise an initial register
1028
- // for a field with no defaultValue would flip isDirty to true. Reset
1029
- // paths repopulate _formValues before re-register, so the key is
1030
- // present then and this branch is skipped (preserves keepDirty).
1031
1087
  if (wasUnsetInFormValues &&
1032
1088
  _formState.isDirty &&
1033
1089
  (_proxyFormState.isDirty || _proxySubscribeFormState.isDirty)) {
@@ -1037,6 +1093,12 @@ function createFormControl(props = {}) {
1037
1093
  _subjects.state.next({ ..._formState });
1038
1094
  }
1039
1095
  }
1096
+ if (props.shouldUnregister &&
1097
+ wasUnsetInFormValues &&
1098
+ !isUndefined(get(_formValues, name)) &&
1099
+ isWatched(name, _names)) {
1100
+ _state.watch = true;
1101
+ }
1040
1102
  }
1041
1103
  }
1042
1104
  };
@@ -1048,12 +1110,13 @@ function createFormControl(props = {}) {
1048
1110
  };
1049
1111
  if (!_options.disabled) {
1050
1112
  if (!isBlurEvent || shouldDirty) {
1113
+ const isCurrentFieldPristine = deepEqual(get(_defaultValues, name), fieldValue);
1051
1114
  if (_proxyFormState.isDirty || _proxySubscribeFormState.isDirty) {
1052
1115
  isPreviousDirty = _formState.isDirty;
1053
- _formState.isDirty = output.isDirty = _getDirty();
1116
+ _formState.isDirty = output.isDirty =
1117
+ !isCurrentFieldPristine || _getDirty();
1054
1118
  shouldUpdateField = isPreviousDirty !== output.isDirty;
1055
1119
  }
1056
- const isCurrentFieldPristine = deepEqual(get(_defaultValues, name), fieldValue);
1057
1120
  isPreviousDirty = !!get(_formState.dirtyFields, name);
1058
1121
  if (isCurrentFieldPristine !== _formState.isDirty) {
1059
1122
  _formState.dirtyFields = getDirtyFields(_defaultValues, _formValues);
@@ -1101,6 +1164,7 @@ function createFormControl(props = {}) {
1101
1164
  error
1102
1165
  ? set(_formState.errors, name, error)
1103
1166
  : unset(_formState.errors, name);
1167
+ _formState.errors = { ..._formState.errors };
1104
1168
  }
1105
1169
  if ((error ? !deepEqual(previousFieldError, error) : previousFieldError) ||
1106
1170
  !isEmptyObject(fieldState) ||
@@ -1129,11 +1193,14 @@ function createFormControl(props = {}) {
1129
1193
  for (const name of names) {
1130
1194
  const error = get(errors, name);
1131
1195
  error
1132
- ? _names.array.has(name) && isObject(error)
1196
+ ? _names.array.has(name) &&
1197
+ isObject(error) &&
1198
+ !Object.keys(error).some((key) => !Number.isNaN(Number(key)))
1133
1199
  ? updateFieldArrayRootError(_formState.errors, { [name]: error }, name)
1134
1200
  : set(_formState.errors, name, error)
1135
1201
  : unset(_formState.errors, name);
1136
1202
  }
1203
+ _formState.errors = { ..._formState.errors };
1137
1204
  }
1138
1205
  else {
1139
1206
  _formState.errors = errors;
@@ -1248,7 +1315,7 @@ function createFormControl(props = {}) {
1248
1315
  };
1249
1316
  const _getDirty = (name, data) => !_options.disabled &&
1250
1317
  (name && data && set(_formValues, name, data),
1251
- !deepEqual(getValues(), _defaultValues));
1318
+ !deepEqual(_state.mount ? _formValues : _defaultValues, _defaultValues));
1252
1319
  const _getWatch = (names, defaultValue, isGlobal) => generateWatchOutput(names, _names, {
1253
1320
  ...(_state.mount
1254
1321
  ? _formValues
@@ -1259,7 +1326,7 @@ function createFormControl(props = {}) {
1259
1326
  : defaultValue),
1260
1327
  }, isGlobal, defaultValue);
1261
1328
  const _getFieldArray = (name) => compact(get(_state.mount ? _formValues : _defaultValues, name, _options.shouldUnregister ? get(_defaultValues, name, []) : []));
1262
- const setFieldValue = (name, value, options = {}) => {
1329
+ const setFieldValue = (name, value, options = {}, skipClone = false, skipRender = false) => {
1263
1330
  const field = get(_fields, name);
1264
1331
  let fieldValue = value;
1265
1332
  if (field) {
@@ -1297,20 +1364,20 @@ function createFormControl(props = {}) {
1297
1364
  }
1298
1365
  else {
1299
1366
  fieldReference.ref.value = fieldValue;
1300
- if (!fieldReference.ref.type) {
1367
+ if (!fieldReference.ref.type && !skipRender) {
1301
1368
  _subjects.state.next({
1302
1369
  name,
1303
- values: cloneObject(_formValues),
1370
+ values: skipClone ? _formValues : cloneObject(_formValues),
1304
1371
  });
1305
1372
  }
1306
1373
  }
1307
1374
  }
1308
1375
  }
1309
1376
  (options.shouldDirty || options.shouldTouch) &&
1310
- updateTouchAndDirty(name, fieldValue, options.shouldTouch, options.shouldDirty, true);
1377
+ updateTouchAndDirty(name, fieldValue, options.shouldTouch, options.shouldDirty, !skipRender);
1311
1378
  options.shouldValidate && trigger(name);
1312
1379
  };
1313
- const setFieldValues = (name, value, options) => {
1380
+ const setFieldValues = (name, value, options, skipClone = false, skipRender = false) => {
1314
1381
  for (const fieldKey in value) {
1315
1382
  if (!value.hasOwnProperty(fieldKey)) {
1316
1383
  return;
@@ -1322,14 +1389,14 @@ function createFormControl(props = {}) {
1322
1389
  isObject(fieldValue) ||
1323
1390
  (field && !field._f)) &&
1324
1391
  !isDateObject(fieldValue)
1325
- ? setFieldValues(fieldName, fieldValue, options)
1326
- : setFieldValue(fieldName, fieldValue, options);
1392
+ ? setFieldValues(fieldName, fieldValue, options, skipClone, skipRender)
1393
+ : setFieldValue(fieldName, fieldValue, options, skipClone, skipRender);
1327
1394
  }
1328
1395
  };
1329
- const setValue = (name, value, options = {}) => {
1396
+ const _setValue = (name, value, options, skipClone, skipStateEmit = false) => {
1330
1397
  const field = get(_fields, name);
1331
1398
  const isFieldArray = _names.array.has(name);
1332
- const cloneValue = cloneObject(value);
1399
+ const cloneValue = skipClone ? value : cloneObject(value);
1333
1400
  const previousValue = get(_formValues, name);
1334
1401
  const isValueUnchanged = deepEqual(previousValue, cloneValue);
1335
1402
  if (!isValueUnchanged) {
@@ -1338,7 +1405,7 @@ function createFormControl(props = {}) {
1338
1405
  if (isFieldArray) {
1339
1406
  _subjects.array.next({
1340
1407
  name,
1341
- values: cloneObject(_formValues),
1408
+ values: skipClone ? _formValues : cloneObject(_formValues),
1342
1409
  });
1343
1410
  if ((_proxyFormState.isDirty ||
1344
1411
  _proxyFormState.dirtyFields ||
@@ -1346,31 +1413,28 @@ function createFormControl(props = {}) {
1346
1413
  _proxySubscribeFormState.dirtyFields) &&
1347
1414
  options.shouldDirty) {
1348
1415
  _updateDirtyFields();
1349
- _subjects.state.next({
1350
- name,
1351
- dirtyFields: _formState.dirtyFields,
1352
- isDirty: _getDirty(name, cloneValue),
1353
- });
1416
+ if (!skipStateEmit) {
1417
+ _subjects.state.next({
1418
+ name,
1419
+ dirtyFields: _formState.dirtyFields,
1420
+ isDirty: _getDirty(name, cloneValue),
1421
+ });
1422
+ }
1354
1423
  }
1355
1424
  }
1356
1425
  else {
1357
1426
  const isEmpty = (Array.isArray(cloneValue) && !cloneValue.length) ||
1358
1427
  isEmptyObject(cloneValue);
1359
1428
  if (!field || field._f || isNullOrUndefined(cloneValue) || isEmpty) {
1360
- setFieldValue(name, cloneValue, options);
1429
+ setFieldValue(name, cloneValue, options, skipClone, skipStateEmit);
1361
1430
  }
1362
1431
  else {
1363
- setFieldValues(name, cloneValue, options);
1432
+ setFieldValues(name, cloneValue, options, skipClone, skipStateEmit);
1364
1433
  }
1365
1434
  }
1366
- if (!isValueUnchanged) {
1435
+ if (!isValueUnchanged && !skipStateEmit) {
1367
1436
  const watched = isWatched(name, _names);
1368
- const values = cloneObject(_formValues);
1369
- if (!isFieldArray) {
1370
- for (const arrayName of getFieldArrayParentNames(_names.array, name)) {
1371
- _subjects.array.next({ name: arrayName, values });
1372
- }
1373
- }
1437
+ const values = skipClone ? _formValues : cloneObject(_formValues);
1374
1438
  _subjects.state.next({
1375
1439
  ...(watched && _formState),
1376
1440
  name: _state.mount || watched ? name : undefined,
@@ -1378,7 +1442,8 @@ function createFormControl(props = {}) {
1378
1442
  });
1379
1443
  }
1380
1444
  };
1381
- const setValues = (formValues) => {
1445
+ const setValue = (name, value, options = {}) => _setValue(name, value, options, false);
1446
+ const setValues = (formValues, options = {}) => {
1382
1447
  const updatedFormValues = isFunction(formValues)
1383
1448
  ? formValues(_formValues)
1384
1449
  : formValues;
@@ -1387,10 +1452,21 @@ function createFormControl(props = {}) {
1387
1452
  ..._formValues,
1388
1453
  ...updatedFormValues,
1389
1454
  };
1455
+ const flattenedUpdates = flatten(updatedFormValues);
1390
1456
  for (const fieldName of _names.mount) {
1391
- setValue(fieldName, get(updatedFormValues, fieldName));
1457
+ if (fieldName in flattenedUpdates) {
1458
+ _setValue(fieldName, flattenedUpdates[fieldName], options, true, true);
1459
+ }
1460
+ }
1461
+ _subjects.state.next({
1462
+ ..._formState,
1463
+ name: undefined,
1464
+ type: undefined,
1465
+ ...(_valuesSubscriberCount ? { values: _formValues } : {}),
1466
+ });
1467
+ if (options.shouldValidate) {
1468
+ _setValid();
1392
1469
  }
1393
- _subjects.state.next({ ..._formState, values: _formValues });
1394
1470
  }
1395
1471
  };
1396
1472
  const onChange = async (event) => {
@@ -1405,8 +1481,6 @@ function createFormControl(props = {}) {
1405
1481
  (isDateObject(fieldValue) && isNaN(fieldValue.getTime())) ||
1406
1482
  deepEqual(fieldValue, get(_formValues, name, fieldValue));
1407
1483
  };
1408
- const validationModeBeforeSubmit = getValidationModes(_options.mode);
1409
- const validationModeAfterSubmit = getValidationModes(_options.reValidateMode);
1410
1484
  if (field) {
1411
1485
  let error;
1412
1486
  let isValid;
@@ -1414,12 +1488,13 @@ function createFormControl(props = {}) {
1414
1488
  ? getFieldValue(field._f)
1415
1489
  : getEventValue(event);
1416
1490
  const isBlurEvent = event.type === EVENTS.BLUR || event.type === EVENTS.FOCUS_OUT;
1417
- const shouldSkipValidation = (!hasValidation(field._f) &&
1491
+ const hasNoValidationEffect = !hasValidation(field._f) &&
1418
1492
  !props.validate &&
1419
1493
  !_options.resolver &&
1420
1494
  !get(_formState.errors, name) &&
1421
- !field._f.deps) ||
1422
- skipValidation(isBlurEvent, get(_formState.touchedFields, name), _formState.isSubmitted, validationModeAfterSubmit, validationModeBeforeSubmit);
1495
+ !field._f.deps;
1496
+ const shouldSkipValidation = hasNoValidationEffect ||
1497
+ skipValidation(isBlurEvent, get(_formState.touchedFields, name), _formState.isSubmitted, _validationModeAfterSubmit, _validationModeBeforeSubmit);
1423
1498
  const watched = isWatched(name, _names, isBlurEvent);
1424
1499
  set(_formValues, name, fieldValue);
1425
1500
  if (isBlurEvent) {
@@ -1437,10 +1512,13 @@ function createFormControl(props = {}) {
1437
1512
  _subjects.state.next({
1438
1513
  name,
1439
1514
  type: event.type,
1440
- values: cloneObject(_formValues),
1515
+ ...(_valuesSubscriberCount
1516
+ ? { values: cloneObject(_formValues) }
1517
+ : {}),
1441
1518
  });
1442
1519
  if (shouldSkipValidation) {
1443
- if (_proxyFormState.isValid || _proxySubscribeFormState.isValid) {
1520
+ if ((!hasNoValidationEffect || !_formState.isValid) &&
1521
+ (_proxyFormState.isValid || _proxySubscribeFormState.isValid)) {
1444
1522
  if (_options.mode === 'onBlur') {
1445
1523
  if (isBlurEvent) {
1446
1524
  _setValid();
@@ -1464,13 +1542,15 @@ function createFormControl(props = {}) {
1464
1542
  const { errors } = await _runSchema([name]);
1465
1543
  _updateIsValidating([name]);
1466
1544
  _updateIsFieldValueUpdated(fieldValue);
1467
- if (isFieldValueUpdated) {
1468
- const previousErrorLookupResult = schemaErrorLookup(_formState.errors, _fields, name);
1469
- const errorLookupResult = schemaErrorLookup(errors, _fields, previousErrorLookupResult.name || name);
1470
- error = errorLookupResult.error;
1471
- name = errorLookupResult.name;
1472
- isValid = isEmptyObject(errors);
1545
+ if (!isFieldValueUpdated) {
1546
+ !isEmptyObject(fieldState) && _subjects.state.next(fieldState);
1547
+ return;
1473
1548
  }
1549
+ const previousErrorLookupResult = schemaErrorLookup(_formState.errors, _fields, name);
1550
+ const errorLookupResult = schemaErrorLookup(errors, _fields, previousErrorLookupResult.name || name);
1551
+ error = errorLookupResult.error;
1552
+ name = errorLookupResult.name;
1553
+ isValid = isEmptyObject(errors);
1474
1554
  }
1475
1555
  else {
1476
1556
  _updateIsValidating([name], true);
@@ -1573,8 +1653,6 @@ function createFormControl(props = {}) {
1573
1653
  const names = name ? convertToArrayPayload(name) : undefined;
1574
1654
  names?.forEach((inputName) => unset(_formState.errors, inputName));
1575
1655
  if (names) {
1576
- // Emit for each cleared field with the field name so that
1577
- // shouldSubscribeByName can filter and avoid broad re-renders
1578
1656
  names.forEach((inputName) => {
1579
1657
  _subjects.state.next({
1580
1658
  name: inputName,
@@ -1583,7 +1661,6 @@ function createFormControl(props = {}) {
1583
1661
  });
1584
1662
  }
1585
1663
  else {
1586
- // Clear all errors - emit without name to notify all subscribers
1587
1664
  _subjects.state.next({
1588
1665
  errors: {},
1589
1666
  });
@@ -1592,7 +1669,6 @@ function createFormControl(props = {}) {
1592
1669
  const setError = (name, error, options) => {
1593
1670
  const ref = (get(_fields, name, { _f: {} })._f || {}).ref;
1594
1671
  const currentError = get(_formState.errors, name) || {};
1595
- // Don't override existing error messages elsewhere in the object tree.
1596
1672
  const { ref: currentRef, message, type, ...restOfErrorTree } = currentError;
1597
1673
  set(_formState.errors, name, {
1598
1674
  ...restOfErrorTree,
@@ -1606,21 +1682,59 @@ function createFormControl(props = {}) {
1606
1682
  });
1607
1683
  options && options.shouldFocus && ref && ref.focus && ref.focus();
1608
1684
  };
1609
- const watch = (name, defaultValue) => _getWatch(name, defaultValue, true);
1610
- const _subscribe = (props) => _subjects.state.subscribe({
1611
- next: (formState) => {
1612
- if (shouldSubscribeByName(props.name, formState.name, props.exact) &&
1613
- shouldRenderFormState(formState, props.formState || _proxyFormState, _setFormState, props.reRenderRoot)) {
1614
- const snapshot = { ..._formValues };
1615
- props.callback({
1616
- values: snapshot,
1617
- ..._formState,
1618
- ...formState,
1619
- defaultValues: _defaultValues,
1620
- });
1685
+ const watch = (name, defaultValue) => {
1686
+ if (isFunction(name)) {
1687
+ _valuesSubscriberCount++;
1688
+ const { unsubscribe } = _subjects.state.subscribe({
1689
+ next: (payload) => 'values' in payload &&
1690
+ name(payload.values || _getWatch(undefined, defaultValue), payload),
1691
+ });
1692
+ let called = false;
1693
+ return {
1694
+ unsubscribe: () => {
1695
+ if (called) {
1696
+ return;
1697
+ }
1698
+ called = true;
1699
+ _valuesSubscriberCount--;
1700
+ unsubscribe();
1701
+ },
1702
+ };
1703
+ }
1704
+ return _getWatch(name, defaultValue, true);
1705
+ };
1706
+ const _subscribe = (props) => {
1707
+ const needsValues = !!props.formState?.values;
1708
+ if (needsValues) {
1709
+ _valuesSubscriberCount++;
1710
+ }
1711
+ const { unsubscribe } = _subjects.state.subscribe({
1712
+ next: (formState) => {
1713
+ if (shouldSubscribeByName(props.name, formState.name, props.exact) &&
1714
+ shouldRenderFormState(formState, props.formState || _proxyFormState, _setFormState, props.reRenderRoot)) {
1715
+ const snapshot = { ..._formValues };
1716
+ props.callback({
1717
+ values: snapshot,
1718
+ ..._formState,
1719
+ ...formState,
1720
+ defaultValues: _defaultValues,
1721
+ });
1722
+ }
1723
+ },
1724
+ });
1725
+ if (!needsValues) {
1726
+ return unsubscribe;
1727
+ }
1728
+ let called = false;
1729
+ return () => {
1730
+ if (called) {
1731
+ return;
1621
1732
  }
1622
- },
1623
- }).unsubscribe;
1733
+ called = true;
1734
+ _valuesSubscriberCount--;
1735
+ unsubscribe();
1736
+ };
1737
+ };
1624
1738
  const subscribe = (props) => {
1625
1739
  _state.mount = true;
1626
1740
  _proxySubscribeFormState = {
@@ -1755,8 +1869,7 @@ function createFormControl(props = {}) {
1755
1869
  field._f.mount = false;
1756
1870
  }
1757
1871
  (_options.shouldUnregister || options.shouldUnregister) &&
1758
- !(getFieldArrayParentNames(_names.array, name).length &&
1759
- _state.action) &&
1872
+ !(isNameInFieldArray(_names.array, name) && _state.action) &&
1760
1873
  _names.unMount.add(name);
1761
1874
  }
1762
1875
  },
@@ -1868,7 +1981,7 @@ function createFormControl(props = {}) {
1868
1981
  const updatedValues = formValues ? cloneObject(formValues) : _defaultValues;
1869
1982
  const cloneUpdatedValues = cloneObject(updatedValues);
1870
1983
  const isEmptyResetValues = isEmptyObject(formValues);
1871
- const values = isEmptyResetValues ? _defaultValues : cloneUpdatedValues;
1984
+ const values = cloneUpdatedValues;
1872
1985
  if (!keepStateOptions.keepDefaultValues) {
1873
1986
  _defaultValues = updatedValues;
1874
1987
  }
@@ -1917,15 +2030,25 @@ function createFormControl(props = {}) {
1917
2030
  _fields = {};
1918
2031
  }
1919
2032
  }
1920
- _formValues = _options.shouldUnregister
1921
- ? keepStateOptions.keepDefaultValues
2033
+ if (_options.shouldUnregister) {
2034
+ _formValues = keepStateOptions.keepDefaultValues
1922
2035
  ? cloneObject(_defaultValues)
1923
- : {}
1924
- : cloneObject(values);
2036
+ : {};
2037
+ if (keepStateOptions.keepFieldsRef) {
2038
+ for (const fieldName of _names.mount) {
2039
+ set(_formValues, fieldName, get(values, fieldName));
2040
+ }
2041
+ }
2042
+ }
2043
+ else {
2044
+ _formValues = cloneObject(values);
2045
+ }
1925
2046
  _subjects.array.next({
1926
2047
  values: { ...values },
1927
2048
  });
1928
2049
  _subjects.state.next({
2050
+ name: undefined,
2051
+ type: undefined,
1929
2052
  values: { ...values },
1930
2053
  });
1931
2054
  }
@@ -1947,9 +2070,6 @@ function createFormControl(props = {}) {
1947
2070
  _state.watch = !!_options.shouldUnregister;
1948
2071
  _state.keepIsValid = !!keepStateOptions.keepIsValid;
1949
2072
  _state.action = false;
1950
- // Clear errors synchronously to prevent validation errors on subsequent submissions
1951
- // This fixes the issue where form.reset() causes validation errors on subsequent
1952
- // submissions in Next.js 16 with Server Actions
1953
2073
  if (!keepStateOptions.keepErrors) {
1954
2074
  _formState.errors = {};
1955
2075
  }
@@ -1961,8 +2081,10 @@ function createFormControl(props = {}) {
1961
2081
  ? false
1962
2082
  : keepStateOptions.keepDirty
1963
2083
  ? _formState.isDirty
1964
- : !!(keepStateOptions.keepDefaultValues &&
1965
- !deepEqual(formValues, _defaultValues)),
2084
+ : keepStateOptions.keepValues
2085
+ ? _getDirty()
2086
+ : !!(keepStateOptions.keepDefaultValues &&
2087
+ !deepEqual(formValues, _defaultValues)),
1966
2088
  isSubmitted: keepStateOptions.keepIsSubmitted
1967
2089
  ? _formState.isSubmitted
1968
2090
  : false,
@@ -1999,8 +2121,6 @@ function createFormControl(props = {}) {
1999
2121
  ? fieldReference.refs[0]
2000
2122
  : fieldReference.ref;
2001
2123
  if (fieldRef.focus) {
2002
- // Use setTimeout to ensure focus happens after any pending state updates
2003
- // This fixes the issue where setFocus doesn't work immediately after setError
2004
2124
  setTimeout(() => {
2005
2125
  fieldRef.focus();
2006
2126
  options.shouldSelect &&
@@ -2011,9 +2131,15 @@ function createFormControl(props = {}) {
2011
2131
  }
2012
2132
  };
2013
2133
  const _setFormState = (updatedFormState) => {
2134
+ // `name`, `type`, and `values` describe the event that produced this
2135
+ // update, not the form's persisted state (they aren't part of
2136
+ // `FormState`). Merging them in would leak a stale `name`/`type` from
2137
+ // one event into a later, unrelated notification that doesn't specify
2138
+ // its own.
2139
+ const { name, type, values, ...formState } = updatedFormState;
2014
2140
  _formState = {
2015
2141
  ..._formState,
2016
- ...updatedFormState,
2142
+ ...formState,
2017
2143
  };
2018
2144
  };
2019
2145
  const _resetDefaultValues = () => isFunction(_options.defaultValues) &&
@@ -2023,6 +2149,21 @@ function createFormControl(props = {}) {
2023
2149
  isLoading: false,
2024
2150
  });
2025
2151
  });
2152
+ const resetDefaultValues = (values, options = {}) => {
2153
+ _defaultValues = cloneObject(values);
2154
+ if (!options.keepDirty) {
2155
+ const newDirtyFields = getDirtyFields(_defaultValues, _formValues);
2156
+ _formState.dirtyFields = newDirtyFields;
2157
+ _formState.isDirty = !isEmptyObject(newDirtyFields);
2158
+ }
2159
+ if (!options.keepIsValid) {
2160
+ _setValid();
2161
+ }
2162
+ _subjects.state.next({
2163
+ ..._formState,
2164
+ defaultValues: _defaultValues,
2165
+ });
2166
+ };
2026
2167
  const methods = {
2027
2168
  control: {
2028
2169
  register,
@@ -2079,6 +2220,8 @@ function createFormControl(props = {}) {
2079
2220
  ..._options,
2080
2221
  ...value,
2081
2222
  };
2223
+ _validationModeBeforeSubmit = getValidationModes(_options.mode);
2224
+ _validationModeAfterSubmit = getValidationModes(_options.reValidateMode);
2082
2225
  },
2083
2226
  },
2084
2227
  subscribe,
@@ -2091,6 +2234,7 @@ function createFormControl(props = {}) {
2091
2234
  getValues,
2092
2235
  reset,
2093
2236
  resetField,
2237
+ resetDefaultValues,
2094
2238
  clearErrors,
2095
2239
  unregister,
2096
2240
  setError,