react-hook-form 7.76.1 → 7.78.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.
@@ -52,19 +52,48 @@ function cloneObject(data) {
52
52
  return copy;
53
53
  }
54
54
 
55
+ const EVENTS = {
56
+ BLUR: 'blur',
57
+ FOCUS_OUT: 'focusout',
58
+ CHANGE: 'change',
59
+ SUBMIT: 'submit',
60
+ TRIGGER: 'trigger',
61
+ VALID: 'valid',
62
+ };
63
+ const VALIDATION_MODE = {
64
+ onBlur: 'onBlur',
65
+ onChange: 'onChange',
66
+ onSubmit: 'onSubmit',
67
+ onTouched: 'onTouched',
68
+ all: 'all',
69
+ };
70
+ const INPUT_VALIDATION_RULES = {
71
+ max: 'max',
72
+ min: 'min',
73
+ maxLength: 'maxLength',
74
+ minLength: 'minLength',
75
+ pattern: 'pattern',
76
+ required: 'required',
77
+ validate: 'validate',
78
+ };
79
+ const FORM_ERROR_TYPE = 'form';
80
+ const ROOT_ERROR_TYPE = 'root';
81
+ const PROTOTYPE_KEYWORDS = ['__proto__', 'constructor', 'prototype'];
82
+
55
83
  var isKey = (value) => /^\w*$/.test(value);
56
84
 
57
85
  var isUndefined = (val) => val === undefined;
58
86
 
