react-hook-form 7.86.0 → 7.88.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/controller.d.ts +7 -35
  2. package/dist/controller.d.ts.map +1 -1
  3. package/dist/errorMessage.d.ts +27 -0
  4. package/dist/errorMessage.d.ts.map +1 -0
  5. package/dist/form.d.ts +7 -15
  6. package/dist/form.d.ts.map +1 -1
  7. package/dist/index.cjs.js +1 -1
  8. package/dist/index.cjs.js.map +1 -1
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.esm.mjs +317 -313
  12. package/dist/index.esm.mjs.map +1 -1
  13. package/dist/index.react-server.d.ts +3 -0
  14. package/dist/index.react-server.d.ts.map +1 -0
  15. package/dist/index.umd.js +1 -1
  16. package/dist/index.umd.js.map +1 -1
  17. package/dist/logic/createFormControl.d.ts.map +1 -1
  18. package/dist/logic/getNullAncestorValue.d.ts +5 -0
  19. package/dist/logic/getNullAncestorValue.d.ts.map +1 -0
  20. package/dist/logic/iterateFieldsByAction.d.ts +1 -1
  21. package/dist/logic/iterateFieldsByAction.d.ts.map +1 -1
  22. package/dist/logic/schemaErrorLookup.d.ts.map +1 -1
  23. package/dist/logic/validateField.d.ts.map +1 -1
  24. package/dist/react-server.esm.mjs +2464 -0
  25. package/dist/react-server.esm.mjs.map +1 -0
  26. package/dist/types/errors.d.ts +3 -3
  27. package/dist/types/errors.d.ts.map +1 -1
  28. package/dist/types/form.d.ts +2 -1
  29. package/dist/types/form.d.ts.map +1 -1
  30. package/dist/types/path/eager.d.ts +3 -3
  31. package/dist/types/path/eager.d.ts.map +1 -1
  32. package/dist/types/utils.d.ts +29 -4
  33. package/dist/types/utils.d.ts.map +1 -1
  34. package/dist/useController.d.ts +5 -17
  35. package/dist/useController.d.ts.map +1 -1
  36. package/dist/useFieldArray.d.ts +5 -30
  37. package/dist/useFieldArray.d.ts.map +1 -1
  38. package/dist/useForm.d.ts +7 -22
  39. package/dist/useForm.d.ts.map +1 -1
  40. package/dist/useFormContext.d.ts +10 -46
  41. package/dist/useFormContext.d.ts.map +1 -1
  42. package/dist/useFormState.d.ts +4 -23
  43. package/dist/useFormState.d.ts.map +1 -1
  44. package/dist/useResyncOnReconnect.d.ts +1 -1
  45. package/dist/useResyncOnReconnect.d.ts.map +1 -1
  46. package/dist/useWatch.d.ts +7 -140
  47. package/dist/useWatch.d.ts.map +1 -1
  48. package/dist/utils/cloneObject.d.ts.map +1 -1
  49. package/dist/utils/flatten.d.ts.map +1 -1
  50. package/dist/utils/formData.d.ts.map +1 -1
  51. package/dist/watch.d.ts +4 -16
  52. package/dist/watch.d.ts.map +1 -1
  53. package/package.json +1 -1
@@ -22,6 +22,24 @@ var getEventValue = (event) => isObject(event) && event.target
22
22
  : event.target.value
23
23
  : event;
24
24
 
