react-hook-form 7.74.0 → 7.75.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.
@@ -143,9 +143,7 @@ HookFormControlContext.displayName = 'HookFormControlContext';
143
143
  const useFormControlContext = () => React.useContext(HookFormControlContext);
144
144
 
145
145
  var getProxyFormState = (formState, control, localProxyFormState, isRoot = true) => {
146
- const result = {
147
- defaultValues: control._defaultValues,
148
- };
146
+ const result = {};
149
147
  for (const key in formState) {
150
148
  Object.defineProperty(result, key, {
151
149
  get: () => {
@@ -196,7 +194,10 @@ const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? React.useLayou
196
194
  function useFormState(props) {
197
195
  const formControl = useFormControlContext();
198
196
  const { control = formControl, disabled, name, exact } = props || {};
199
- const [formState, updateFormState] = React.useState(control._formState);
197
+ const [formState, updateFormState] = React.useState(() => ({
198
+ ...control._formState,
199
+ defaultValues: control._defaultValues,
200
+ }));
200
201
  const _localProxyFormState = React.useRef({
201
202
  isDirty: false,
202
203
  isLoading: false,
@@ -216,6 +217,7 @@ function useFormState(props) {
216
217
  updateFormState({
217
218
  ...control._formState,
218
219
  ...formState,
220
+ defaultValues: control._defaultValues,
219
221
  });
220
222
  },
221
223
  }), [name, disabled, exact]);
@@ -639,7 +641,7 @@ const useFormContext = () => React.useContext(HookFormContext);
639
641
  * ```
640
642
  */
641
643
  const FormProvider = (props) => {
642
- const { children, watch, getValues, getFieldState, setError, clearErrors, setValue, trigger, formState, resetField, reset, handleSubmit, unregister, control, register, setFocus, subscribe, } = props;
644
+ const { children, watch, getValues, getFieldState, setError, clearErrors, setValue, setValues, trigger, formState, resetField, reset, handleSubmit, unregister, control, register, setFocus, subscribe, } = props;
643
645
  const memoizedValue = React.useMemo(() => ({
644
646
  watch,
645
647
  getValues,
@@ -647,6 +649,7 @@ const FormProvider = (props) => {
647
649
  setError,
648
650
  clearErrors,
649
651
  setValue,
652
+ setValues,
650
653
  trigger,
651
654
  formState,
652
655
  resetField,
@@ -670,6 +673,7 @@ const FormProvider = (props) => {
670
673
  setError,
671
674
  setFocus,
672
675
  setValue,
676
+ setValues,
673
677
  subscribe,
674
678
  trigger,
675
679
  unregister,
@@ -943,6 +947,29 @@ function markFieldsDirty(data, fields = {}) {
943
947
  }
944
948
  return fields;
945
949
  }
950
+ function pruneDirtyFields(value) {
951
+ if (value === false) {
952
+ return undefined;
953
+ }
954
+ if (value === true) {
955
+ return true;
956
+ }
957
+ if (Array.isArray(value)) {
958
+ const result = value.map((value) => pruneDirtyFields(value));
959
+ return (result.some((value) => value !== undefined) ? result : undefined);
960
+ }
961
+ if (isObject(value)) {
962
+ const result = {};
963
+ for (const key in value) {
964
+ const pruned = pruneDirtyFields(value[key]);
965
+ if (!isUndefined(pruned)) {
966
+ result[key] = pruned;
967
+ }
968
+ }
969
+ return (Object.keys(result).length ? result : undefined);
970
+ }
971
+ return undefined;
972
+ }
946
973
  function getDirtyFields(data, formValues, dirtyFieldsFromValues) {
947
974
  if (!dirtyFieldsFromValues) {
948
975
  dirtyFieldsFromValues = markFieldsDirty(formValues);
@@ -962,7 +989,7 @@ function getDirtyFields(data, formValues, dirtyFieldsFromValues) {
962
989
  dirtyFieldsFromValues[key] = !deepEqual(value, formValue);
963
990
  }
964
991
  }
965
- return dirtyFieldsFromValues;
992
+ return pruneDirtyFields(dirtyFieldsFromValues) || {};
966
993
  }
967
994
 
968
995
  const defaultResult = {
@@ -1034,8 +1061,6 @@ function getFieldValue(_f) {
1034
1061
  return getFieldValueAs(isUndefined(ref.value) ? _f.ref.value : ref.value, _f);
1035
1062
  }
1036
1063
 
1037
- var getNodeParentName = (name) => name.substring(0, name.search(/\.\d+(\.|$)/)) || name;
1038
-
1039
1064
  var getResolverOptions = (fieldsNames, _fields, criteriaMode, shouldUseNativeValidation) => {
1040
1065
  const fields = {};
1041
1066
  for (const name of fieldsNames) {
@@ -1160,7 +1185,8 @@ var shouldRenderFormState = (formStateData, _proxyFormState, updateFormState, is
1160
1185
  updateFormState(formStateData);
1161
1186
  const { name, ...formState } = formStateData;
1162
1187
  return (isEmptyObject(formState) ||
1163
- Object.keys(formState).length >= Object.keys(_proxyFormState).length ||
1188
+ (isRoot &&
1189
+ Object.keys(formState).length >= Object.keys(_proxyFormState).length) ||
1164
1190
  Object.keys(formState).find((key) => _proxyFormState[key] ===
1165
1191
  (!isRoot || VALIDATION_MODE.all)));
1166
1192
  };
@@ -1403,24 +1429,27 @@ const defaultOptions = {
1403
1429
  reValidateMode: VALIDATION_MODE.onChange,
1404
1430
  shouldFocusError: true,
1405
1431
  };
1432
+ const DEFAULT_FORM_STATE = {
1433
+ submitCount: 0,
1434
+ isDirty: false,
1435
+ isReady: false,
1436
+ isValidating: false,
1437
+ isSubmitted: false,
1438
+ isSubmitting: false,
1439
+ isSubmitSuccessful: false,
1440
+ isValid: false,
1441
+ touchedFields: {},
1442
+ dirtyFields: {},
1443
+ validatingFields: {},
1444
+ };
1406
1445
  function createFormControl(props = {}) {
1407
1446
  let _options = {
1408
1447
  ...defaultOptions,
1409
1448
  ...props,
1410
1449
  };
1411
1450
  let _formState = {
1412
- submitCount: 0,
1413
- isDirty: false,
1414
- isReady: false,
1451
+ ...cloneObject(DEFAULT_FORM_STATE),
1415
1452
  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
1453
  errors: _options.errors || {},
1425
1454
  disabled: _options.disabled || false,
1426
1455
  };
@@ -1517,10 +1546,8 @@ function createFormControl(props = {}) {
1517
1546
  });
1518
1547
  }
1519
1548
  };
1520
- const _updateDirtyFields = (name) => {
1521
- const fullDirtyFields = getDirtyFields(_defaultValues, _formValues);
1522
- const rootName = getNodeParentName(name);
1523
- set(_formState.dirtyFields, rootName, get(fullDirtyFields, rootName));
1549
+ const _updateDirtyFields = () => {
1550
+ _formState.dirtyFields = getDirtyFields(_defaultValues, _formValues);
1524
1551
  };
1525
1552
  const _setFieldArray = (name, values = [], method, args, shouldSetValues = true, shouldUpdateFieldsAndState = true) => {
1526
1553
  if (args && method && !_options.disabled) {
@@ -1543,7 +1570,7 @@ function createFormControl(props = {}) {
1543
1570
  shouldSetValues && set(_formState.touchedFields, name, touchedFields);
1544
1571
  }
1545
1572
  if (_proxyFormState.dirtyFields || _proxySubscribeFormState.dirtyFields) {
1546
- _updateDirtyFields(name);
1573
+ _updateDirtyFields();
1547
1574
  }
1548
1575
  _subjects.state.next({
1549
1576
  name,
@@ -1573,13 +1600,31 @@ function createFormControl(props = {}) {
1573
1600
  const updateValidAndValue = (name, shouldSkipSetValueAs, value, ref) => {
1574
1601
  const field = get(_fields, name);
1575
1602
  if (field) {
1603
+ const wasUnsetInFormValues = isUndefined(get(_formValues, name));
1576
1604
  const defaultValue = get(_formValues, name, isUndefined(value) ? get(_defaultValues, name) : value);
1577
1605
  isUndefined(defaultValue) ||
1578
1606
  (ref && ref.defaultChecked) ||
1579
1607
  shouldSkipSetValueAs
1580
1608
  ? set(_formValues, name, shouldSkipSetValueAs ? defaultValue : getFieldValue(field._f))
1581
1609
  : setFieldValue(name, defaultValue);
1582
- _state.mount && !_state.action && _setValid();
1610
+ if (_state.mount && !_state.action) {
1611
+ _setValid();
1612
+ // Re-registering a field after a prior unregister puts its key back
1613
+ // into _formValues, which can flip isDirty back to false (#13397).
1614
+ // Only run when we are currently dirty, otherwise an initial register
1615
+ // for a field with no defaultValue would flip isDirty to true. Reset
1616
+ // paths repopulate _formValues before re-register, so the key is
1617
+ // present then and this branch is skipped (preserves keepDirty).
1618
+ if (wasUnsetInFormValues &&
1619
+ _formState.isDirty &&
1620
+ (_proxyFormState.isDirty || _proxySubscribeFormState.isDirty)) {
1621
+ const isDirty = _getDirty();
1622
+ if (!isDirty) {
1623
+ _formState.isDirty = false;
1624
+ _subjects.state.next({ ..._formState });
1625
+ }
1626
+ }
1627
+ }
1583
1628
  }
1584
1629
  };
1585
1630
  const updateTouchAndDirty = (name, fieldValue, isBlurEvent, shouldDirty, shouldRender) => {
@@ -1869,8 +1914,12 @@ function createFormControl(props = {}) {
1869
1914
  name,
1870
1915
  values: cloneObject(_formValues),
1871
1916
  });
1872
- if (options.shouldDirty) {
1873
- _updateDirtyFields(name);
1917
+ if ((_proxyFormState.isDirty ||
1918
+ _proxyFormState.dirtyFields ||
1919
+ _proxySubscribeFormState.isDirty ||
1920
+ _proxySubscribeFormState.dirtyFields) &&
1921
+ options.shouldDirty) {
1922
+ _updateDirtyFields();
1874
1923
  _subjects.state.next({
1875
1924
  name,
1876
1925
  dirtyFields: _formState.dirtyFields,
@@ -1889,19 +1938,12 @@ function createFormControl(props = {}) {
1889
1938
  }
1890
1939
  }
1891
1940
  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
- });
1904
- }
1941
+ const watched = isWatched(name, _names);
1942
+ _subjects.state.next({
1943
+ ...(watched && _formState),
1944
+ name: _state.mount || watched ? name : undefined,
1945
+ values: cloneObject(_formValues),
1946
+ });
1905
1947
  }
1906
1948
  };
1907
1949
  const setValues = (formValues) => {
@@ -2910,15 +2952,22 @@ function useFieldArray(props) {
2910
2952
  React.useEffect(() => {
2911
2953
  !get(control._formValues, name) && control._setFieldArray(name);
2912
2954
  return () => {
2955
+ const shouldKeepFieldArrayValues = !(control._options.shouldUnregister || shouldUnregister);
2913
2956
  const updateMounted = (name, value) => {
2914
2957
  const field = get(control._fields, name);
2915
2958
  if (field && field._f) {
2916
2959
  field._f.mount = value;
2917
2960
  }
2918
2961
  };
2919
- control._options.shouldUnregister || shouldUnregister
2920
- ? control.unregister(name)
2921
- : updateMounted(name, false);
2962
+ if (_actioned.current && shouldKeepFieldArrayValues) {
2963
+ control._subjects.state.next({
2964
+ name,
2965
+ values: cloneObject(control._formValues),
2966
+ });
2967
+ }
2968
+ shouldKeepFieldArrayValues
2969
+ ? updateMounted(name, false)
2970
+ : control.unregister(name);
2922
2971
  };
2923
2972
  }, [name, control, keyName, shouldUnregister]);
2924
2973
  return {
@@ -2969,25 +3018,15 @@ function useFieldArray(props) {
2969
3018
  function useForm(props = {}) {
2970
3019
  const _formControl = React.useRef(undefined);
2971
3020
  const _values = React.useRef(undefined);
2972
- const [formState, updateFormState] = React.useState({
2973
- isDirty: false,
2974
- isValidating: false,
3021
+ const [formState, updateFormState] = React.useState(() => ({
3022
+ ...cloneObject(DEFAULT_FORM_STATE),
2975
3023
  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
3024
  errors: props.errors || {},
2985
3025
  disabled: props.disabled || false,
2986
- isReady: false,
2987
3026
  defaultValues: isFunction(props.defaultValues)
2988
3027
  ? undefined
2989
3028
  : props.defaultValues,
2990
- });
3029
+ }));
2991
3030
  if (!_formControl.current) {
2992
3031
  if (props.formControl) {
2993
3032
  _formControl.current = {