react-hook-form 7.79.0 → 7.80.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.
@@ -76,15 +76,16 @@ const INPUT_VALIDATION_RULES = {
76
76
  required: 'required',
77
77
  validate: 'validate',
78
78
  };
79
- const FORM_ERROR_TYPE = 'form';
80
79
  const ROOT_ERROR_TYPE = 'root';
81
80
  const PROTOTYPE_KEYWORDS = ['__proto__', 'constructor', 'prototype'];
82
81
 
83
- var isKey = (value) => /^\w*$/.test(value);
82
+ const IS_KEY_RE = /^\w*$/;
83
+ var isKey = (value) => IS_KEY_RE.test(value);
84
84
 
85
85
  var isUndefined = (val) => val === undefined;
86
86
 
87
- var stringToPath = (input) => input.split(/[.[\]'"]/g).filter(Boolean);
87
+ const FIELD_PATH_RE = /[.[\]'"]/;
88
+ var stringToPath = (input) => input.split(FIELD_PATH_RE).filter(Boolean);
88
89
 
89
90
  var get = (object, path, defaultValue) => {
90
91
  if (!path || !isObject(object)) {
@@ -268,6 +269,9 @@ function deepEqual(object1, object2, visited = new WeakMap()) {
268
269
  isEmptyObjectWithCustomPrototype(object2, keys2)) {
269
270
  return Object.is(object1, object2);
270
271
  }
272
+ if (!keys1.length && Array.isArray(object1) !== Array.isArray(object2)) {
273
+ return false;
274
+ }
271
275
  const visitedPairs = visited.get(object1);
272
276
  if (visitedPairs && visitedPairs.has(object2)) {
273
277
  return true;
@@ -276,7 +280,9 @@ function deepEqual(object1, object2, visited = new WeakMap()) {
276
280
  visitedPairs.add(object2);
277
281
  }
278
282
  else {
279
- visited.set(object1, new WeakSet([object2]));
283
+ const ws = new WeakSet();
284
+ ws.add(object2);
285
+ visited.set(object1, ws);
280
286
  }
281
287
  for (const key of keys1) {
282
288
  const val1 = object1[key];
@@ -1132,12 +1138,22 @@ var getValidationModes = (mode) => ({
1132
1138
  });
1133
1139
 
1134
1140
  const ASYNC_FUNCTION = 'AsyncFunction';
1135
- var hasPromiseValidation = (fieldReference) => !!fieldReference &&
1136
- !!fieldReference.validate &&
1137
- !!((isFunction(fieldReference.validate) &&
1138
- fieldReference.validate.constructor.name === ASYNC_FUNCTION) ||
1139
- (isObject(fieldReference.validate) &&
1140
- Object.values(fieldReference.validate).find((validateFunction) => validateFunction.constructor.name === ASYNC_FUNCTION)));
1141
+ var hasPromiseValidation = (fieldReference) => {
1142
+ if (!fieldReference || !fieldReference.validate)
1143
+ return false;
1144
+ if (isFunction(fieldReference.validate)) {
1145
+ return fieldReference.validate.constructor.name === ASYNC_FUNCTION;
1146
+ }
1147
+ if (isObject(fieldReference.validate)) {
1148
+ for (const key in fieldReference.validate) {
1149
+ if (fieldReference.validate[key].constructor
1150
+ .name === ASYNC_FUNCTION) {
1151
+ return true;
1152
+ }
1153
+ }
1154
+ }
1155
+ return false;
1156
+ };
1141
1157
 
1142
1158
  var hasValidation = (options) => options.mount &&
1143
1159
  (options.required ||
@@ -1148,10 +1164,17 @@ var hasValidation = (options) => options.mount &&
1148
1164
  options.pattern ||
1149
1165
  options.validate);
1150
1166
 
1151
- var isWatched = (name, _names, isBlurEvent) => !isBlurEvent &&
1152
- (_names.watchAll ||
1153
- _names.watch.has(name) ||
1154
- [..._names.watch].some((watchName) => name.startsWith(`${watchName}.`)));
1167
+ var isWatched = (name, _names, isBlurEvent) => {
1168
+ if (isBlurEvent)
1169
+ return false;
1170
+ if (_names.watchAll || _names.watch.has(name))
1171
+ return true;
1172
+ for (const watchName of _names.watch) {
1173
+ if (name.startsWith(watchName) && name.charAt(watchName.length) === '.')
1174
+ return true;
1175
+ }
1176
+ return false;
1177
+ };
1155
1178
 
1156
1179
  const iterateFieldsByAction = (fields, action, fieldsNames, abortEarly) => {
1157
1180
  for (const key of fieldsNames || Object.keys(fields)) {
@@ -1219,10 +1242,10 @@ function schemaErrorLookup(errors, _fields, name) {
1219
1242
  var shouldRenderFormState = (formStateData, _proxyFormState, updateFormState, isRoot) => {
1220
1243
  updateFormState(formStateData);
1221
1244
  const { name, ...formState } = formStateData;
1222
- return (isEmptyObject(formState) ||
1223
- (isRoot &&
1224
- Object.keys(formState).length >= Object.keys(_proxyFormState).length) ||
1225
- Object.keys(formState).find((key) => _proxyFormState[key] ===
1245
+ const keys = Object.keys(formState);
1246
+ return (!keys.length ||
1247
+ (isRoot && keys.length >= Object.keys(_proxyFormState).length) ||
1248
+ keys.find((key) => _proxyFormState[key] ===
1226
1249
  (!isRoot || VALIDATION_MODE.all)));
1227
1250
  };
1228
1251
 
@@ -1470,6 +1493,7 @@ const defaultOptions = {
1470
1493
  reValidateMode: VALIDATION_MODE.onChange,
1471
1494
  shouldFocusError: true,
1472
1495
  };
1496
+ const FORM_ERROR_TYPE = 'form';
1473
1497
  const DEFAULT_FORM_STATE = {
1474
1498
  submitCount: 0,
1475
1499
  isDirty: false,
@@ -1517,6 +1541,9 @@ function createFormControl(props = {}) {
1517
1541
  };
1518
1542
  let delayErrorCallback;
1519
1543
  let timer = 0;
1544
+ let _valuesSubscriberCount = 0;
1545
+ let _validationModeBeforeSubmit = getValidationModes(_options.mode);
1546
+ let _validationModeAfterSubmit = getValidationModes(_options.reValidateMode);
1520
1547
  const defaultProxyFormState = {
1521
1548
  isDirty: false,
1522
1549
  dirtyFields: false,
@@ -1670,12 +1697,6 @@ function createFormControl(props = {}) {
1670
1697
  : setFieldValue(name, defaultValue);
1671
1698
  if (_state.mount && !_state.action) {
1672
1699
  _setValid();
1673
- // Re-registering a field after a prior unregister puts its key back
1674
- // into _formValues, which can flip isDirty back to false (#13397).
1675
- // Only run when we are currently dirty, otherwise an initial register
1676
- // for a field with no defaultValue would flip isDirty to true. Reset
1677
- // paths repopulate _formValues before re-register, so the key is
1678
- // present then and this branch is skipped (preserves keepDirty).
1679
1700
  if (wasUnsetInFormValues &&
1680
1701
  _formState.isDirty &&
1681
1702
  (_proxyFormState.isDirty || _proxySubscribeFormState.isDirty)) {
@@ -1685,9 +1706,6 @@ function createFormControl(props = {}) {
1685
1706
  _subjects.state.next({ ..._formState });
1686
1707
  }
1687
1708
  }
1688
- // When a watched field is re-registered after being unregistered and
1689
- // its value is restored, trigger a deferred watch broadcast so that
1690
- // components using watch() re-render with the new value.
1691
1709
  if (props.shouldUnregister &&
1692
1710
  wasUnsetInFormValues &&
1693
1711
  !isUndefined(get(_formValues, name)) &&
@@ -1705,12 +1723,13 @@ function createFormControl(props = {}) {
1705
1723
  };
1706
1724
  if (!_options.disabled) {
1707
1725
  if (!isBlurEvent || shouldDirty) {
1726
+ const isCurrentFieldPristine = deepEqual(get(_defaultValues, name), fieldValue);
1708
1727
  if (_proxyFormState.isDirty || _proxySubscribeFormState.isDirty) {
1709
1728
  isPreviousDirty = _formState.isDirty;
1710
- _formState.isDirty = output.isDirty = _getDirty();
1729
+ _formState.isDirty = output.isDirty =
1730
+ !isCurrentFieldPristine || _getDirty();
1711
1731
  shouldUpdateField = isPreviousDirty !== output.isDirty;
1712
1732
  }
1713
- const isCurrentFieldPristine = deepEqual(get(_defaultValues, name), fieldValue);
1714
1733
  isPreviousDirty = !!get(_formState.dirtyFields, name);
1715
1734
  if (isCurrentFieldPristine !== _formState.isDirty) {
1716
1735
  _formState.dirtyFields = getDirtyFields(_defaultValues, _formValues);
@@ -1909,7 +1928,7 @@ function createFormControl(props = {}) {
1909
1928
  };
1910
1929
  const _getDirty = (name, data) => !_options.disabled &&
1911
1930
  (name && data && set(_formValues, name, data),
1912
- !deepEqual(getValues(), _defaultValues));
1931
+ !deepEqual(_state.mount ? _formValues : _defaultValues, _defaultValues));
1913
1932
  const _getWatch = (names, defaultValue, isGlobal) => generateWatchOutput(names, _names, {
1914
1933
  ...(_state.mount
1915
1934
  ? _formValues
@@ -1920,7 +1939,7 @@ function createFormControl(props = {}) {
1920
1939
  : defaultValue),
1921
1940
  }, isGlobal, defaultValue);
1922
1941
  const _getFieldArray = (name) => compact(get(_state.mount ? _formValues : _defaultValues, name, _options.shouldUnregister ? get(_defaultValues, name, []) : []));
1923
- const setFieldValue = (name, value, options = {}, skipClone = false) => {
1942
+ const setFieldValue = (name, value, options = {}, skipClone = false, skipRender = false) => {
1924
1943
  const field = get(_fields, name);
1925
1944
  let fieldValue = value;
1926
1945
  if (field) {
@@ -1958,7 +1977,7 @@ function createFormControl(props = {}) {
1958
1977
  }
1959
1978
  else {
1960
1979
  fieldReference.ref.value = fieldValue;
1961
- if (!fieldReference.ref.type) {
1980
+ if (!fieldReference.ref.type && !skipRender) {
1962
1981
  _subjects.state.next({
1963
1982
  name,
1964
1983
  values: skipClone ? _formValues : cloneObject(_formValues),
@@ -1968,10 +1987,10 @@ function createFormControl(props = {}) {
1968
1987
  }
1969
1988
  }
1970
1989
  (options.shouldDirty || options.shouldTouch) &&
1971
- updateTouchAndDirty(name, fieldValue, options.shouldTouch, options.shouldDirty, true);
1990
+ updateTouchAndDirty(name, fieldValue, options.shouldTouch, options.shouldDirty, !skipRender);
1972
1991
  options.shouldValidate && trigger(name);
1973
1992
  };
1974
- const setFieldValues = (name, value, options, skipClone = false) => {
1993
+ const setFieldValues = (name, value, options, skipClone = false, skipRender = false) => {
1975
1994
  for (const fieldKey in value) {
1976
1995
  if (!value.hasOwnProperty(fieldKey)) {
1977
1996
  return;
@@ -1983,11 +2002,11 @@ function createFormControl(props = {}) {
1983
2002
  isObject(fieldValue) ||
1984
2003
  (field && !field._f)) &&
1985
2004
  !isDateObject(fieldValue)
1986
- ? setFieldValues(fieldName, fieldValue, options, skipClone)
1987
- : setFieldValue(fieldName, fieldValue, options, skipClone);
2005
+ ? setFieldValues(fieldName, fieldValue, options, skipClone, skipRender)
2006
+ : setFieldValue(fieldName, fieldValue, options, skipClone, skipRender);
1988
2007
  }
1989
2008
  };
1990
- const _setValue = (name, value, options, skipClone) => {
2009
+ const _setValue = (name, value, options, skipClone, skipStateEmit = false) => {
1991
2010
  const field = get(_fields, name);
1992
2011
  const isFieldArray = _names.array.has(name);
1993
2012
  const cloneValue = skipClone ? value : cloneObject(value);
@@ -2007,24 +2026,26 @@ function createFormControl(props = {}) {
2007
2026
  _proxySubscribeFormState.dirtyFields) &&
2008
2027
  options.shouldDirty) {
2009
2028
  _updateDirtyFields();
2010
- _subjects.state.next({
2011
- name,
2012
- dirtyFields: _formState.dirtyFields,
2013
- isDirty: _getDirty(name, cloneValue),
2014
- });
2029
+ if (!skipStateEmit) {
2030
+ _subjects.state.next({
2031
+ name,
2032
+ dirtyFields: _formState.dirtyFields,
2033
+ isDirty: _getDirty(name, cloneValue),
2034
+ });
2035
+ }
2015
2036
  }
2016
2037
  }
2017
2038
  else {
2018
2039
  const isEmpty = (Array.isArray(cloneValue) && !cloneValue.length) ||
2019
2040
  isEmptyObject(cloneValue);
2020
2041
  if (!field || field._f || isNullOrUndefined(cloneValue) || isEmpty) {
2021
- setFieldValue(name, cloneValue, options, skipClone);
2042
+ setFieldValue(name, cloneValue, options, skipClone, skipStateEmit);
2022
2043
  }
2023
2044
  else {
2024
- setFieldValues(name, cloneValue, options, skipClone);
2045
+ setFieldValues(name, cloneValue, options, skipClone, skipStateEmit);
2025
2046
  }
2026
2047
  }
2027
- if (!isValueUnchanged) {
2048
+ if (!isValueUnchanged && !skipStateEmit) {
2028
2049
  const watched = isWatched(name, _names);
2029
2050
  const values = skipClone ? _formValues : cloneObject(_formValues);
2030
2051
  _subjects.state.next({
@@ -2045,13 +2066,13 @@ function createFormControl(props = {}) {
2045
2066
  ...updatedFormValues,
2046
2067
  };
2047
2068
  for (const fieldName of _names.mount) {
2048
- _setValue(fieldName, get(updatedFormValues, fieldName), options, true);
2069
+ _setValue(fieldName, get(updatedFormValues, fieldName), options, true, true);
2049
2070
  }
2050
2071
  _subjects.state.next({
2051
2072
  ..._formState,
2052
2073
  name: undefined,
2053
2074
  type: undefined,
2054
- values: _formValues,
2075
+ ...(_valuesSubscriberCount ? { values: _formValues } : {}),
2055
2076
  });
2056
2077
  if (options.shouldValidate) {
2057
2078
  _setValid();
@@ -2070,8 +2091,6 @@ function createFormControl(props = {}) {
2070
2091
  (isDateObject(fieldValue) && isNaN(fieldValue.getTime())) ||
2071
2092
  deepEqual(fieldValue, get(_formValues, name, fieldValue));
2072
2093
  };
2073
- const validationModeBeforeSubmit = getValidationModes(_options.mode);
2074
- const validationModeAfterSubmit = getValidationModes(_options.reValidateMode);
2075
2094
  if (field) {
2076
2095
  let error;
2077
2096
  let isValid;
@@ -2079,12 +2098,13 @@ function createFormControl(props = {}) {
2079
2098
  ? getFieldValue(field._f)
2080
2099
  : getEventValue(event);
2081
2100
  const isBlurEvent = event.type === EVENTS.BLUR || event.type === EVENTS.FOCUS_OUT;
2082
- const shouldSkipValidation = (!hasValidation(field._f) &&
2101
+ const hasNoValidationEffect = !hasValidation(field._f) &&
2083
2102
  !props.validate &&
2084
2103
  !_options.resolver &&
2085
2104
  !get(_formState.errors, name) &&
2086
- !field._f.deps) ||
2087
- skipValidation(isBlurEvent, get(_formState.touchedFields, name), _formState.isSubmitted, validationModeAfterSubmit, validationModeBeforeSubmit);
2105
+ !field._f.deps;
2106
+ const shouldSkipValidation = hasNoValidationEffect ||
2107
+ skipValidation(isBlurEvent, get(_formState.touchedFields, name), _formState.isSubmitted, _validationModeAfterSubmit, _validationModeBeforeSubmit);
2088
2108
  const watched = isWatched(name, _names, isBlurEvent);
2089
2109
  set(_formValues, name, fieldValue);
2090
2110
  if (isBlurEvent) {
@@ -2102,10 +2122,13 @@ function createFormControl(props = {}) {
2102
2122
  _subjects.state.next({
2103
2123
  name,
2104
2124
  type: event.type,
2105
- values: cloneObject(_formValues),
2125
+ ...(_valuesSubscriberCount
2126
+ ? { values: cloneObject(_formValues) }
2127
+ : {}),
2106
2128
  });
2107
2129
  if (shouldSkipValidation) {
2108
- if (_proxyFormState.isValid || _proxySubscribeFormState.isValid) {
2130
+ if ((!hasNoValidationEffect || !_formState.isValid) &&
2131
+ (_proxyFormState.isValid || _proxySubscribeFormState.isValid)) {
2109
2132
  if (_options.mode === 'onBlur') {
2110
2133
  if (isBlurEvent) {
2111
2134
  _setValid();
@@ -2240,8 +2263,6 @@ function createFormControl(props = {}) {
2240
2263
  const names = name ? convertToArrayPayload(name) : undefined;
2241
2264
  names === null || names === void 0 ? void 0 : names.forEach((inputName) => unset(_formState.errors, inputName));
2242
2265
  if (names) {
2243
- // Emit for each cleared field with the field name so that
2244
- // shouldSubscribeByName can filter and avoid broad re-renders
2245
2266
  names.forEach((inputName) => {
2246
2267
  _subjects.state.next({
2247
2268
  name: inputName,
@@ -2250,7 +2271,6 @@ function createFormControl(props = {}) {
2250
2271
  });
2251
2272
  }
2252
2273
  else {
2253
- // Clear all errors - emit without name to notify all subscribers
2254
2274
  _subjects.state.next({
2255
2275
  errors: {},
2256
2276
  });
@@ -2259,7 +2279,6 @@ function createFormControl(props = {}) {
2259
2279
  const setError = (name, error, options) => {
2260
2280
  const ref = (get(_fields, name, { _f: {} })._f || {}).ref;
2261
2281
  const currentError = get(_formState.errors, name) || {};
2262
- // Don't override existing error messages elsewhere in the object tree.
2263
2282
  const { ref: currentRef, message, type, ...restOfErrorTree } = currentError;
2264
2283
  set(_formState.errors, name, {
2265
2284
  ...restOfErrorTree,
@@ -2273,26 +2292,60 @@ function createFormControl(props = {}) {
2273
2292
  });
2274
2293
  options && options.shouldFocus && ref && ref.focus && ref.focus();
2275
2294
  };
2276
- const watch = (name, defaultValue) => isFunction(name)
2277
- ? _subjects.state.subscribe({
2278
- next: (payload) => 'values' in payload &&
2279
- name(payload.values || _getWatch(undefined, defaultValue), payload),
2280
- })
2281
- : _getWatch(name, defaultValue, true);
2282
- const _subscribe = (props) => _subjects.state.subscribe({
2283
- next: (formState) => {
2284
- if (shouldSubscribeByName(props.name, formState.name, props.exact) &&
2285
- shouldRenderFormState(formState, props.formState || _proxyFormState, _setFormState, props.reRenderRoot)) {
2286
- const snapshot = { ..._formValues };
2287
- props.callback({
2288
- values: snapshot,
2289
- ..._formState,
2290
- ...formState,
2291
- defaultValues: _defaultValues,
2292
- });
2295
+ const watch = (name, defaultValue) => {
2296
+ if (isFunction(name)) {
2297
+ _valuesSubscriberCount++;
2298
+ const { unsubscribe } = _subjects.state.subscribe({
2299
+ next: (payload) => 'values' in payload &&
2300
+ name(payload.values || _getWatch(undefined, defaultValue), payload),
2301
+ });
2302
+ let called = false;
2303
+ return {
2304
+ unsubscribe: () => {
2305
+ if (called) {
2306
+ return;
2307
+ }
2308
+ called = true;
2309
+ _valuesSubscriberCount--;
2310
+ unsubscribe();
2311
+ },
2312
+ };
2313
+ }
2314
+ return _getWatch(name, defaultValue, true);
2315
+ };
2316
+ const _subscribe = (props) => {
2317
+ var _a;
2318
+ const needsValues = !!((_a = props.formState) === null || _a === void 0 ? void 0 : _a.values);
2319
+ if (needsValues) {
2320
+ _valuesSubscriberCount++;
2321
+ }
2322
+ const { unsubscribe } = _subjects.state.subscribe({
2323
+ next: (formState) => {
2324
+ if (shouldSubscribeByName(props.name, formState.name, props.exact) &&
2325
+ shouldRenderFormState(formState, props.formState || _proxyFormState, _setFormState, props.reRenderRoot)) {
2326
+ const snapshot = { ..._formValues };
2327
+ props.callback({
2328
+ values: snapshot,
2329
+ ..._formState,
2330
+ ...formState,
2331
+ defaultValues: _defaultValues,
2332
+ });
2333
+ }
2334
+ },
2335
+ });
2336
+ if (!needsValues) {
2337
+ return unsubscribe;
2338
+ }
2339
+ let called = false;
2340
+ return () => {
2341
+ if (called) {
2342
+ return;
2293
2343
  }
2294
- },
2295
- }).unsubscribe;
2344
+ called = true;
2345
+ _valuesSubscriberCount--;
2346
+ unsubscribe();
2347
+ };
2348
+ };
2296
2349
  const subscribe = (props) => {
2297
2350
  _state.mount = true;
2298
2351
  _proxySubscribeFormState = {
@@ -2626,9 +2679,6 @@ function createFormControl(props = {}) {
2626
2679
  _state.watch = !!_options.shouldUnregister;
2627
2680
  _state.keepIsValid = !!keepStateOptions.keepIsValid;
2628
2681
  _state.action = false;
2629
- // Clear errors synchronously to prevent validation errors on subsequent submissions
2630
- // This fixes the issue where form.reset() causes validation errors on subsequent
2631
- // submissions in Next.js 16 with Server Actions
2632
2682
  if (!keepStateOptions.keepErrors) {
2633
2683
  _formState.errors = {};
2634
2684
  }
@@ -2680,8 +2730,6 @@ function createFormControl(props = {}) {
2680
2730
  ? fieldReference.refs[0]
2681
2731
  : fieldReference.ref;
2682
2732
  if (fieldRef.focus) {
2683
- // Use setTimeout to ensure focus happens after any pending state updates
2684
- // This fixes the issue where setFocus doesn't work immediately after setError
2685
2733
  setTimeout(() => {
2686
2734
  fieldRef.focus();
2687
2735
  options.shouldSelect &&
@@ -2775,6 +2823,8 @@ function createFormControl(props = {}) {
2775
2823
  ..._options,
2776
2824
  ...value,
2777
2825
  };
2826
+ _validationModeBeforeSubmit = getValidationModes(_options.mode);
2827
+ _validationModeAfterSubmit = getValidationModes(_options.reValidateMode);
2778
2828
  },
2779
2829
  },
2780
2830
  subscribe,
@@ -2909,8 +2959,8 @@ var updateAt = (fieldValues, index, value) => {
2909
2959
  function useFieldArray(props) {
2910
2960
  const formControl = useFormControlContext();
2911
2961
  const { control = formControl, name, keyName = 'id', disabled, shouldUnregister, rules, } = props;
2912
- const [fields, setFields] = React.useState(disabled ? [] : control._getFieldArray(name));
2913
- const ids = React.useRef(disabled ? [] : control._getFieldArray(name).map(generateId));
2962
+ const [fields, setFields] = React.useState(control._getFieldArray(name));
2963
+ const ids = React.useRef(control._getFieldArray(name).map(generateId));
2914
2964
  const _actioned = React.useRef(false);
2915
2965
  if (!disabled) {
2916
2966
  control._names.array.add(name);
@@ -3164,8 +3214,9 @@ function useFieldArray(props) {
3164
3214
  ]),
3165
3215
  fields: React.useMemo(() => fields.map((field, index) => ({
3166
3216
  ...field,
3217
+ ...(isBoolean(disabled) ? { disabled } : {}),
3167
3218
  [keyName]: ids.current[index] || generateId(),
3168
- })), [fields, keyName]),
3219
+ })), [fields, keyName, disabled]),
3169
3220
  };
3170
3221
  }
3171
3222