25
+ const FIELD_PATH_RE = /[.[\]'"]/;
26
+ var stringToPath = (input) => input.split(FIELD_PATH_RE).filter(Boolean);
27
+
28
+ function getNullAncestorValue(control, name) {
29
+ const segments = stringToPath(name);
30
+ let formValues = control._formValues;
31
+ let defaultValues = control._defaultValues;
32
+ for (let i = 0; i < segments.length - 1; i++) {
33
+ const key = segments[i];
34
+ formValues = formValues == null ? formValues : formValues[key];
35
+ defaultValues = defaultValues == null ? defaultValues : defaultValues[key];
36
+ if (formValues === null || defaultValues === null) {
37
+ return null;
38
+ }
39
+ }
40
+ return undefined;
41
+ }
42
+
25
43
  var isNameInFieldArray = (names, name) => name
26
44
  .split('.')
27
45
  .some((part, index, arr) => !isNaN(Number(part)) && names.has(arr.slice(0, index).join('.')));
@@ -37,8 +55,9 @@ function cloneObject(data) {
37
55
  if (data instanceof Date) {
38
56
  return new Date(data);
39
57
  }
58
+ const isBlobInstance = typeof Blob !== 'undefined' && data instanceof Blob;
40
59
  const isFileListInstance = typeof FileList !== 'undefined' && data instanceof FileList;
41
- if (isWeb && (data instanceof Blob || isFileListInstance)) {
60
+ if (isWeb && (isBlobInstance || isFileListInstance)) {
42
61
  return data;
43
62
  }
44
63
  const isArray = Array.isArray(data);
@@ -86,9 +105,6 @@ var isKey = (value) => IS_KEY_RE.test(value);
86
105
 
87
106
  var isUndefined = (val) => val === undefined;
88
107
 
89
- const FIELD_PATH_RE = /[.[\]'"]/;
90
- var stringToPath = (input) => input.split(FIELD_PATH_RE).filter(Boolean);
91
-
92
108
  var get = (object, path, defaultValue) => {
93
109
  if (!path || !isObject(object)) {
94
110
  return defaultValue;
@@ -229,11 +245,20 @@ function deepEqual(object1, object2, visited = new WeakMap()) {
229
245
  return true;
230
246
  }
231
247
 
232
- function useResyncOnReconnect() {
248
+ function useResyncOnReconnect(getInitialValue) {
233
249
  const _connected = React.useRef(false);
250
+ const _initialized = React.useRef(false);
234
251
  const _prevValue = React.useRef(undefined);
252
+ const _renderCount = React.useRef(0);
253
+ _renderCount.current++;
254
+ if (!_initialized.current && getInitialValue) {
255
+ _initialized.current = true;
256
+ _prevValue.current = cloneObject(getInitialValue());
257
+ }
235
258
  const resyncIfNeeded = React.useCallback((enabled, getCurrentValue, setValue) => {
236
- if (enabled && _connected.current) {
259
+ if (enabled &&
260
+ (_connected.current ||
261
+ (_initialized.current && _renderCount.current > 1))) {
237
262
  const currentValue = getCurrentValue();
238
263
  if (!deepEqual(_prevValue.current, currentValue)) {
239
264
  setValue(currentValue);
@@ -250,42 +275,24 @@ function useResyncOnReconnect() {
250
275
  }
251
276
 
252
277
  /**
253
- * Subscribes to each form state and isolates re-renders at the custom hook level. It has its own scope for form state subscriptions, so it will not affect other useFormState or useForm instances. Using this hook can reduce the re-render impact on large and complex form applications.
254
- *
255
- * @remarks
256
- * [API](https://react-hook-form.com/docs/useformstate) • [Demo](https://codesandbox.io/s/useformstate-75xly)
278
+ * Subscribes to form state with re-renders isolated to this hook.
279
+ * Optionally scope to specific field names to minimize re-render surface.
257
280
  *
258
- * @param props - Include options to specify fields to subscribe to. {@link UseFormStateReturn}
281
+ * @see [API](https://react-hook-form.com/docs/useformstate)
259
282
  *
260
283
  * @example
261
284
  * ```tsx
262
- * function App() {
263
- * const { register, handleSubmit, control } = useForm({
264
- * defaultValues: {
265
- * firstName: "firstName"
266
- * }});
267
- * const { dirtyFields } = useFormState({
268
- * control
269
- * });
270
- * const onSubmit = (data) => console.log(data);
271
- *
272
- * return (
273
- * <form onSubmit={handleSubmit(onSubmit)}>
274
- * <input {...register("firstName")} placeholder="First Name" />
275
- * {dirtyFields.firstName && <p>Field is dirty.</p>}
276
- * <input type="submit" />
277
- * </form>
278
- * );
279
- * }
285
+ * const { errors, isDirty } = useFormState({ control, name: "email" });
280
286
  * ```
281
287
  */
282
288
  function useFormState(props) {
283
289
  const formControl = useFormControlContext();
284
290
  const { control = formControl, disabled, name, exact } = props || {};
285
- const [formState, updateFormState] = React.useState(() => ({
291
+ const getCurrentFormState = () => ({
286
292
  ...control._formState,
287
293
  defaultValues: control._defaultValues,
288
- }));
294
+ });
295
+ const [formState, updateFormState] = React.useState(getCurrentFormState);
289
296
  const _localProxyFormState = React.useRef({
290
297
  isDirty: false,
291
298
  isLoading: false,
@@ -296,12 +303,8 @@ function useFormState(props) {
296
303
  isValid: false,
297
304
  errors: false,
298
305
  });
299
- const { resyncIfNeeded, snapshot } = useResyncOnReconnect();
306
+ const { resyncIfNeeded, snapshot } = useResyncOnReconnect(getCurrentFormState);
300
307
  useIsomorphicLayoutEffect(() => {
301
- const getCurrentFormState = () => ({
302
- ...control._formState,
303
- defaultValues: control._defaultValues,
304
- });
305
308
  resyncIfNeeded(!disabled, getCurrentFormState, updateFormState);
306
309
  const unsubscribe = control._subscribe({
307
310
  name,
@@ -320,7 +323,7 @@ function useFormState(props) {
320
323
  unsubscribe();
321
324
  snapshot(!disabled, getCurrentFormState);
322
325
  };
323
- }, [name, disabled, exact, resyncIfNeeded, snapshot]);
326
+ }, [control, name, disabled, exact, resyncIfNeeded, snapshot]);
324
327
  React.useEffect(() => {
325
328
  _localProxyFormState.current.isValid && control._setValid(true);
326
329
  }, [control]);
@@ -336,26 +339,22 @@ var generateWatchOutput = (names, _names, formValues, isGlobal, defaultValue) =>
336
339
  }
337
340
  if (Array.isArray(names)) {
338
341
  return names.map((fieldName) => (isGlobal && _names.watch.add(fieldName),
339
- get(formValues, fieldName)));
342
+ get(formValues, fieldName, get(defaultValue, fieldName))));
340
343
  }
341
344
  isGlobal && (_names.watchAll = true);
342
345
  return formValues;
343
346
  };
344
347
 
345
348
  /**
346
- * Custom hook to subscribe to field changes and isolate re-rendering at the component level.
347
- *
348
- * @remarks
349
+ * Subscribes to field value changes and isolates re-renders to the hook level.
349
350
  *
350
- * [API](https://react-hook-form.com/docs/usewatch) • [Demo](https://codesandbox.io/s/react-hook-form-v7-ts-usewatch-h9i5e)
351
+ * @see [API](https://react-hook-form.com/docs/usewatch)
351
352
  *
352
353
  * @example
353
354
  * ```tsx
354
- * const { control } = useForm();
355
- * const values = useWatch({
356
- * name: "fieldName",
357
- * control,
358
- * })
355
+ * const email = useWatch({ control, name: "email" });
356
+ * const all = useWatch({ control });
357
+ * const adult = useWatch({ control, name: "age", compute: (v) => v >= 18 });
359
358
  * ```
360
359
  */
361
360
  function useWatch(props) {
@@ -363,14 +362,15 @@ function useWatch(props) {
363
362
  const { control = formControl, name, defaultValue, disabled, exact, compute, } = props || {};
364
363
  const _defaultValue = React.useRef(defaultValue);
365
364
  const _compute = React.useRef(compute);
366
- const _computeFormValues = React.useRef(undefined);
367
365
  const _prevControl = React.useRef(control);
368
366
  const _prevName = React.useRef(name);
369
367
  _compute.current = compute;
370
- const [value, updateValue] = React.useState(() => {
368
+ const getInitialOutput = () => {
371
369
  const defaultValue = control._getWatch(name, _defaultValue.current);
372
370
  return _compute.current ? _compute.current(defaultValue) : defaultValue;
373
- });
371
+ };
372
+ const [value, updateValue] = React.useState(getInitialOutput);
373
+ const _computeFormValues = React.useRef(value);
374
374
  const getCurrentOutput = React.useCallback((values) => {
375
375
  const formValues = generateWatchOutput(name, control._names, values || control._formValues, false, _defaultValue.current);
376
376
  return _compute.current ? _compute.current(formValues) : formValues;
@@ -390,7 +390,7 @@ function useWatch(props) {
390
390
  }
391
391
  }
392
392
  }, [control._formValues, control._names, disabled, name]);
393
- const { resyncIfNeeded, snapshot } = useResyncOnReconnect();
393
+ const { resyncIfNeeded, snapshot } = useResyncOnReconnect(getInitialOutput);
394
394
  const _refreshValue = React.useRef(refreshValue);
395
395
  _refreshValue.current = refreshValue;
396
396
  const _getCurrentOutput = React.useRef(getCurrentOutput);
@@ -444,34 +444,27 @@ function useWatch(props) {
444
444
  }
445
445
 
446
446
  /**
447
- * Custom hook to work with controlled component, this function provide you with both form and field level state. Re-render is isolated at the hook level.
448
- *
449
- * @remarks
450
- * [API](https://react-hook-form.com/docs/usecontroller) • [Demo](https://codesandbox.io/s/usecontroller-0o8px)
447
+ * Hook for controlled inputs. Returns `field`, `fieldState`, and `formState`.
448
+ * Re-renders are isolated to the hook level.
451
449
  *
452
- * @param props - the path name to the form field value, and validation rules.
453
- *
454
- * @returns field properties, field and form state. {@link UseControllerReturn}
450
+ * @see [API](https://react-hook-form.com/docs/usecontroller)
455
451
  *
456
452
  * @example
457
453
  * ```tsx
458
- * function Input(props) {
459
- * const { field, fieldState, formState } = useController(props);
460
- * return (
461
- * <div>
462
- * <input {...field} placeholder={props.name} />
463
- * <p>{fieldState.isTouched && "Touched"}</p>
464
- * <p>{formState.isSubmitted ? "submitted" : ""}</p>
465
- * </div>
466
- * );
467
- * }
454
+ * const { field, fieldState } = useController({ control, name: "email" });
455
+ * return <input {...field} />;
468
456
  * ```
469
457
  */
470
458
  function useController(props) {
471
459
  const formControl = useFormControlContext();
472
460
  const { name, disabled, control = formControl, shouldUnregister, defaultValue, exact = true, } = props;
473
461
  const isArrayField = isNameInFieldArray(control._names.array, name);
474
- const defaultValueMemo = React.useMemo(() => get(control._formValues, name, get(control._defaultValues, name, defaultValue)), [control, name, defaultValue]);
462
+ const defaultValueMemo = React.useMemo(() => {
463
+ const resolved = get(control._formValues, name, get(control._defaultValues, name, defaultValue));
464
+ return isUndefined(resolved)
465
+ ? getNullAncestorValue(control, name)
466
+ : resolved;
467
+ }, [control, name, defaultValue]);
475
468
  const value = useWatch({
476
469
  control,
477
470
  name,
@@ -613,48 +606,50 @@ function useController(props) {
613
606
  }
614
607
 
615
608
  /**
616
- * Component based on `useController` hook to work with controlled component.
609
+ * Component wrapper around `useController` for controlled inputs.
617
610
  *
618
- * @remarks
619
- * [API](https://react-hook-form.com/docs/usecontroller/controller) • [Demo](https://codesandbox.io/s/react-hook-form-v6-controller-ts-jwyzw) • [Video](https://www.youtube.com/watch?v=N2UNk_UCVyA)
611
+ * @see [API](https://react-hook-form.com/docs/usecontroller/controller)
620
612
  *
621
- * @param props - the path name to the form field value, and validation rules.
613
+ * @example
614
+ * ```tsx
615
+ * <Controller
616
+ * control={control}
617
+ * name="test"
618
+ * render={({ field, fieldState, formState }) => <input {...field} />}
619
+ * />
620
+ * ```
621
+ */
622
+ const Controller = (props) => props.render(useController(props));
623
+
624
+ /**
625
+ * Displays the validation error for a single field.
626
+ * Reads from `control` when provided, otherwise from the nearest `FormProvider`.
622
627
  *
623
- * @returns provide field handler functions, field and form state.
628
+ * @see [API](https://react-hook-form.com/docs/useformstate/errormessage)
624
629
  *
625
630
  * @example
626
631
  * ```tsx
627
- * function App() {
628
- * const { control } = useForm<FormValues>({
629
- * defaultValues: {
630
- * test: ""
631
- * }
632
- * });
633
- *
634
- * return (
635
- * <form>
636
- * <Controller
637
- * control={control}
638
- * name="test"
639
- * render={({ field: { onChange, onBlur, value, ref }, formState, fieldState }) => (
640
- * <>
641
- * <input
642
- * onChange={onChange} // send value to hook form
643
- * onBlur={onBlur} // notify when input is touched
644
- * value={value} // return updated value
645
- * ref={ref} // set ref for focus management
646
- * />
647
- * <p>{formState.isSubmitted ? "submitted" : ""}</p>
648
- * <p>{fieldState.isTouched ? "touched" : ""}</p>
649
- * </>
650
- * )}
651
- * />
652
- * </form>
653
- * );
654
- * }
632
+ * <ErrorMessage control={control} name="email" as="p" />
633
+ * <ErrorMessage name="email" as="span" />
634
+ * <ErrorMessage control={control} name="email"
635
+ * render={({ message }) => <Alert>{message}</Alert>} />
655
636
  * ```
656
637
  */
657
- const Controller = (props) => props.render(useController(props));
638
+ const ErrorMessage = ({ as, control, name, render, }) => {
639
+ const { errors } = useFormState({
640
+ control,
641
+ name: name,
642
+ });
643
+ const error = get(errors, name);
644
+ if (!error) {
645
+ return null;
646
+ }
647
+ const message = error.message || '';
648
+ if (render) {
649
+ return render({ message, messages: error.types });
650
+ }
651
+ return React.createElement(as || React.Fragment, null, message);
652
+ };
658
653
 
659
654
  var generateId = () => typeof crypto !== 'undefined' && crypto.randomUUID
660
655
  ? crypto.randomUUID()
@@ -688,7 +683,7 @@ var isWatched = (name, _names, isBlurEvent) => {
688
683
  return false;
689
684
  };
690
685
 
691
- const iterateFieldsByAction = (fields, action, fieldsNames, abortEarly) => {
686
+ const iterateFieldsByAction = (fields, action, fieldsNames) => {
692
687
  for (const key of fieldsNames || Object.keys(fields)) {
693
688
  if (key === '_f') {
694
689
  continue;
@@ -697,21 +692,21 @@ const iterateFieldsByAction = (fields, action, fieldsNames, abortEarly) => {
697
692
  if (field) {
698
693
  const { _f } = field;
699
694
  if (_f) {
700
- if (_f.refs && _f.refs[0] && action(_f.refs[0], key) && !abortEarly) {
695
+ if (_f.refs && _f.refs[0] && action(_f.refs[0], _f.name)) {
701
696
  return true;
702
697
  }
703
- else if (_f.ref && action(_f.ref, _f.name) && !abortEarly) {
698
+ else if (_f.ref && action(_f.ref, _f.name)) {
704
699
  return true;
705
700
  }
706
701
  else {
707
702
  if (iterateFieldsByAction(field, action)) {
708
- break;
703
+ return true;
709
704
  }
710
705
  }
711
706
  }
712
707
  else if (isObject(field) || Array.isArray(field)) {
713
708
  if (iterateFieldsByAction(field, action)) {
714
- break;
709
+ return true;
715
710
  }
716
711
  }
717
712
  }
@@ -977,7 +972,9 @@ var validateField = async (field, disabledFieldNames, formValues, validateAllFie
977
972
  ...validateError,
978
973
  ...appendErrorsCurry(key, validateError.message),
979
974
  };
980
- setCustomValidity(validateError.message);
975
+ if (!validateAllFieldCriteria) {
976
+ setCustomValidity(validateError.message);
977
+ }
981
978
  if (validateAllFieldCriteria) {
982
979
  error[name] = validationResult;
983
980
  }
@@ -1105,48 +1102,27 @@ var updateAt = (fieldValues, index, value) => {
1105
1102
  };
1106
1103
 
1107
1104
  /**
1108
- * A custom hook that exposes convenient methods to perform operations with a list of dynamic inputs that need to be appended, updated, removed etc. • [Demo](https://codesandbox.io/s/react-hook-form-usefieldarray-ssugn) • [Video](https://youtu.be/4MrbfGSFY2A)
1109
- *
1110
- * @remarks
1111
- * [API](https://react-hook-form.com/docs/usefieldarray) • [Demo](https://codesandbox.io/s/react-hook-form-usefieldarray-ssugn)
1112
- *
1113
- * @param props - useFieldArray props
1105
+ * Hook for dynamic field arrays. Provides `fields` and mutation methods:
1106
+ * `append`, `prepend`, `remove`, `insert`, `swap`, `move`, `update`, `replace`.
1114
1107
  *
1115
- * @returns methods - functions to manipulate with the Field Arrays (dynamic inputs) {@link UseFieldArrayReturn}
1108
+ * @see [API](https://react-hook-form.com/docs/usefieldarray)
1116
1109
  *
1117
1110
  * @example
1118
1111
  * ```tsx
1119
- * function App() {
1120
- * const { register, control, handleSubmit, reset, trigger, setError } = useForm({
1121
- * defaultValues: {
1122
- * test: []
1123
- * }
1124
- * });
1125
- * const { fields, append } = useFieldArray({
1126
- * control,
1127
- * name: "test"
1128
- * });
1129
- *
1130
- * return (
1131
- * <form onSubmit={handleSubmit(data => console.log(data))}>
1132
- * {fields.map((item, index) => (
1133
- * <input key={item.id} {...register(`test.${index}.firstName`)} />
1134
- * ))}
1135
- * <button type="button" onClick={() => append({ firstName: "bill" })}>
1136
- * append
1137
- * </button>
1138
- * <input type="submit" />
1139
- * </form>
1140
- * );
1141
- * }
1112
+ * const { fields, append } = useFieldArray({ control, name: "items" });
1113
+ * return fields.map((f, i) => <input key={f.id} {...register(`items.${i}.name`)} />);
1142
1114
  * ```
1143
1115
  */
1144
1116
  function useFieldArray(props) {
1145
1117
  const formControl = useFormControlContext();
1146
1118
  const { control = formControl, name, keyName = 'id', disabled, shouldUnregister, rules, } = props;
1147
- const [fields, setFields] = React.useState(control._getFieldArray(name));
1119
+ const getCurrentFieldArray = () => control._getFieldArray(name);
1120
+ const [fields, setFields] = React.useState(getCurrentFieldArray);
1148
1121
  const ids = React.useRef(control._getFieldArray(name).map(generateId));
1149
1122
  const _actioned = React.useRef(false);
1123
+ const { resyncIfNeeded, snapshot } = useResyncOnReconnect(getCurrentFieldArray);
1124
+ const _prevControl = React.useRef(control);
1125
+ const _prevName = React.useRef(name);
1150
1126
  if (!disabled) {
1151
1127
  control._names.array.add(name);
1152
1128
  }
@@ -1158,7 +1134,23 @@ function useFieldArray(props) {
1158
1134
  if (disabled) {
1159
1135
  return;
1160
1136
  }
1161
- return control._subjects.array.subscribe({
1137
+ if (_prevControl.current === control && _prevName.current === name) {
1138
+ resyncIfNeeded(true, getCurrentFieldArray, (fieldValues) => {
1139
+ setFields(fieldValues);
1140
+ ids.current = fieldValues.map(generateId);
1141
+ });
1142
+ }
1143
+ else {
1144
+ _prevControl.current = control;
1145
+ _prevName.current = name;
1146
+ const fieldValues = getCurrentFieldArray();
1147
+ if (!deepEqual(fields, fieldValues)) {
1148
+ setFields(fieldValues);
1149
+ ids.current = fieldValues.map(generateId);
1150
+ }
1151
+ snapshot(true, getCurrentFieldArray);
1152
+ }
1153
+ const unsubscribe = control._subjects.array.subscribe({
1162
1154
  next: ({ values, name: fieldArrayName, }) => {
1163
1155
  if (fieldArrayName === name || !fieldArrayName) {
1164
1156
  const fieldValues = get(values, name);
@@ -1173,7 +1165,11 @@ function useFieldArray(props) {
1173
1165
  }
1174
1166
  },
1175
1167
  }).unsubscribe;
1176
- }, [control, name, disabled]);
1168
+ return () => {
1169
+ unsubscribe();
1170
+ snapshot(true, getCurrentFieldArray);
1171
+ };
1172
+ }, [control, name, disabled, resyncIfNeeded, snapshot]);
1177
1173
  const updateValues = React.useCallback((updatedFieldArrayValues) => {
1178
1174
  _actioned.current = true;
1179
1175
  control._setFieldArray(name, updatedFieldArrayValues);
@@ -1285,7 +1281,9 @@ function useFieldArray(props) {
1285
1281
  ids.current = updatedFieldArrayValues.map(generateId);
1286
1282
  updateValues([...updatedFieldArrayValues]);
1287
1283
  setFields([...updatedFieldArrayValues]);
1288
- control._setFieldArray(name, [...updatedFieldArrayValues], (data) => data, {}, true, false);
1284
+ control._setFieldArray(name, [...updatedFieldArrayValues], (data) => Array.isArray(data)
1285
+ ? data.slice(0, updatedFieldArrayValues.length)
1286
+ : data, {});
1289
1287
  };
1290
1288
  React.useEffect(() => {
1291
1289
  if (disabled) {
@@ -1455,13 +1453,15 @@ const FieldArray = (props) => props.render(useFieldArray(props));
1455
1453
 
1456
1454
  const isFileLike = (value) => (typeof Blob !== 'undefined' && value instanceof Blob) ||
1457
1455
  (typeof File !== 'undefined' && value instanceof File);
1456
+ const isFileListLike = (value) => typeof FileList !== 'undefined' && value instanceof FileList;
1458
1457
  const flatten = (obj) => {
1459
1458
  const output = {};
1460
1459
  for (const key of Object.keys(obj)) {
1461
1460
  if (isObjectType(obj[key]) &&
1462
1461
  obj[key] !== null &&
1463
1462
  !isDateObject(obj[key]) &&
1464
- !isFileLike(obj[key])) {
1463
+ !isFileLike(obj[key]) &&
1464
+ !isFileListLike(obj[key])) {
1465
1465
  const nested = flatten(obj[key]);
1466
1466
  for (const nestedKey of Object.keys(nested)) {
1467
1467
  output[`${key}.${nestedKey}`] = nested[nestedKey];
@@ -1478,7 +1478,18 @@ function jsonToFormData(json) {
1478
1478
  const result = new FormData();
1479
1479
  const flattenFormValues = flatten(json);
1480
1480
  for (const key in flattenFormValues) {
1481
- result.append(key, flattenFormValues[key]);
1481
+ const value = flattenFormValues[key];
1482
+ if (isUndefined(value)) {
1483
+ continue;
1484
+ }
1485
+ if (typeof FileList !== 'undefined' && value instanceof FileList) {
1486
+ for (let index = 0; index < value.length; index++) {
1487
+ const file = value[index];
1488
+ file && result.append(key, file);
1489
+ }
1490
+ continue;
1491
+ }
1492
+ result.append(key, value);
1482
1493
  }
1483
1494
  return result;
1484
1495
  }
@@ -1497,64 +1508,28 @@ function noop() { }
1497
1508
  const HookFormContext = React.createContext(null);
1498
1509
  HookFormContext.displayName = 'HookFormContext';
1499
1510
  /**
1500
- * 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}.
1501
- *
1502
- * @remarks
1503
- * [API](https://react-hook-form.com/docs/useformcontext) • [Demo](https://codesandbox.io/s/react-hook-form-v7-form-context-ytudi)
1511
+ * Retrieves all `useForm` methods from the nearest `FormProvider`.
1512
+ * Use in deeply nested components to avoid prop-drilling.
1504
1513
  *
1505
- * @returns return all useForm methods
1514
+ * @see [API](https://react-hook-form.com/docs/useformcontext)
1506
1515
  *
1507
1516
  * @example
1508
1517
  * ```tsx
1509
- * function App() {
1510
- * const methods = useForm();
1511
- * const onSubmit = data => console.log(data);
1512
- *
1513
- * return (
1514
- * <FormProvider {...methods} >
1515
- * <form onSubmit={methods.handleSubmit(onSubmit)}>
1516
- * <NestedInput />
1517
- * <input type="submit" />
1518
- * </form>
1519
- * </FormProvider>
1520
- * );
1521
- * }
1522
- *
1523
- * function NestedInput() {
1524
- * const { register } = useFormContext(); // retrieve all hook methods
1525
- * return <input {...register("test")} />;
1526
- * }
1518
+ * const { register } = useFormContext<FormValues>();
1527
1519
  * ```
1528
1520
  */
1529
1521
  const useFormContext = () => React.useContext(HookFormContext);
1530
1522
  /**
1531
- * 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}.
1523
+ * Provides all `useForm` methods to the component tree via React Context.
1524
+ * Pair with `useFormContext` to consume them in any descendant.
1532
1525
  *
1533
- * @remarks
1534
- * [API](https://react-hook-form.com/docs/useformcontext) • [Demo](https://codesandbox.io/s/react-hook-form-v7-form-context-ytudi)
1535
- *
1536
- * @param props - all useForm methods
1526
+ * @see [API](https://react-hook-form.com/docs/useformcontext)
1537
1527
  *
1538
1528
  * @example
1539
1529
  * ```tsx
1540
- * function App() {
1541
- * const methods = useForm();
1542
- * const onSubmit = data => console.log(data);
1543
- *
1544
- * return (
1545
- * <FormProvider {...methods} >
1546
- * <form onSubmit={methods.handleSubmit(onSubmit)}>
1547
- * <NestedInput />
1548
- * <input type="submit" />
1549
- * </form>
1550
- * </FormProvider>
1551
- * );
1552
- * }
1553
- *
1554
- * function NestedInput() {
1555
- * const { register } = useFormContext(); // retrieve all hook methods
1556
- * return <input {...register("test")} />;
1557
- * }
1530
+ * <FormProvider {...methods}>
1531
+ * <form onSubmit={methods.handleSubmit(onSubmit)}>{children}</form>
1532
+ * </FormProvider>
1558
1533
  * ```
1559
1534
  */
1560
1535
  const FormProvider = ({ children, watch, getValues, getErrors, getFieldState, setError, clearErrors, setValue, setValues, trigger, formState, resetField, reset, resetDefaultValues, handleSubmit, unregister, control, register, setFocus, subscribe, }) => {
@@ -1608,25 +1583,17 @@ function defaultValidateStatus(status) {
1608
1583
  return status >= 200 && status < 300;
1609
1584
  }
1610
1585
  /**
1611
- * Form component to manage submission.
1586
+ * Form component that handles submission, including optional `action` fetch and server error wiring.
1612
1587
  *
1613
- * @param props - to setup submission detail. {@link FormProps}
1614
- *
1615
- * @returns form component or headless render prop.
1588
+ * @see [API](https://react-hook-form.com/docs/useform/form)
1616
1589
  *
1617
1590
  * @example
1618
1591
  * ```tsx
1619
- * function App() {
1620
- * const { control, formState: { errors } } = useForm();
1621
- *
1622
- * return (
1623
- * <Form action="/api" control={control}>
1624
- * <input {...register("name")} />
1625
- * <p>{errors?.root?.server && 'Server error'}</p>
1626
- * <button>Submit</button>
1627
- * </Form>
1628
- * );
1629
- * }
1592
+ * <Form action="/api" control={control}>
1593
+ * <input {...register("name")} />
1594
+ * <p>{errors?.root?.server && 'Server error'}</p>
1595
+ * <button>Submit</button>
1596
+ * </Form>
1630
1597
  * ```
1631
1598
  */
1632
1599
  function Form(props) {
@@ -1984,7 +1951,7 @@ var hasValidation = (options) => options.mount &&
1984
1951
 
1985
1952
  function schemaErrorLookup(errors, _fields, name) {
1986
1953
  const error = get(errors, name);
1987
- if (error || isKey(name)) {
1954
+ if ((error === null || error === void 0 ? void 0 : error.type) || (error === null || error === void 0 ? void 0 : error.message) || Array.isArray(error)) {
1988
1955
  return {
1989
1956
  error,
1990
1957
  name,
@@ -2138,24 +2105,35 @@ function createFormControl(props = {}) {
2138
2105
  let _proxySubscribeFormState = {
2139
2106
  ..._proxyFormState,
2140
2107
  };
2108
+ const _isTracked = (...keys) => keys.some((key) => _proxyFormState[key] || _proxySubscribeFormState[key]);
2141
2109
  const _subjects = {
2142
2110
  array: createSubject(),
2143
2111
  state: createSubject(),
2144
2112
  };
2145
2113
  let _setValidCallId = 0;
2146
- const shouldDisplayAllAssociatedErrors = _options.criteriaMode === VALIDATION_MODE.all;
2114
+ let _resetCallId = 0;
2115
+ let shouldDisplayAllAssociatedErrors = _options.criteriaMode === VALIDATION_MODE.all;
2147
2116
  const debounce = (name, callback) => (wait) => {
2148
2117
  clearTimeout(timers[name]);
2149
2118
  timers[name] = setTimeout(callback, wait);
2150
2119
  };
2120
+ const cancelDelayedError = (name) => {
2121
+ clearTimeout(timers[name]);
2122
+ delete timers[name];
2123
+ delete delayErrorCallbacks[name];
2124
+ };
2125
+ const cancelDelayedErrorTree = (name) => {
2126
+ cancelDelayedError(name);
2127
+ const prefix = `${name}.`;
2128
+ for (const key of Object.keys(delayErrorCallbacks)) {
2129
+ key.startsWith(prefix) && cancelDelayedError(key);
2130
+ }
2131
+ };
2151
2132
  const _setValid = async (shouldUpdateValid) => {
2152
2133
  if (_state.keepIsValid) {
2153
2134
  return;
2154
2135
  }
2155
- if (!_options.disabled &&
2156
- (_proxyFormState.isValid ||
2157
- _proxySubscribeFormState.isValid ||
2158
- shouldUpdateValid)) {
2136
+ if (!_options.disabled && (_isTracked('isValid') || shouldUpdateValid)) {
2159
2137
  const callId = ++_setValidCallId;
2160
2138
  let isValid;
2161
2139
  if (_options.resolver) {
@@ -2177,11 +2155,7 @@ function createFormControl(props = {}) {
2177
2155
  }
2178
2156
  };
2179
2157
  const _updateIsValidating = (names, isValidating) => {
2180
- if (!_options.disabled &&
2181
- (_proxyFormState.isValidating ||
2182
- _proxyFormState.validatingFields ||
2183
- _proxySubscribeFormState.isValidating ||
2184
- _proxySubscribeFormState.validatingFields)) {
2158
+ if (!_options.disabled && _isTracked('isValidating', 'validatingFields')) {
2185
2159
  (names || _names.mount).forEach((name) => {
2186
2160
  if (name) {
2187
2161
  isValidating
@@ -2220,14 +2194,13 @@ function createFormControl(props = {}) {
2220
2194
  unsetEmptyArray(_formState.errors, name);
2221
2195
  }
2222
2196
  const touchedFieldsArray = get(_formState.touchedFields, name);
2223
- if ((_proxyFormState.touchedFields ||
2224
- _proxySubscribeFormState.touchedFields) &&
2197
+ if (_isTracked('touchedFields') &&
2225
2198
  shouldUpdateFieldsAndState &&
2226
2199
  Array.isArray(touchedFieldsArray)) {
2227
2200
  const touchedFields = method(touchedFieldsArray, args.argA, args.argB);
2228
2201
  shouldSetValues && set(_formState.touchedFields, name, touchedFields);
2229
2202
  }
2230
- if (_proxyFormState.dirtyFields || _proxySubscribeFormState.dirtyFields) {
2203
+ if (_isTracked('dirtyFields')) {
2231
2204
  _updateDirtyFields();
2232
2205
  }
2233
2206
  _subjects.state.next({
@@ -2250,6 +2223,7 @@ function createFormControl(props = {}) {
2250
2223
  });
2251
2224
  };
2252
2225
  const _setErrors = (errors) => {
2226
+ Object.keys(delayErrorCallbacks).forEach(cancelDelayedError);
2253
2227
  _formState.errors = errors;
2254
2228
  _subjects.state.next({
2255
2229
  errors: _formState.errors,
@@ -2272,7 +2246,7 @@ function createFormControl(props = {}) {
2272
2246
  }
2273
2247
  return false;
2274
2248
  };
2275
- const isStaleArrayIndex = (name) => {
2249
+ const isStaleArrayField = (name) => {
2276
2250
  if (!_state.actionArrayLengths.size) {
2277
2251
  return false;
2278
2252
  }
@@ -2299,13 +2273,19 @@ function createFormControl(props = {}) {
2299
2273
  ownerPreActionLength = _state.actionArrayLengths.get(path);
2300
2274
  }
2301
2275
  node = node[key];
2276
+ if (isUndefined(node) &&
2277
+ ownerDepth !== -1 &&
2278
+ i > ownerDepth &&
2279
+ +segments[ownerDepth] < ownerPreActionLength) {
2280
+ return true;
2281
+ }
2302
2282
  }
2303
2283
  return false;
2304
2284
  };
2305
2285
  const updateValidAndValue = (name, shouldSkipSetValueAs, value, ref) => {
2306
2286
  const field = get(_fields, name);
2307
2287
  if (field) {
2308
- if (hasExplicitNullIntermediate(name) || isStaleArrayIndex(name)) {
2288
+ if (hasExplicitNullIntermediate(name) || isStaleArrayField(name)) {
2309
2289
  return;
2310
2290
  }
2311
2291
  const wasUnsetInFormValues = isUndefined(get(_formValues, name));
@@ -2319,7 +2299,7 @@ function createFormControl(props = {}) {
2319
2299
  _setValid();
2320
2300
  if (wasUnsetInFormValues &&
2321
2301
  _formState.isDirty &&
2322
- (_proxyFormState.isDirty || _proxySubscribeFormState.isDirty)) {
2302
+ _isTracked('isDirty')) {
2323
2303
  const isDirty = _getDirty();
2324
2304
  if (!isDirty) {
2325
2305
  _formState.isDirty = false;
@@ -2346,7 +2326,7 @@ function createFormControl(props = {}) {
2346
2326
  if (!_options.disabled || shouldDirty === true) {
2347
2327
  if (!isBlurEvent || shouldDirty) {
2348
2328
  const isCurrentFieldPristine = deepEqual(get(_defaultValues, name), fieldValue);
2349
- if (_proxyFormState.isDirty || _proxySubscribeFormState.isDirty) {
2329
+ if (_isTracked('isDirty')) {
2350
2330
  isPreviousDirty = _formState.isDirty;
2351
2331
  _formState.isDirty = output.isDirty =
2352
2332
  !isCurrentFieldPristine || _getDirty();
@@ -2364,8 +2344,7 @@ function createFormControl(props = {}) {
2364
2344
  output.dirtyFields = _formState.dirtyFields;
2365
2345
  shouldUpdateField =
2366
2346
  shouldUpdateField ||
2367
- ((_proxyFormState.dirtyFields ||
2368
- _proxySubscribeFormState.dirtyFields) &&
2347
+ (_isTracked('dirtyFields') &&
2369
2348
  isPreviousDirty !== !isCurrentFieldPristine);
2370
2349
  }
2371
2350
  if (isBlurEvent) {
@@ -2375,8 +2354,7 @@ function createFormControl(props = {}) {
2375
2354
  output.touchedFields = _formState.touchedFields;
2376
2355
  shouldUpdateField =
2377
2356
  shouldUpdateField ||
2378
- ((_proxyFormState.touchedFields ||
2379
- _proxySubscribeFormState.touchedFields) &&
2357
+ (_isTracked('touchedFields') &&
2380
2358
  isPreviousFieldTouched !== isBlurEvent);
2381
2359
  }
2382
2360
  }
@@ -2386,7 +2364,7 @@ function createFormControl(props = {}) {
2386
2364
  };
2387
2365
  const shouldRenderByError = (name, isValid, error, fieldState) => {
2388
2366
  const previousFieldError = get(_formState.errors, name);
2389
- const shouldUpdateValid = (_proxyFormState.isValid || _proxySubscribeFormState.isValid) &&
2367
+ const shouldUpdateValid = _isTracked('isValid') &&
2390
2368
  isBoolean(isValid) &&
2391
2369
  _formState.isValid !== isValid;
2392
2370
  if (_options.delayError && error) {
@@ -2394,8 +2372,7 @@ function createFormControl(props = {}) {
2394
2372
  delayErrorCallbacks[name](_options.delayError);
2395
2373
  }
2396
2374
  else {
2397
- clearTimeout(timers[name]);
2398
- delete delayErrorCallbacks[name];
2375
+ cancelDelayedError(name);
2399
2376
  error
2400
2377
  ? set(_formState.errors, name, error)
2401
2378
  : unset(_formState.errors, name);
@@ -2418,22 +2395,34 @@ function createFormControl(props = {}) {
2418
2395
  return await _options.resolver(_formValues, _options.context, getResolverOptions(name || _names.mount, _fields, _options.criteriaMode, _options.shouldUseNativeValidation));
2419
2396
  };
2420
2397
  const executeSchemaAndUpdateState = async (names) => {
2398
+ const resetCallId = _resetCallId;
2421
2399
  const { errors } = await _runSchema(names);
2400
+ if (resetCallId !== _resetCallId) {
2401
+ return errors;
2402
+ }
2422
2403
  _updateIsValidating(names);
2423
2404
  if (names) {
2424
2405
  for (const name of names) {
2425
2406
  const error = get(errors, name);
2426
- error
2427
- ? _names.array.has(name) &&
2428
- isObject(error) &&
2429
- !Object.keys(error).some((key) => !Number.isNaN(Number(key)))
2430
- ? updateFieldArrayRootError(_formState.errors, { [name]: error }, name)
2431
- : set(_formState.errors, name, error)
2432
- : unset(_formState.errors, name);
2407
+ cancelDelayedError(name);
2408
+ const isFieldArrayRootError = _names.array.has(name) &&
2409
+ isObject(error) &&
2410
+ !Object.keys(error).some((key) => !Number.isNaN(Number(key)));
2411
+ const field = get(_fields, name);
2412
+ const hasNestedFields = isObject(field) && Object.keys(field).some((key) => key !== '_f');
2413
+ isFieldArrayRootError
2414
+ ? updateFieldArrayRootError(_formState.errors, { [name]: error }, name)
2415
+ : (error === null || error === void 0 ? void 0 : error.type) ||
2416
+ (error === null || error === void 0 ? void 0 : error.message) ||
2417
+ Array.isArray(error) ||
2418
+ (isObject(error) && hasNestedFields)
2419
+ ? set(_formState.errors, name, error)
2420
+ : unset(_formState.errors, name);
2433
2421
  }
2434
2422
  _formState.errors = { ..._formState.errors };
2435
2423
  }
2436
2424
  else {
2425
+ Object.keys(delayErrorCallbacks).forEach(cancelDelayedError);
2437
2426
  _formState.errors = errors;
2438
2427
  }
2439
2428
  return errors;
@@ -2494,10 +2483,7 @@ function createFormControl(props = {}) {
2494
2483
  if (_f) {
2495
2484
  const isFieldArrayRoot = _names.array.has(_f.name);
2496
2485
  const isPromiseFunction = field._f && hasPromiseValidation(field._f);
2497
- const shouldTrackIsValidatingState = _proxyFormState.validatingFields ||
2498
- _proxyFormState.isValidating ||
2499
- _proxySubscribeFormState.validatingFields ||
2500
- _proxySubscribeFormState.isValidating;
2486
+ const shouldTrackIsValidatingState = _isTracked('isValidating', 'validatingFields');
2501
2487
  if (isPromiseFunction && shouldTrackIsValidatingState) {
2502
2488
  _updateIsValidating([_f.name], true);
2503
2489
  }
@@ -2511,12 +2497,14 @@ function createFormControl(props = {}) {
2511
2497
  break;
2512
2498
  }
2513
2499
  }
2514
- !onlyCheckValid &&
2515
- (get(fieldError, _f.name)
2500
+ if (!onlyCheckValid) {
2501
+ cancelDelayedError(_f.name);
2502
+ get(fieldError, _f.name)
2516
2503
  ? isFieldArrayRoot
2517
2504
  ? updateFieldArrayRootError(_formState.errors, fieldError, _f.name)
2518
2505
  : set(_formState.errors, _f.name, fieldError[_f.name])
2519
- : unset(_formState.errors, _f.name));
2506
+ : unset(_formState.errors, _f.name);
2507
+ }
2520
2508
  if (props.shouldUseNativeValidation && fieldError[_f.name]) {
2521
2509
  break;
2522
2510
  }
@@ -2602,7 +2590,14 @@ function createFormControl(props = {}) {
2602
2590
  }
2603
2591
  }
2604
2592
  (options.shouldDirty || options.shouldTouch) &&
2605
- updateTouchAndDirty(name, fieldValue, options.shouldTouch, options.shouldDirty, !skipRender);
2593
+ updateTouchAndDirty(name, field &&
2594
+ field._f &&
2595
+ !field._f.disabled &&
2596
+ (field._f.valueAsNumber ||
2597
+ field._f.valueAsDate ||
2598
+ field._f.setValueAs)
2599
+ ? getFieldValueAs(value, field._f)
2600
+ : fieldValue, options.shouldTouch, options.shouldDirty, !skipRender);
2606
2601
  options.shouldValidate &&
2607
2602
  trigger(name, {
2608
2603
  delayError: options.delayError,
@@ -2617,7 +2612,7 @@ function createFormControl(props = {}) {
2617
2612
  }
2618
2613
  for (const fieldKey in value) {
2619
2614
  if (!value.hasOwnProperty(fieldKey)) {
2620
- return;
2615
+ continue;
2621
2616
  }
2622
2617
  const fieldValue = value[fieldKey];
2623
2618
  const fieldName = name + '.' + fieldKey;
@@ -2644,11 +2639,7 @@ function createFormControl(props = {}) {
2644
2639
  name,
2645
2640
  values: skipClone ? _formValues : cloneObject(_formValues),
2646
2641
  });
2647
- if ((_proxyFormState.isDirty ||
2648
- _proxyFormState.dirtyFields ||
2649
- _proxySubscribeFormState.isDirty ||
2650
- _proxySubscribeFormState.dirtyFields) &&
2651
- options.shouldDirty) {
2642
+ if (_isTracked('isDirty', 'dirtyFields') && options.shouldDirty) {
2652
2643
  _updateDirtyFields();
2653
2644
  if (!skipStateEmit) {
2654
2645
  _subjects.state.next({
@@ -2738,7 +2729,7 @@ function createFormControl(props = {}) {
2738
2729
  const shouldSkipValidation = hasNoValidationEffect ||
2739
2730
  skipValidation(isBlurEvent, get(_formState.touchedFields, name), _formState.isSubmitted, _validationModeAfterSubmit, _validationModeBeforeSubmit);
2740
2731
  const watched = isWatched(name, _names, isBlurEvent);
2741
- set(_formValues, name, fieldValue);
2732
+ set(_formValues, name, cloneObject(fieldValue));
2742
2733
  if (isBlurEvent) {
2743
2734
  if (!target || !target.readOnly) {
2744
2735
  field._f.onBlur && field._f.onBlur(event);
@@ -2761,7 +2752,7 @@ function createFormControl(props = {}) {
2761
2752
  });
2762
2753
  if (shouldSkipValidation) {
2763
2754
  if ((!hasNoValidationEffect || !_formState.isValid) &&
2764
- (_proxyFormState.isValid || _proxySubscribeFormState.isValid)) {
2755
+ _isTracked('isValid')) {
2765
2756
  if (_options.mode === 'onBlur') {
2766
2757
  if (isBlurEvent) {
2767
2758
  _setValid();
@@ -2804,8 +2795,7 @@ function createFormControl(props = {}) {
2804
2795
  if (error) {
2805
2796
  isValid = false;
2806
2797
  }
2807
- else if (_proxyFormState.isValid ||
2808
- _proxySubscribeFormState.isValid) {
2798
+ else if (_isTracked('isValid')) {
2809
2799
  isValid = await executeBuiltInValidation({
2810
2800
  fields: _fields,
2811
2801
  onlyCheckValid: true,
@@ -2835,11 +2825,15 @@ function createFormControl(props = {}) {
2835
2825
  let validationResult;
2836
2826
  const fieldNames = convertToArrayPayload(name);
2837
2827
  if (_options.resolver) {
2828
+ const resetCallId = _resetCallId;
2838
2829
  const errors = await executeSchemaAndUpdateState(isUndefined(name) ? name : fieldNames);
2839
2830
  isValid = isEmptyObject(errors);
2840
2831
  validationResult = name
2841
2832
  ? !fieldNames.some((name) => get(errors, name))
2842
2833
  : isValid;
2834
+ if (resetCallId !== _resetCallId) {
2835
+ return validationResult;
2836
+ }
2843
2837
  }
2844
2838
  else if (name) {
2845
2839
  validationResult = (await Promise.all(fieldNames.map(async (fieldName) => {
@@ -2866,17 +2860,24 @@ function createFormControl(props = {}) {
2866
2860
  delayErrorCallbacks[name](_options.delayError);
2867
2861
  }
2868
2862
  else {
2869
- clearTimeout(timers[name]);
2870
- delete delayErrorCallbacks[name];
2863
+ cancelDelayedError(name);
2864
+ }
2865
+ }
2866
+ if (options.shouldTouch) {
2867
+ for (const fieldName of name ? fieldNames : _names.mount) {
2868
+ !_names.array.has(fieldName) &&
2869
+ set(_formState.touchedFields, fieldName, true);
2871
2870
  }
2872
2871
  }
2873
2872
  _subjects.state.next({
2874
2873
  ...(!isString(name) ||
2875
- ((_proxyFormState.isValid || _proxySubscribeFormState.isValid) &&
2876
- isValid !== _formState.isValid)
2874
+ (_isTracked('isValid') && isValid !== _formState.isValid)
2877
2875
  ? {}
2878
2876
  : { name }),
2879
2877
  ...(_options.resolver || !name ? { isValid } : {}),
2878
+ ...(options.shouldTouch && _isTracked('touchedFields')
2879
+ ? { touchedFields: _formState.touchedFields }
2880
+ : {}),
2880
2881
  errors: _formState.errors,
2881
2882
  });
2882
2883
  options.shouldFocus &&
@@ -2909,15 +2910,16 @@ function createFormControl(props = {}) {
2909
2910
  invalid: !!error,
2910
2911
  isDirty: !!get(targetFormState.dirtyFields, name),
2911
2912
  error,
2912
- isValidating: !!get(_formState.validatingFields, name),
2913
+ isValidating: !!get(targetFormState.validatingFields, name),
2913
2914
  isTouched: !!get(targetFormState.touchedFields, name),
2914
2915
  };
2915
2916
  };
2916
2917
  const clearErrors = (name) => {
2917
2918
  const names = name ? convertToArrayPayload(name) : undefined;
2918
- names === null || names === void 0 ? void 0 : names.forEach((inputName) => unset(_formState.errors, inputName));
2919
2919
  if (names) {
2920
2920
  names.forEach((inputName) => {
2921
+ cancelDelayedErrorTree(inputName);
2922
+ unset(_formState.errors, inputName);
2921
2923
  _subjects.state.next({
2922
2924
  name: inputName,
2923
2925
  errors: _formState.errors,
@@ -2925,6 +2927,7 @@ function createFormControl(props = {}) {
2925
2927
  });
2926
2928
  }
2927
2929
  else {
2930
+ Object.keys(delayErrorCallbacks).forEach(cancelDelayedError);
2928
2931
  _formState.errors = {};
2929
2932
  _subjects.state.next({
2930
2933
  errors: _formState.errors,
@@ -2932,9 +2935,10 @@ function createFormControl(props = {}) {
2932
2935
  }
2933
2936
  };
2934
2937
  const setError = (name, error, options) => {
2938
+ cancelDelayedError(name);
2935
2939
  const ref = (get(_fields, name, { _f: {} })._f || {}).ref;
2936
2940
  const currentError = get(_formState.errors, name) || {};
2937
- const { ref: currentRef, message, type, ...restOfErrorTree } = currentError;
2941
+ const { ref: currentRef, message, type, types, ...restOfErrorTree } = currentError;
2938
2942
  set(_formState.errors, name, {
2939
2943
  ...restOfErrorTree,
2940
2944
  ...error,
@@ -3019,11 +3023,15 @@ function createFormControl(props = {}) {
3019
3023
  for (const fieldName of name ? convertToArrayPayload(name) : _names.mount) {
3020
3024
  _names.mount.delete(fieldName);
3021
3025
  _names.array.delete(fieldName);
3026
+ _names.disabled.delete(fieldName);
3022
3027
  if (!options.keepValue) {
3023
3028
  unset(_fields, fieldName);
3024
3029
  unset(_formValues, fieldName);
3025
3030
  }
3026
- !options.keepError && unset(_formState.errors, fieldName);
3031
+ if (!options.keepError) {
3032
+ cancelDelayedErrorTree(fieldName);
3033
+ unset(_formState.errors, fieldName);
3034
+ }
3027
3035
  !options.keepDirty && unset(_formState.dirtyFields, fieldName);
3028
3036
  !options.keepTouched && unset(_formState.touchedFields, fieldName);
3029
3037
  !options.keepIsValidating &&
@@ -3039,6 +3047,9 @@ function createFormControl(props = {}) {
3039
3047
  _subjects.state.next({
3040
3048
  ..._formState,
3041
3049
  ...(options.keepDirty ? {} : { isDirty: _getDirty() }),
3050
+ ...(options.keepIsValidating
3051
+ ? {}
3052
+ : { isValidating: !isEmptyObject(_formState.validatingFields) }),
3042
3053
  });
3043
3054
  !options.keepIsValid && _setValid();
3044
3055
  };
@@ -3161,7 +3172,7 @@ function createFormControl(props = {}) {
3161
3172
  });
3162
3173
  }
3163
3174
  }
3164
- }, 0, false);
3175
+ }, 0);
3165
3176
  }
3166
3177
  };
3167
3178
  const handleSubmit = (onValid, onInvalid) => async (e) => {
@@ -3177,8 +3188,13 @@ function createFormControl(props = {}) {
3177
3188
  isSubmitting: true,
3178
3189
  });
3179
3190
  if (_options.resolver) {
3191
+ const resetCallId = _resetCallId;
3180
3192
  const { errors, values } = await _runSchema();
3193
+ if (resetCallId !== _resetCallId) {
3194
+ return;
3195
+ }
3181
3196
  _updateIsValidating();
3197
+ Object.keys(delayErrorCallbacks).forEach(cancelDelayedError);
3182
3198
  _formState.errors = errors;
3183
3199
  fieldValues = cloneObject(values);
3184
3200
  }
@@ -3187,13 +3203,13 @@ function createFormControl(props = {}) {
3187
3203
  fields: _fields,
3188
3204
  eventType: EVENTS.SUBMIT,
3189
3205
  });
3206
+ unset(_formState.errors, ROOT_ERROR_TYPE);
3190
3207
  }
3191
3208
  if (_names.disabled.size) {
3192
3209
  for (const name of _names.disabled) {
3193
3210
  unset(fieldValues, name);
3194
3211
  }
3195
3212
  }
3196
- unset(_formState.errors, ROOT_ERROR_TYPE);
3197
3213
  if (isEmptyObject(_formState.errors)) {
3198
3214
  _subjects.state.next({
3199
3215
  errors: {},
@@ -3226,6 +3242,7 @@ function createFormControl(props = {}) {
3226
3242
  };
3227
3243
  const resetField = (name, options = {}) => {
3228
3244
  if (get(_fields, name)) {
3245
+ unset(_formState.validatingFields, name);
3229
3246
  if (isUndefined(options.defaultValue)) {
3230
3247
  setValue(name, cloneObject(get(_defaultValues, name)));
3231
3248
  }
@@ -3243,18 +3260,24 @@ function createFormControl(props = {}) {
3243
3260
  : _getDirty();
3244
3261
  }
3245
3262
  if (!options.keepError) {
3263
+ cancelDelayedError(name);
3246
3264
  unset(_formState.errors, name);
3247
- _proxyFormState.isValid && _setValid();
3265
+ _setValid();
3248
3266
  }
3249
- _subjects.state.next({ ..._formState });
3267
+ _subjects.state.next({
3268
+ ..._formState,
3269
+ isValidating: !isEmptyObject(_formState.validatingFields),
3270
+ });
3250
3271
  }
3251
3272
  };
3252
3273
  const _reset = (formValues, keepStateOptions = {}) => {
3274
+ _resetCallId++;
3253
3275
  const updatedValues = formValues ? cloneObject(formValues) : _defaultValues;
3254
3276
  const cloneUpdatedValues = cloneObject(updatedValues);
3255
3277
  const isEmptyResetValues = isEmptyObject(formValues);
3256
3278
  const values = cloneUpdatedValues;
3257
3279
  const fieldRefs = _fields;
3280
+ Object.keys(delayErrorCallbacks).forEach(cancelDelayedError);
3258
3281
  if (!keepStateOptions.keepDefaultValues) {
3259
3282
  _defaultValues = updatedValues;
3260
3283
  }
@@ -3372,10 +3395,16 @@ function createFormControl(props = {}) {
3372
3395
  ? getDirtyFields(_defaultValues, formValues, undefined, fieldRefs)
3373
3396
  : keepStateOptions.keepDirty
3374
3397
  ? _formState.dirtyFields
3375
- : {},
3398
+ : keepStateOptions.keepValues
3399
+ ? getDirtyFields(_defaultValues, _formValues, undefined, fieldRefs)
3400
+ : {},
3376
3401
  touchedFields: keepStateOptions.keepTouched
3377
3402
  ? _formState.touchedFields
3378
3403
  : {},
3404
+ ...(!keepStateOptions.keepIsValidating &&
3405
+ (_formState.isValidating || !isEmptyObject(_formState.validatingFields))
3406
+ ? { validatingFields: {}, isValidating: false }
3407
+ : null),
3379
3408
  errors: keepStateOptions.keepErrors ? _formState.errors : {},
3380
3409
  isSubmitSuccessful: keepStateOptions.keepIsSubmitSuccessful
3381
3410
  ? _formState.isSubmitSuccessful
@@ -3497,6 +3526,8 @@ function createFormControl(props = {}) {
3497
3526
  };
3498
3527
  _validationModeBeforeSubmit = getValidationModes(_options.mode);
3499
3528
  _validationModeAfterSubmit = getValidationModes(_options.reValidateMode);
3529
+ shouldDisplayAllAssociatedErrors =
3530
+ _options.criteriaMode === VALIDATION_MODE.all;
3500
3531
  },
3501
3532
  },
3502
3533
  subscribe,
@@ -3524,32 +3555,17 @@ function createFormControl(props = {}) {
3524
3555
  }
3525
3556
 
3526
3557
  /**
3527
- * Custom hook to manage the entire form.
3528
- *
3529
- * @remarks
3530
- * [API](https://react-hook-form.com/docs/useform) • [Demo](https://codesandbox.io/s/react-hook-form-get-started-ts-5ksmm) • [Video](https://www.youtube.com/watch?v=RkXv4AXXC_4)
3558
+ * Core hook for managing a form. Returns all methods and state for
3559
+ * registration, validation, and submission.
3531
3560
  *
3532
- * @param props - form configuration and validation parameters.
3533
- *
3534
- * @returns methods - individual functions to manage the form state. {@link UseFormReturn}
3561
+ * @see [API](https://react-hook-form.com/docs/useform)
3535
3562
  *
3536
3563
  * @example
3537
3564
  * ```tsx
3538
- * function App() {
3539
- * const { register, handleSubmit, watch, formState: { errors } } = useForm();
3540
- * const onSubmit = data => console.log(data);
3541
- *
3542
- * console.log(watch("example"));
3543
- *
3544
- * return (
3545
- * <form onSubmit={handleSubmit(onSubmit)}>
3546
- * <input defaultValue="test" {...register("example")} />
3547
- * <input {...register("exampleRequired", { required: true })} />
3548
- * {errors.exampleRequired && <span>This field is required</span>}
3549
- * <button>Submit</button>
3550
- * </form>
3551
- * );
3552
- * }
3565
+ * const { register, handleSubmit, formState: { errors } } = useForm<FormValues>();
3566
+ * <form onSubmit={handleSubmit(onSubmit)}>
3567
+ * <input {...register("email", { required: true })} />
3568
+ * </form>
3553
3569
  * ```
3554
3570
  */
3555
3571
  function useForm(props = {}) {
@@ -3587,12 +3603,12 @@ function useForm(props = {}) {
3587
3603
  }
3588
3604
  const control = _formControl.current.control;
3589
3605
  control._options = props;
3590
- const { resyncIfNeeded, snapshot } = useResyncOnReconnect();
3606
+ const getCurrentFormState = () => ({
3607
+ ...control._formState,
3608
+ defaultValues: control._defaultValues,
3609
+ });
3610
+ const { resyncIfNeeded, snapshot } = useResyncOnReconnect(getCurrentFormState);
3591
3611
  useIsomorphicLayoutEffect(() => {
3592
- const getCurrentFormState = () => ({
3593
- ...control._formState,
3594
- defaultValues: control._defaultValues,
3595
- });
3596
3612
  resyncIfNeeded(true, getCurrentFormState, updateFormState);
3597
3613
  const unsubscribe = control._subscribe({
3598
3614
  formState: control._proxyFormState,
@@ -3676,32 +3692,20 @@ function useForm(props = {}) {
3676
3692
  }
3677
3693
 
3678
3694
  /**
3679
- * Watch component that subscribes to form field changes and re-renders when watched fields update.
3695
+ * Component wrapper around `useWatch`. Re-renders only when watched fields change.
3680
3696
  *
3681
- * @param control - The form control object from useForm
3682
- * @param name - Can be field name, array of field names, or undefined to watch the entire form
3683
- * @param disabled - Disable subscription
3684
- * @param exact - Whether to watch exact field names or not
3685
- * @param defaultValue - The default value to use if the field is not yet set
3686
- * @param compute - Function to compute derived values from watched fields
3687
- * @param render - The function that receives watched values and returns ReactNode
3688
- * @returns The result of calling render function with watched values
3697
+ * @see [API](https://react-hook-form.com/docs/usewatch)
3689
3698
  *
3690
3699
  * @example
3691
- * The `Watch` component only re-render when the values of `foo`, `bar`, and `baz.qux` change.
3692
- * The types of `foo`, `bar`, and `baz.qux` are precisely inferred.
3693
- *
3694
3700
  * ```tsx
3695
- * const { control } = useForm();
3696
- *
3697
3701
  * <Watch
3698
3702
  * control={control}
3699
- * names={['foo', 'bar', 'baz.qux']}
3700
- * render={([foo, bar, baz_qux]) => <div>{foo}{bar}{baz_qux}</div>}
3703
+ * names={["foo", "bar"]}
3704
+ * render={([foo, bar]) => <span>{foo} {bar}</span>}
3701
3705
  * />
3702
3706
  * ```
3703
3707
  */
3704
3708
  const Watch = (props) => props.render(useWatch({ name: props.names, ...props }));
3705
3709
 
3706
- export { Controller, FieldArray, Form, FormProvider, FormState, FormStateSubscribe, Watch, appendErrors, createFormControl, get, set, useController, useFieldArray, useForm, useFormContext, useFormState, useWatch };
3710
+ export { Controller, ErrorMessage, FieldArray, Form, FormProvider, FormState, FormStateSubscribe, Watch, appendErrors, createFormControl, get, set, useController, useFieldArray, useForm, useFormContext, useFormState, useWatch };
3707
3711
  //# sourceMappingURL=index.esm.mjs.map