react-hook-form 7.74.0 → 7.76.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.
@@ -18,9 +18,15 @@ var getEventValue = (event) => isObject(event) && event.target
18
18
  : event.target.value
19
19
  : event;
20
20
 
21
- var isNameInFieldArray = (names, name) => name
22
- .split('.')
23
- .some((part, index, arr) => !isNaN(Number(part)) && names.has(arr.slice(0, index).join('.')));
21
+ var getFieldArrayParentNames = (names, name) => {
22
+ const parts = name.split('.');
23
+ const matches = [];
24
+ let prefix = parts[0];
25
+ for (let i = 1; i < parts.length; prefix += '.' + parts[i++]) {
26
+ !isNaN(+parts[i]) && names.has(prefix) && matches.push(prefix);
27
+ }
28
+ return matches;
29
+ };
24
30
 
25
31
  var isPlainObject = (tempObject) => {
26
32
  const prototypeCopy = tempObject.constructor && tempObject.constructor.prototype;
@@ -143,9 +149,7 @@ HookFormControlContext.displayName = 'HookFormControlContext';
143
149
  const useFormControlContext = () => React.useContext(HookFormControlContext);
144
150
 
145
151
  var getProxyFormState = (formState, control, localProxyFormState, isRoot = true) => {
146
- const result = {
147
- defaultValues: control._defaultValues,
148
- };
152
+ const result = {};
149
153
  for (const key in formState) {
150
154
  Object.defineProperty(result, key, {
151
155
  get: () => {
@@ -196,7 +200,10 @@ const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? React.useLayou
196
200
  function useFormState(props) {
197
201
  const formControl = useFormControlContext();
198
202
  const { control = formControl, disabled, name, exact } = props || {};
199
- const [formState, updateFormState] = React.useState(control._formState);
203
+ const [formState, updateFormState] = React.useState(() => ({
204
+ ...control._formState,
205
+ defaultValues: control._defaultValues,
206
+ }));
200
207
  const _localProxyFormState = React.useRef({
201
208
  isDirty: false,
202
209
  isLoading: false,
@@ -216,6 +223,7 @@ function useFormState(props) {
216
223
  updateFormState({
217
224
  ...control._formState,
218
225
  ...formState,
226
+ defaultValues: control._defaultValues,
219
227
  });
220
228
  },
221
229
  }), [name, disabled, exact]);
@@ -394,7 +402,8 @@ function useWatch(props) {
394
402
  function useController(props) {
395
403
  const formControl = useFormControlContext();
396
404
  const { name, disabled, control = formControl, shouldUnregister, defaultValue, exact = true, } = props;
397
- const isArrayField = isNameInFieldArray(control._names.array, name);
405
+ const isArrayField = !!getFieldArrayParentNames(control._names.array, name)
406
+ .length;
398
407
  const defaultValueMemo = React.useMemo(() => get(control._formValues, name, get(control._defaultValues, name, defaultValue)), [control, name, defaultValue]);
399
408
  const value = useWatch({
400
409
  control,
@@ -487,7 +496,7 @@ function useController(props) {
487
496
  };
488
497
  updateMounted(name, true);
489
498
  if (_shouldUnregisterField) {
490
- const value = cloneObject(get(control._options.defaultValues, name, _props.current.defaultValue));
499
+ const value = cloneObject(get(control._defaultValues, name, get(control._options.defaultValues, name, _props.current.defaultValue)));
491
500
  set(control._defaultValues, name, value);
492
501
  if (isUndefined(get(control._formValues, name))) {
493
502
  set(control._formValues, name, value);
@@ -639,7 +648,7 @@ const useFormContext = () => React.useContext(HookFormContext);
639
648
  * ```
640
649
  */
641
650
  const FormProvider = (props) => {
642
- const { children, watch, getValues, getFieldState, setError, clearErrors, setValue, trigger, formState, resetField, reset, handleSubmit, unregister, control, register, setFocus, subscribe, } = props;
651
+ const { children, watch, getValues, getFieldState, setError, clearErrors, setValue, setValues, trigger, formState, resetField, reset, handleSubmit, unregister, control, register, setFocus, subscribe, } = props;
643
652
  const memoizedValue = React.useMemo(() => ({
644
653
  watch,
645
654
  getValues,
@@ -647,6 +656,7 @@ const FormProvider = (props) => {
647
656
  setError,
648
657
  clearErrors,
649
658
  setValue,
659
+ setValues,
650
660
  trigger,
651
661
  formState,
652
662
  resetField,
@@ -670,6 +680,7 @@ const FormProvider = (props) => {
670
680
  setError,
671
681
  setFocus,
672
682
  setValue,
683
+ setValues,
673
684
  subscribe,
674
685
  trigger,
675
686
  unregister,
@@ -943,6 +954,29 @@ function markFieldsDirty(data, fields = {}) {
943
954
  }
944
955
  return fields;
945
956
  }
957
+ function pruneDirtyFields(value) {
958
+ if (value === false) {
959
+ return undefined;
960
+ }
961
+ if (value === true) {
962
+ return true;
963
+ }
964
+ if (Array.isArray(value)) {
965
+ const result = value.map((value) => pruneDirtyFields(value));
966
+ return (result.some((value) => value !== undefined) ? result : undefined);
967
+ }
968
+ if (isObject(value)) {
969
+ const result = {};
970
+ for (const key in value) {
971
+ const pruned = pruneDirtyFields(value[key]);
972
+ if (!isUndefined(pruned)) {
973
+ result[key] = pruned;
974
+ }
975
+ }
976
+ return (Object.keys(result).length ? result : undefined);
977
+ }
978
+ return undefined;
979
+ }
946
980
  function getDirtyFields(data, formValues, dirtyFieldsFromValues) {
947
981
  if (!dirtyFieldsFromValues) {
948
982
  dirtyFieldsFromValues = markFieldsDirty(formValues);
@@ -962,7 +996,7 @@ function getDirtyFields(data, formValues, dirtyFieldsFromValues) {
962
996
  dirtyFieldsFromValues[key] = !deepEqual(value, formValue);
963
997
  }
964
998
  }
965
- return dirtyFieldsFromValues;
999
+ return pruneDirtyFields(dirtyFieldsFromValues) || {};
966
1000
  }
967
1001
 
968
1002
  const defaultResult = {
@@ -1034,8 +1068,6 @@ function getFieldValue(_f) {
1034
1068
  return getFieldValueAs(isUndefined(ref.value) ? _f.ref.value : ref.value, _f);
1035
1069
  }
1036
1070
 
1037
- var getNodeParentName = (name) => name.substring(0, name.search(/\.\d+(\.|$)/)) || name;
1038
-
1039
1071
  var getResolverOptions = (fieldsNames, _fields, criteriaMode, shouldUseNativeValidation) => {
1040
1072
  const fields = {};
1041
1073
  for (const name of fieldsNames) {
@@ -1160,7 +1192,8 @@ var shouldRenderFormState = (formStateData, _proxyFormState, updateFormState, is
1160
1192
  updateFormState(formStateData);
1161
1193
  const { name, ...formState } = formStateData;
1162
1194
  return (isEmptyObject(formState) ||
1163
- Object.keys(formState).length >= Object.keys(_proxyFormState).length ||
1195
+ (isRoot &&
1196
+ Object.keys(formState).length >= Object.keys(_proxyFormState).length) ||
1164
1197
  Object.keys(formState).find((key) => _proxyFormState[key] ===
1165
1198
  (!isRoot || VALIDATION_MODE.all)));
1166
1199
  };
@@ -1403,24 +1436,27 @@ const defaultOptions = {
1403
1436
  reValidateMode: VALIDATION_MODE.onChange,
1404
1437
  shouldFocusError: true,
1405
1438
  };
1439
+ const DEFAULT_FORM_STATE = {
1440
+ submitCount: 0,
1441
+ isDirty: false,
1442
+ isReady: false,
1443
+ isValidating: false,
1444
+ isSubmitted: false,
1445
+ isSubmitting: false,
1446
+ isSubmitSuccessful: false,
1447
+ isValid: false,
1448
+ touchedFields: {},
1449
+ dirtyFields: {},
1450
+ validatingFields: {},
1451
+ };
1406
1452
  function createFormControl(props = {}) {
1407
1453
  let _options = {
1408
1454
  ...defaultOptions,
1409
1455
  ...props,
1410
1456
  };
1411
1457
  let _formState = {
1412
- submitCount: 0,
1413
- isDirty: false,
1414
- isReady: false,
1458
+ ...cloneObject(DEFAULT_FORM_STATE),
1415
1459
  isLoading: isFunction(_options.defaultValues),
1416
- isValidating: false,
1417
- isSubmitted: false,
1418
- isSubmitting: false,
1419
- isSubmitSuccessful: false,
1420
- isValid: false,
1421
- touchedFields: {},
1422
- dirtyFields: {},
1423
- validatingFields: {},
1424
1460
  errors: _options.errors || {},
1425
1461
  disabled: _options.disabled || false,
1426
1462
  };
@@ -1517,10 +1553,8 @@ function createFormControl(props = {}) {
1517
1553
  });
1518
1554
  }
1519
1555
  };
1520
- const _updateDirtyFields = (name) => {
1521
- const fullDirtyFields = getDirtyFields(_defaultValues, _formValues);
1522
- const rootName = getNodeParentName(name);
1523
- set(_formState.dirtyFields, rootName, get(fullDirtyFields, rootName));
1556
+ const _updateDirtyFields = () => {
1557
+ _formState.dirtyFields = getDirtyFields(_defaultValues, _formValues);
1524
1558
  };
1525
1559
  const _setFieldArray = (name, values = [], method, args, shouldSetValues = true, shouldUpdateFieldsAndState = true) => {
1526
1560
  if (args && method && !_options.disabled) {
@@ -1543,7 +1577,7 @@ function createFormControl(props = {}) {
1543
1577
  shouldSetValues && set(_formState.touchedFields, name, touchedFields);
1544
1578
  }
1545
1579
  if (_proxyFormState.dirtyFields || _proxySubscribeFormState.dirtyFields) {
1546
- _updateDirtyFields(name);
1580
+ _updateDirtyFields();
1547
1581
  }
1548
1582
  _subjects.state.next({
1549
1583
  name,
@@ -1570,16 +1604,53 @@ function createFormControl(props = {}) {
1570
1604
  isValid: false,
1571
1605
  });
1572
1606
  };
1607
+ const hasExplicitNullIntermediate = (name) => {
1608
+ const segments = isKey(name) ? [name] : stringToPath(name);
1609
+ let formValues = _formValues;
1610
+ let defaultValues = _defaultValues;
1611
+ for (let i = 0; i < segments.length - 1; i++) {
1612
+ const key = segments[i];
1613
+ formValues = isNullOrUndefined(formValues) ? formValues : formValues[key];
1614
+ defaultValues = isNullOrUndefined(defaultValues)
1615
+ ? defaultValues
1616
+ : defaultValues[key];
1617
+ if (formValues === null && defaultValues !== null) {
1618
+ return true;
1619
+ }
1620
+ }
1621
+ return false;
1622
+ };
1573
1623
  const updateValidAndValue = (name, shouldSkipSetValueAs, value, ref) => {
1574
1624
  const field = get(_fields, name);
1575
1625
  if (field) {
1626
+ if (hasExplicitNullIntermediate(name)) {
1627
+ return;
1628
+ }
1629
+ const wasUnsetInFormValues = isUndefined(get(_formValues, name));
1576
1630
  const defaultValue = get(_formValues, name, isUndefined(value) ? get(_defaultValues, name) : value);
1577
1631
  isUndefined(defaultValue) ||
1578
1632
  (ref && ref.defaultChecked) ||
1579
1633
  shouldSkipSetValueAs
1580
1634
  ? set(_formValues, name, shouldSkipSetValueAs ? defaultValue : getFieldValue(field._f))
1581
1635
  : setFieldValue(name, defaultValue);
1582
- _state.mount && !_state.action && _setValid();
1636
+ if (_state.mount && !_state.action) {
1637
+ _setValid();
1638
+ // Re-registering a field after a prior unregister puts its key back
1639
+ // into _formValues, which can flip isDirty back to false (#13397).
1640
+ // Only run when we are currently dirty, otherwise an initial register
1641
+ // for a field with no defaultValue would flip isDirty to true. Reset
1642
+ // paths repopulate _formValues before re-register, so the key is
1643
+ // present then and this branch is skipped (preserves keepDirty).
1644
+ if (wasUnsetInFormValues &&
1645
+ _formState.isDirty &&
1646
+ (_proxyFormState.isDirty || _proxySubscribeFormState.isDirty)) {
1647
+ const isDirty = _getDirty();
1648
+ if (!isDirty) {
1649
+ _formState.isDirty = false;
1650
+ _subjects.state.next({ ..._formState });
1651
+ }
1652
+ }
1653
+ }
1583
1654
  }
1584
1655
  };
1585
1656
  const updateTouchAndDirty = (name, fieldValue, isBlurEvent, shouldDirty, shouldRender) => {
@@ -1597,9 +1668,14 @@ function createFormControl(props = {}) {
1597
1668
  }
1598
1669
  const isCurrentFieldPristine = deepEqual(get(_defaultValues, name), fieldValue);
1599
1670
  isPreviousDirty = !!get(_formState.dirtyFields, name);
1600
- isCurrentFieldPristine
1601
- ? unset(_formState.dirtyFields, name)
1602
- : set(_formState.dirtyFields, name, true);
1671
+ if (isCurrentFieldPristine !== _formState.isDirty) {
1672
+ _formState.dirtyFields = getDirtyFields(_defaultValues, _formValues);
1673
+ }
1674
+ else {
1675
+ isCurrentFieldPristine
1676
+ ? unset(_formState.dirtyFields, name)
1677
+ : set(_formState.dirtyFields, name, true);
1678
+ }
1603
1679
  output.dirtyFields = _formState.dirtyFields;
1604
1680
  shouldUpdateField =
1605
1681
  shouldUpdateField ||
@@ -1666,7 +1742,9 @@ function createFormControl(props = {}) {
1666
1742
  for (const name of names) {
1667
1743
  const error = get(errors, name);
1668
1744
  error
1669
- ? set(_formState.errors, name, error)
1745
+ ? _names.array.has(name) && isObject(error)
1746
+ ? updateFieldArrayRootError(_formState.errors, { [name]: error }, name)
1747
+ : set(_formState.errors, name, error)
1670
1748
  : unset(_formState.errors, name);
1671
1749
  }
1672
1750
  }
@@ -1688,8 +1766,8 @@ function createFormControl(props = {}) {
1688
1766
  const error = result[key];
1689
1767
  if (error) {
1690
1768
  setError(`${FORM_ERROR_TYPE}.${key}`, {
1691
- message: isString(result.message) ? result.message : '',
1692
- type: INPUT_VALIDATION_RULES.validate,
1769
+ message: isString(error.message) ? error.message : '',
1770
+ type: error.type || INPUT_VALIDATION_RULES.validate,
1693
1771
  });
1694
1772
  }
1695
1773
  }
@@ -1731,11 +1809,15 @@ function createFormControl(props = {}) {
1731
1809
  if (_f) {
1732
1810
  const isFieldArrayRoot = _names.array.has(_f.name);
1733
1811
  const isPromiseFunction = field._f && hasPromiseValidation(field._f);
1734
- if (isPromiseFunction && _proxyFormState.validatingFields) {
1812
+ const shouldTrackIsValidatingState = _proxyFormState.validatingFields ||
1813
+ _proxyFormState.isValidating ||
1814
+ _proxySubscribeFormState.validatingFields ||
1815
+ _proxySubscribeFormState.isValidating;
1816
+ if (isPromiseFunction && shouldTrackIsValidatingState) {
1735
1817
  _updateIsValidating([_f.name], true);
1736
1818
  }
1737
1819
  const fieldError = await validateField(field, _names.disabled, _formValues, shouldDisplayAllAssociatedErrors, _options.shouldUseNativeValidation && !onlyCheckValid, isFieldArrayRoot);
1738
- if (isPromiseFunction && _proxyFormState.validatingFields) {
1820
+ if (isPromiseFunction && shouldTrackIsValidatingState) {
1739
1821
  _updateIsValidating([_f.name]);
1740
1822
  }
1741
1823
  if (fieldError[_f.name]) {
@@ -1863,14 +1945,20 @@ function createFormControl(props = {}) {
1863
1945
  const cloneValue = cloneObject(value);
1864
1946
  const previousValue = get(_formValues, name);
1865
1947
  const isValueUnchanged = deepEqual(previousValue, cloneValue);
1866
- set(_formValues, name, cloneValue);
1948
+ if (!isValueUnchanged) {
1949
+ set(_formValues, name, cloneValue);
1950
+ }
1867
1951
  if (isFieldArray) {
1868
1952
  _subjects.array.next({
1869
1953
  name,
1870
1954
  values: cloneObject(_formValues),
1871
1955
  });
1872
- if (options.shouldDirty) {
1873
- _updateDirtyFields(name);
1956
+ if ((_proxyFormState.isDirty ||
1957
+ _proxyFormState.dirtyFields ||
1958
+ _proxySubscribeFormState.isDirty ||
1959
+ _proxySubscribeFormState.dirtyFields) &&
1960
+ options.shouldDirty) {
1961
+ _updateDirtyFields();
1874
1962
  _subjects.state.next({
1875
1963
  name,
1876
1964
  dirtyFields: _formState.dirtyFields,
@@ -1889,19 +1977,18 @@ function createFormControl(props = {}) {
1889
1977
  }
1890
1978
  }
1891
1979
  if (!isValueUnchanged) {
1892
- if (isWatched(name, _names)) {
1893
- _subjects.state.next({
1894
- ..._formState,
1895
- name,
1896
- values: cloneObject(_formValues),
1897
- });
1898
- }
1899
- else {
1900
- _subjects.state.next({
1901
- name: _state.mount ? name : undefined,
1902
- values: cloneObject(_formValues),
1903
- });
1980
+ const watched = isWatched(name, _names);
1981
+ const values = cloneObject(_formValues);
1982
+ if (!isFieldArray) {
1983
+ for (const arrayName of getFieldArrayParentNames(_names.array, name)) {
1984
+ _subjects.array.next({ name: arrayName, values });
1985
+ }
1904
1986
  }
1987
+ _subjects.state.next({
1988
+ ...(watched && _formState),
1989
+ name: _state.mount || watched ? name : undefined,
1990
+ values,
1991
+ });
1905
1992
  }
1906
1993
  };
1907
1994
  const setValues = (formValues) => {
@@ -1913,6 +2000,9 @@ function createFormControl(props = {}) {
1913
2000
  ..._formValues,
1914
2001
  ...updatedFormValues,
1915
2002
  };
2003
+ for (const fieldName of _names.mount) {
2004
+ setValue(fieldName, get(updatedFormValues, fieldName));
2005
+ }
1916
2006
  _subjects.state.next({ ..._formState, values: _formValues });
1917
2007
  }
1918
2008
  };
@@ -2283,13 +2373,15 @@ function createFormControl(props = {}) {
2283
2373
  field._f.mount = false;
2284
2374
  }
2285
2375
  (_options.shouldUnregister || options.shouldUnregister) &&
2286
- !(isNameInFieldArray(_names.array, name) && _state.action) &&
2376
+ !(getFieldArrayParentNames(_names.array, name).length &&
2377
+ _state.action) &&
2287
2378
  _names.unMount.add(name);
2288
2379
  }
2289
2380
  },
2290
2381
  };
2291
2382
  };
2292
2383
  const _focusError = () => _options.shouldFocusError &&
2384
+ !_options.shouldUseNativeValidation &&
2293
2385
  iterateFieldsByAction(_fields, _focusInput, _names.mount);
2294
2386
  const _disableForm = (disabled) => {
2295
2387
  if (isBoolean(disabled)) {
@@ -2910,15 +3002,22 @@ function useFieldArray(props) {
2910
3002
  React.useEffect(() => {
2911
3003
  !get(control._formValues, name) && control._setFieldArray(name);
2912
3004
  return () => {
3005
+ const shouldKeepFieldArrayValues = !(control._options.shouldUnregister || shouldUnregister);
2913
3006
  const updateMounted = (name, value) => {
2914
3007
  const field = get(control._fields, name);
2915
3008
  if (field && field._f) {
2916
3009
  field._f.mount = value;
2917
3010
  }
2918
3011
  };
2919
- control._options.shouldUnregister || shouldUnregister
2920
- ? control.unregister(name)
2921
- : updateMounted(name, false);
3012
+ if (_actioned.current && shouldKeepFieldArrayValues) {
3013
+ control._subjects.state.next({
3014
+ name,
3015
+ values: cloneObject(control._formValues),
3016
+ });
3017
+ }
3018
+ shouldKeepFieldArrayValues
3019
+ ? updateMounted(name, false)
3020
+ : control.unregister(name);
2922
3021
  };
2923
3022
  }, [name, control, keyName, shouldUnregister]);
2924
3023
  return {
@@ -2969,25 +3068,15 @@ function useFieldArray(props) {
2969
3068
  function useForm(props = {}) {
2970
3069
  const _formControl = React.useRef(undefined);
2971
3070
  const _values = React.useRef(undefined);
2972
- const [formState, updateFormState] = React.useState({
2973
- isDirty: false,
2974
- isValidating: false,
3071
+ const [formState, updateFormState] = React.useState(() => ({
3072
+ ...cloneObject(DEFAULT_FORM_STATE),
2975
3073
  isLoading: isFunction(props.defaultValues),
2976
- isSubmitted: false,
2977
- isSubmitting: false,
2978
- isSubmitSuccessful: false,
2979
- isValid: false,
2980
- submitCount: 0,
2981
- dirtyFields: {},
2982
- touchedFields: {},
2983
- validatingFields: {},
2984
3074
  errors: props.errors || {},
2985
3075
  disabled: props.disabled || false,
2986
- isReady: false,
2987
3076
  defaultValues: isFunction(props.defaultValues)
2988
3077
  ? undefined
2989
3078
  : props.defaultValues,
2990
- });
3079
+ }));
2991
3080
  if (!_formControl.current) {
2992
3081
  if (props.formControl) {
2993
3082
  _formControl.current = {
@@ -3011,7 +3100,10 @@ function useForm(props = {}) {
3011
3100
  useIsomorphicLayoutEffect(() => {
3012
3101
  const sub = control._subscribe({
3013
3102
  formState: control._proxyFormState,
3014
- callback: () => updateFormState({ ...control._formState }),
3103
+ callback: () => updateFormState({
3104
+ ...control._formState,
3105
+ defaultValues: control._defaultValues,
3106
+ }),
3015
3107
  reRenderRoot: true,
3016
3108
  });
3017
3109
  updateFormState((data) => ({