59
- var compact = (value) => Array.isArray(value) ? value.filter(Boolean) : [];
60
-
61
- var stringToPath = (input) => compact(input.replace(/["|']|\]/g, '').split(/\.|\[/));
87
+ var stringToPath = (input) => input.split(/[.[\]'"]/g).filter(Boolean);
62
88
 
63
89
  var get = (object, path, defaultValue) => {
64
90
  if (!path || !isObject(object)) {
65
91
  return defaultValue;
66
92
  }
67
93
  const paths = isKey(path) ? [path] : stringToPath(path);
94
+ if (paths.some((key) => PROTOTYPE_KEYWORDS.includes(key))) {
95
+ return defaultValue;
96
+ }
68
97
  const result = paths.reduce((result, key) => {
69
98
  return isNullOrUndefined(result) ? undefined : result[key];
70
99
  }, object);
@@ -96,7 +125,7 @@ var set = (object, path, value) => {
96
125
  ? []
97
126
  : {};
98
127
  }
99
- if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
128
+ if (PROTOTYPE_KEYWORDS.includes(key)) {
100
129
  return;
101
130
  }
102
131
  object[key] = newValue;
@@ -104,33 +133,6 @@ var set = (object, path, value) => {
104
133
  }
105
134
  };
106
135
 
107
- const EVENTS = {
108
- BLUR: 'blur',
109
- FOCUS_OUT: 'focusout',
110
- CHANGE: 'change',
111
- SUBMIT: 'submit',
112
- TRIGGER: 'trigger',
113
- VALID: 'valid',
114
- };
115
- const VALIDATION_MODE = {
116
- onBlur: 'onBlur',
117
- onChange: 'onChange',
118
- onSubmit: 'onSubmit',
119
- onTouched: 'onTouched',
120
- all: 'all',
121
- };
122
- const INPUT_VALIDATION_RULES = {
123
- max: 'max',
124
- min: 'min',
125
- maxLength: 'maxLength',
126
- minLength: 'minLength',
127
- pattern: 'pattern',
128
- required: 'required',
129
- validate: 'validate',
130
- };
131
- const FORM_ERROR_TYPE = 'form';
132
- const ROOT_ERROR_TYPE = 'root';
133
-
134
136
  /**
135
137
  * Separate context for `control` to prevent unnecessary rerenders.
136
138
  * Internal hooks that only need control use this instead of full form context.
@@ -159,7 +161,9 @@ var getProxyFormState = (formState, control, localProxyFormState, isRoot = true)
159
161
  return result;
160
162
  };
161
163
 
162
- const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;
164
+ const useIsomorphicLayoutEffect = isWeb
165
+ ? React.useLayoutEffect
166
+ : React.useEffect;
163
167
 
164
168
  /**
165
169
  * This custom hook allows you to subscribe to each form state, and isolate the re-render at the custom hook level. It has its scope in terms of form state subscription, so it would not affect other useFormState and useForm. Using this hook can reduce the re-render impact on large and complex form application.
@@ -244,6 +248,7 @@ var generateWatchOutput = (names, _names, formValues, isGlobal, defaultValue) =>
244
248
 
245
249
  var isPrimitive = (value) => isNullOrUndefined(value) || !isObjectType(value);
246
250
 
251
+ const isEmptyObjectWithCustomPrototype = (object, keys) => keys.length === 0 && !Array.isArray(object) && !isPlainObject(object);
247
252
  function deepEqual(object1, object2, visited = new WeakSet()) {
248
253
  if (object1 === object2) {
249
254
  return true;
@@ -259,6 +264,10 @@ function deepEqual(object1, object2, visited = new WeakSet()) {
259
264
  if (keys1.length !== keys2.length) {
260
265
  return false;
261
266
  }
267
+ if (isEmptyObjectWithCustomPrototype(object1, keys1) ||
268
+ isEmptyObjectWithCustomPrototype(object2, keys2)) {
269
+ return Object.is(object1, object2);
270
+ }
262
271
  if (visited.has(object1) || visited.has(object2)) {
263
272
  return true;
264
273
  }
@@ -438,13 +447,22 @@ function useController(props) {
438
447
  get: () => get(formState.errors, name),
439
448
  },
440
449
  }), [formState, name]);
441
- const onChange = React.useCallback((event) => _registerProps.current.onChange({
442
- target: {
443
- value: getEventValue(event),
444
- name: name,
445
- },
446
- type: EVENTS.CHANGE,
447
- }), [name]);
450
+ const onChange = React.useCallback((event) => {
451
+ const value = getEventValue(event);
452
+ if (!get(control._fields, name)) {
453
+ _registerProps.current = control.register(name, {
454
+ ..._props.current.rules,
455
+ value,
456
+ });
457
+ }
458
+ _registerProps.current.onChange({
459
+ target: {
460
+ value: getEventValue(event),
461
+ name: name,
462
+ },
463
+ type: EVENTS.CHANGE,
464
+ });
465
+ }, [name, control]);
448
466
  const onBlur = React.useCallback(() => _registerProps.current.onBlur({
449
467
  target: {
450
468
  value: get(control._formValues, name),
@@ -489,7 +507,9 @@ function useController(props) {
489
507
  };
490
508
  updateMounted(name, true);
491
509
  if (_shouldUnregisterField) {
492
- const value = cloneObject(get(control._defaultValues, name, get(control._options.defaultValues, name, _props.current.defaultValue)));
510
+ const value = cloneObject(get(shouldUnregister
511
+ ? control._defaultValues
512
+ : control._options.values || control._defaultValues, name, get(control._options.defaultValues, name, _props.current.defaultValue)));
493
513
  set(control._defaultValues, name, value);
494
514
  if (isUndefined(get(control._formValues, name))) {
495
515
  set(control._formValues, name, value);
@@ -640,8 +660,7 @@ const useFormContext = () => React.useContext(HookFormContext);
640
660
  * }
641
661
  * ```
642
662
  */
643
- const FormProvider = (props) => {
644
- const { children, watch, getValues, getFieldState, setError, clearErrors, setValue, setValues, trigger, formState, resetField, reset, handleSubmit, unregister, control, register, setFocus, subscribe, } = props;
663
+ const FormProvider = ({ children, watch, getValues, getFieldState, setError, clearErrors, setValue, setValues, trigger, formState, resetField, reset, handleSubmit, unregister, control, register, setFocus, subscribe, }) => {
645
664
  const memoizedValue = React.useMemo(() => ({
646
665
  watch,
647
666
  getValues,
@@ -806,6 +825,8 @@ var appendErrors = (name, validateAllFieldCriteria, errors, type, message) => va
806
825
  }
807
826
  : {};
808
827
 
828
+ var compact = (value) => Array.isArray(value) ? value.filter(Boolean) : [];
829
+
809
830
  var convertToArrayPayload = (value) => (Array.isArray(value) ? value : [value]);
810
831
 
811
832
  var createSubject = () => {
@@ -1115,8 +1136,7 @@ var hasValidation = (options) => options.mount &&
1115
1136
  var isWatched = (name, _names, isBlurEvent) => !isBlurEvent &&
1116
1137
  (_names.watchAll ||
1117
1138
  _names.watch.has(name) ||
1118
- [..._names.watch].some((watchName) => name.startsWith(watchName) &&
1119
- /^\.\w+/.test(name.slice(watchName.length))));
1139
+ [..._names.watch].some((watchName) => name.startsWith(`${watchName}.`)));
1120
1140
 
1121
1141
  const iterateFieldsByAction = (fields, action, fieldsNames, abortEarly) => {
1122
1142
  for (const key of fieldsNames || Object.keys(fields)) {
@@ -1219,7 +1239,8 @@ var skipValidation = (isBlurEvent, isTouched, isSubmitted, reValidateMode, mode)
1219
1239
  var unsetEmptyArray = (ref, name) => !compact(get(ref, name)).length && unset(ref, name);
1220
1240
 
1221
1241
  var updateFieldArrayRootError = (errors, error, name) => {
1222
- const fieldArrayErrors = convertToArrayPayload(get(errors, name));
1242
+ const existingErrors = get(errors, name);
1243
+ const fieldArrayErrors = Array.isArray(existingErrors) ? existingErrors : [];
1223
1244
  set(fieldArrayErrors, ROOT_ERROR_TYPE, error[name]);
1224
1245
  set(errors, name, fieldArrayErrors);
1225
1246
  return errors;
@@ -1734,7 +1755,9 @@ function createFormControl(props = {}) {
1734
1755
  for (const name of names) {
1735
1756
  const error = get(errors, name);
1736
1757
  error
1737
- ? _names.array.has(name) && isObject(error)
1758
+ ? _names.array.has(name) &&
1759
+ isObject(error) &&
1760
+ !Object.keys(error).some((key) => !Number.isNaN(Number(key)))
1738
1761
  ? updateFieldArrayRootError(_formState.errors, { [name]: error }, name)
1739
1762
  : set(_formState.errors, name, error)
1740
1763
  : unset(_formState.errors, name);
@@ -2073,13 +2096,15 @@ function createFormControl(props = {}) {
2073
2096
  const { errors } = await _runSchema([name]);
2074
2097
  _updateIsValidating([name]);
2075
2098
  _updateIsFieldValueUpdated(fieldValue);
2076
- if (isFieldValueUpdated) {
2077
- const previousErrorLookupResult = schemaErrorLookup(_formState.errors, _fields, name);
2078
- const errorLookupResult = schemaErrorLookup(errors, _fields, previousErrorLookupResult.name || name);
2079
- error = errorLookupResult.error;
2080
- name = errorLookupResult.name;
2081
- isValid = isEmptyObject(errors);
2099
+ if (!isFieldValueUpdated) {
2100
+ !isEmptyObject(fieldState) && _subjects.state.next(fieldState);
2101
+ return;
2082
2102
  }
2103
+ const previousErrorLookupResult = schemaErrorLookup(_formState.errors, _fields, name);
2104
+ const errorLookupResult = schemaErrorLookup(errors, _fields, previousErrorLookupResult.name || name);
2105
+ error = errorLookupResult.error;
2106
+ name = errorLookupResult.name;
2107
+ isValid = isEmptyObject(errors);
2083
2108
  }
2084
2109
  else {
2085
2110
  _updateIsValidating([name], true);
@@ -2481,7 +2506,7 @@ function createFormControl(props = {}) {
2481
2506
  const updatedValues = formValues ? cloneObject(formValues) : _defaultValues;
2482
2507
  const cloneUpdatedValues = cloneObject(updatedValues);
2483
2508
  const isEmptyResetValues = isEmptyObject(formValues);
2484
- const values = isEmptyResetValues ? _defaultValues : cloneUpdatedValues;
2509
+ const values = cloneUpdatedValues;
2485
2510
  if (!keepStateOptions.keepDefaultValues) {
2486
2511
  _defaultValues = updatedValues;
2487
2512
  }
@@ -2530,11 +2555,19 @@ function createFormControl(props = {}) {
2530
2555
  _fields = {};
2531
2556
  }
2532
2557
  }
2533
- _formValues = _options.shouldUnregister
2534
- ? keepStateOptions.keepDefaultValues
2558
+ if (_options.shouldUnregister) {
2559
+ _formValues = keepStateOptions.keepDefaultValues
2535
2560
  ? cloneObject(_defaultValues)
2536
- : {}
2537
- : cloneObject(values);
2561
+ : {};
2562
+ if (keepStateOptions.keepFieldsRef) {
2563
+ for (const fieldName of _names.mount) {
2564
+ set(_formValues, fieldName, get(values, fieldName));
2565
+ }
2566
+ }
2567
+ }
2568
+ else {
2569
+ _formValues = cloneObject(values);
2570
+ }
2538
2571
  _subjects.array.next({
2539
2572
  values: { ...values },
2540
2573
  });
@@ -2574,8 +2607,10 @@ function createFormControl(props = {}) {
2574
2607
  ? false
2575
2608
  : keepStateOptions.keepDirty
2576
2609
  ? _formState.isDirty
2577
- : !!(keepStateOptions.keepDefaultValues &&
2578
- !deepEqual(formValues, _defaultValues)),
2610
+ : keepStateOptions.keepValues
2611
+ ? _getDirty()
2612
+ : !!(keepStateOptions.keepDefaultValues &&
2613
+ !deepEqual(formValues, _defaultValues)),
2579
2614
  isSubmitted: keepStateOptions.keepIsSubmitted
2580
2615
  ? _formState.isSubmitted
2581
2616
  : false,
@@ -2636,6 +2671,21 @@ function createFormControl(props = {}) {
2636
2671
  isLoading: false,
2637
2672
  });
2638
2673
  });
2674
+ const resetDefaultValues = (values, options = {}) => {
2675
+ _defaultValues = cloneObject(values);
2676
+ if (!options.keepDirty) {
2677
+ const newDirtyFields = getDirtyFields(_defaultValues, _formValues);
2678
+ _formState.dirtyFields = newDirtyFields;
2679
+ _formState.isDirty = !isEmptyObject(newDirtyFields);
2680
+ }
2681
+ if (!options.keepIsValid) {
2682
+ _setValid();
2683
+ }
2684
+ _subjects.state.next({
2685
+ ..._formState,
2686
+ defaultValues: _defaultValues,
2687
+ });
2688
+ };
2639
2689
  const methods = {
2640
2690
  control: {
2641
2691
  register,
@@ -2704,6 +2754,7 @@ function createFormControl(props = {}) {
2704
2754
  getValues,
2705
2755
  reset,
2706
2756
  resetField,
2757
+ resetDefaultValues,
2707
2758
  clearErrors,
2708
2759
  unregister,
2709
2760
  setError,
@@ -2840,6 +2891,10 @@ function useFieldArray(props) {
2840
2891
  setFields(fieldValues);
2841
2892
  ids.current = fieldValues.map(generateId);
2842
2893
  }
2894
+ else if (!fieldArrayName) {
2895
+ setFields([]);
2896
+ ids.current = [];
2897
+ }
2843
2898
  }
2844
2899
  },
2845
2900
  }).unsubscribe, [control, name]);