react-hook-form 7.65.0 → 7.66.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.
@@ -161,7 +161,7 @@ HookFormContext.displayName = 'HookFormContext';
161
161
  */
162
162
  const useFormContext = () => React.useContext(HookFormContext);
163
163
  /**
164
- * A provider component that propagates the `useForm` methods to all children components via [React Context](https://reactjs.org/docs/context.html) API. To be used with {@link useFormContext}.
164
+ * 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}.
165
165
  *
166
166
  * @remarks
167
167
  * [API](https://react-hook-form.com/docs/useformcontext) • [Demo](https://codesandbox.io/s/react-hook-form-v7-form-context-ytudi)
@@ -353,33 +353,68 @@ function useWatch(props) {
353
353
  const _defaultValue = React.useRef(defaultValue);
354
354
  const _compute = React.useRef(compute);
355
355
  const _computeFormValues = React.useRef(undefined);
356
+ const _prevControl = React.useRef(control);
357
+ const _prevName = React.useRef(name);
356
358
  _compute.current = compute;
357
- const defaultValueMemo = React.useMemo(() => control._getWatch(name, _defaultValue.current), [control, name]);
358
- const [value, updateValue] = React.useState(_compute.current ? _compute.current(defaultValueMemo) : defaultValueMemo);
359
- useIsomorphicLayoutEffect(() => control._subscribe({
360
- name,
361
- formState: {
362
- values: true,
363
- },
364
- exact,
365
- callback: (formState) => {
366
- if (!disabled) {
367
- const formValues = generateWatchOutput(name, control._names, formState.values || control._formValues, false, _defaultValue.current);
368
- if (_compute.current) {
369
- const computedFormValues = _compute.current(formValues);
370
- if (!deepEqual(computedFormValues, _computeFormValues.current)) {
371
- updateValue(computedFormValues);
372
- _computeFormValues.current = computedFormValues;
373
- }
374
- }
375
- else {
376
- updateValue(formValues);
359
+ const [value, updateValue] = React.useState(() => {
360
+ const defaultValue = control._getWatch(name, _defaultValue.current);
361
+ return _compute.current ? _compute.current(defaultValue) : defaultValue;
362
+ });
363
+ const getCurrentOutput = React.useCallback((values) => {
364
+ const formValues = generateWatchOutput(name, control._names, values || control._formValues, false, _defaultValue.current);
365
+ return _compute.current ? _compute.current(formValues) : formValues;
366
+ }, [control._formValues, control._names, name]);
367
+ const refreshValue = React.useCallback((values) => {
368
+ if (!disabled) {
369
+ const formValues = generateWatchOutput(name, control._names, values || control._formValues, false, _defaultValue.current);
370
+ if (_compute.current) {
371
+ const computedFormValues = _compute.current(formValues);
372
+ if (!deepEqual(computedFormValues, _computeFormValues.current)) {
373
+ updateValue(computedFormValues);
374
+ _computeFormValues.current = computedFormValues;
377
375
  }
378
376
  }
379
- },
380
- }), [control, disabled, name, exact]);
377
+ else {
378
+ updateValue(formValues);
379
+ }
380
+ }
381
+ }, [control._formValues, control._names, disabled, name]);
382
+ useIsomorphicLayoutEffect(() => {
383
+ if (_prevControl.current !== control ||
384
+ !deepEqual(_prevName.current, name)) {
385
+ _prevControl.current = control;
386
+ _prevName.current = name;
387
+ refreshValue();
388
+ }
389
+ return control._subscribe({
390
+ name,
391
+ formState: {
392
+ values: true,
393
+ },
394
+ exact,
395
+ callback: (formState) => {
396
+ refreshValue(formState.values);
397
+ },
398
+ });
399
+ }, [control, exact, name, refreshValue]);
381
400
  React.useEffect(() => control._removeUnmounted());
382
- return value;
401
+ // If name or control changed for this render, synchronously reflect the
402
+ // latest value so callers (like useController) see the correct value
403
+ // immediately on the same render.
404
+ // Optimize: Check control reference first before expensive deepEqual
405
+ const controlChanged = _prevControl.current !== control;
406
+ const prevName = _prevName.current;
407
+ // Cache the computed output to avoid duplicate calls within the same render
408
+ // We include shouldReturnImmediate in deps to ensure proper recomputation
409
+ const computedOutput = React.useMemo(() => {
410
+ if (disabled) {
411
+ return null;
412
+ }
413
+ const nameChanged = !controlChanged && !deepEqual(prevName, name);
414
+ const shouldReturnImmediate = controlChanged || nameChanged;
415
+ return shouldReturnImmediate ? getCurrentOutput() : null;
416
+ }, [disabled, controlChanged, name, prevName, getCurrentOutput]);
417
+ return computedOutput !== null ? computedOutput : value;
383
418
  }
384
419
 
385
420
  /**
@@ -2203,7 +2238,8 @@ function createFormControl(props = {}) {
2203
2238
  _state.mount =
2204
2239
  !_proxyFormState.isValid ||
2205
2240
  !!keepStateOptions.keepIsValid ||
2206
- !!keepStateOptions.keepDirtyValues;
2241
+ !!keepStateOptions.keepDirtyValues ||
2242
+ (!_options.shouldUnregister && !isEmptyObject(values));
2207
2243
  _state.watch = !!_options.shouldUnregister;
2208
2244
  _subjects.state.next({
2209
2245
  submitCount: keepStateOptions.keepSubmitCount