Repository: react-hook-form/react-hook-form (2025-07-24T12:15:16Z)

Issue:
issue: `watch()` returns value | undefined even with full `defaultValues` in v7.61.0 (regression from v7.60.0)
### Version Number

7.61.0

### Steps to reproduce

1. Use `react-hook-form@7.61.0` with Zod resolver
2. Define a schema where all fields are required (non-optional)
3. Pass full `defaultValues` when initializing `useForm`
4. Use `watch("fieldName")`
5. Observe the type is inferred as type value | undefined, despite schema and defaults enforcing it to always be the type of the value for "fieldName"

### Expected behaviour

The type of `watch("fieldName")` should be directly the `"fieldName" value` and not `"fieldName" value | undefined` when:
- The field is required in the schema.
- `defaultValues` includes that field.

This behavior worked as expected in 7.60.0 but not in 7.61.0.

NOTE: `getValues` works as expected though.

### What browsers are you seeing the problem on?

Everything works as expected on the browsers, the error is a type error.

### Relevant log output

```shell
Object is possibly 'undefined'.ts(2532)
```

### Code of Conduct

- [x] I agree to follow this project's Code of Conduct


Discussion:
A little digging and more information

#### Previous Version (7.60.0)

```ts
<TFieldName extends FieldPath<TFieldValues>>(name: TFieldName, defaultValue?: FieldPathValue<TFieldValues, TFieldName>): FieldPathValue<TFieldValues, TFieldName>;
```

- Even without the second `defaultValue` argument, if the field was defined in `defaultValues`, the returned value was strictly typed (e.g., `string`, `number`, etc.).
- The `undefined` was **not** included in the return type when the field was required.
- If the field was optional then `watch` returned the type value or undefined.

#### Current Version (7.61.0)

```ts
<TFieldName extends FieldPath<TFieldValues>>(name: TFieldName): FieldPathValue<TFieldValues, TFieldName> | undefined;
```

- Now the return type **always includes `undefined`**, even when:
  - The field is present in `defaultValues`
  - The schema marks the field as required

Yes, passing a default explicitly like:

```ts
const name = watch("name", "default value");
```

does restore the strict type. But that shouldn’t be necessary if:

- `defaultValues` are already provided at form initialization
- The field is defined and required

With the current behavior, `watch()` forces to add manual `!` assertions, perform undefined checks, or explicitly pass a `defaultValue` to avoid `undefined` in the return type. This was simpler and more ergonomic in previous versions, where `watch()` would infer the correct type based on the schema required/optional properties.

Perhaps this change aligns with the newer `useWatch` behavior and added flexibility, but it introduces friction for cases where strict typing is desired and all defaults are already defined. The previous behavior of `watch()` was more straightforward in such scenarios.
Well, that is an intentional change from https://github.com/react-hook-form/react-hook-form/issues/12705
**However, I find it confusing.**

Let's get an example from the original issue:

```typescript
import { useForm } from "react-hook-form";

type MyType = {
  aString: string;
};

export default function App() {
  const { watch } = useForm<MyType>({
    defaultValues: {
      aString: ''
    }
  });

  // Hover over aStringValue and see that it is showing type `string | undefined`
  // instead of type `string`
  const aStringValue = watch("aString");

  return (
    <div className="App">
      Here is aStringValue.toUpperCase(): {aStringValue.toUpperCase()}
    </div>
  );
}
```
 

Moreover, I would even suggest changing the behaviour and do:

```typescript
import { useForm } from "react-hook-form";

type MyType = {
  aString: string;
};

// Should throw a type error for `defaultValues` unless `aString?: string`;
useForm<MyType>();
```



Patch:
diff --git a/reports/api-extractor.md.api.md b/reports/api-extractor.md.api.md
index 49ba4ac8e50..e17681e68ef 100644
--- a/reports/api-extractor.md.api.md
+++ b/reports/api-extractor.md.api.md
@@ -816,9 +816,7 @@ export type UseFormUnregister<TFieldValues extends FieldValues> = (name?: FieldP
 export type UseFormWatch<TFieldValues extends FieldValues> = {
     (): TFieldValues;
     <TFieldNames extends readonly FieldPath<TFieldValues>[]>(names: readonly [...TFieldNames], defaultValue?: DeepPartial<TFieldValues>): FieldPathValues<TFieldValues, TFieldNames>;
-    <TFieldName extends FieldPath<TFieldValues>>(name: TFieldName): FieldPathValue<TFieldValues, TFieldName> | undefined;
-    <TFieldName extends FieldPath<TFieldValues>>(name: TFieldName, defaultValue: FieldPathValue<TFieldValues, TFieldName>): FieldPathValue<TFieldValues, TFieldName>;
-    <TFieldName extends FieldPath<TFieldValues>>(name: TFieldName, defaultValue?: FieldPathValue<TFieldValues, TFieldName>): FieldPathValue<TFieldValues, TFieldName> | undefined;
+    <TFieldName extends FieldPath<TFieldValues>>(name: TFieldName, defaultValue?: FieldPathValue<TFieldValues, TFieldName>): FieldPathValue<TFieldValues, TFieldName>;
     (callback: WatchObserver<TFieldValues>, defaultValues?: DeepPartial<TFieldValues>): Subscription;
 };
 
@@ -939,7 +937,7 @@ export type WatchObserver<TFieldValues extends FieldValues> = (value: DeepPartia
 
 // Warnings were encountered during analysis:
 //
-// src/types/form.ts:523:3 - (ae-forgotten-export) The symbol "Subscription" needs to be exported by the entry point index.d.ts
+// src/types/form.ts:486:3 - (ae-forgotten-export) The symbol "Subscription" needs to be exported by the entry point index.d.ts
 
 // (No @packageDocumentation comment for this package)
 
diff --git a/src/types/form.ts b/src/types/form.ts
index 8ee92efca8d..df1aac2b58d 100644
--- a/src/types/form.ts
+++ b/src/types/form.ts
@@ -449,56 +449,19 @@ export type UseFormWatch<TFieldValues extends FieldValues> = {
    * [API](https://react-hook-form.com/docs/useform/watch) • [Demo](https://codesandbox.io/s/react-hook-form-watch-v7-ts-8et1d) • [Video](https://www.youtube.com/watch?v=3qLd69WMqKk)
    *
    * @param name - the path name to the form field value.
-   * @returns return the single field value
-   *
-   * @example
-   * ```tsx
-   * const name = watch("name");
-   * ```
-   */
-  <TFieldName extends FieldPath<TFieldValues>>(
-    name: TFieldName,
-  ): FieldPathValue<TFieldValues, TFieldName> | undefined;
-  /**
-   * Watch and subscribe to a single field used outside of render.
-   *
-   * @remarks
-   * [API](https://react-hook-form.com/docs/useform/watch) • [Demo](https://codesandbox.io/s/react-hook-form-watch-v7-ts-8et1d) • [Video](https://www.youtube.com/watch?v=3qLd69WMqKk)
-   *
-   * @param name - the path name to the form field value.
-   * @param defaultValue - defaultValue for the form field
-   *
-   * @returns return the single field value
-   *
-   * @example
-   * ```tsx
-   * const name = watch("name", "default value");
-   * ```
-   */
-  <TFieldName extends FieldPath<TFieldValues>>(
-    name: TFieldName,
-    defaultValue: FieldPathValue<TFieldValues, TFieldName>,
-  ): FieldPathValue<TFieldValues, TFieldName>;
-  /**
-   * Watch and subscribe to a single field used outside of render.
-   *
-   * @remarks
-   * [API](https://react-hook-form.com/docs/useform/watch) • [Demo](https://codesandbox.io/s/react-hook-form-watch-v7-ts-8et1d) • [Video](https://www.youtube.com/watch?v=3qLd69WMqKk)
-   *
-   * @param name - the path name to the form field value.
-   * @param defaultValue - defaultValue for the form field
+   * @param defaultValue - defaultValues for the entire form
    *
    * @returns return the single field value
    *
    * @example
    * ```tsx
-   * const name = watch("name", "default value");
+   * const name = watch("name");
    * ```
    */
   <TFieldName extends FieldPath<TFieldValues>>(
     name: TFieldName,
     defaultValue?: FieldPathValue<TFieldValues, TFieldName>,
-  ): FieldPathValue<TFieldValues, TFieldName> | undefined;
+  ): FieldPathValue<TFieldValues, TFieldName>;
   /**
    * Subscribe to field update/change without trigger re-render
    *


Test patch:
diff --git a/src/__tests__/useFieldArray.test.tsx b/src/__tests__/useFieldArray.test.tsx
index 5940d1bfb62..50547742eae 100644
--- a/src/__tests__/useFieldArray.test.tsx
+++ b/src/__tests__/useFieldArray.test.tsx
@@ -4125,7 +4125,7 @@ describe('useFieldArray', () => {
         }[];
       };
 
-      let array: { data: string }[] | undefined = [];
+      let array: { data: string }[] = [];
 
       function FieldArray({ control }: { control: Control<FormValues> }) {
         useFieldArray({
diff --git a/src/__typetest__/form.test-d.ts b/src/__typetest__/form.test-d.ts
index 0bb46f7d0ed..a7ba4571908 100644
--- a/src/__typetest__/form.test-d.ts
+++ b/src/__typetest__/form.test-d.ts
@@ -57,33 +57,7 @@ const schema = z.object({
     resolver: mockZodResolver(schema),
   });
 
-  expectType<number | undefined>(form.watch('id'));
-
-  form.handleSubmit((data) => {
-    expectType<{ id: number }>(data);
-  });
-}
-
-/** it should correctly infer the output type from a schema */ {
-  /* eslint-disable react-hooks/rules-of-hooks */
-  const form = useForm({
-    resolver: mockZodResolver(schema),
-  });
-
-  expectType<number>(form.watch('id', 4711));
-
-  form.handleSubmit((data) => {
-    expectType<{ id: number }>(data);
-  });
-}
-
-/** it should correctly infer the output type from a schema */ {
-  /* eslint-disable react-hooks/rules-of-hooks */
-  const form = useForm({
-    resolver: mockZodResolver(schema),
-  });
-
-  expectType<number | undefined>(form.watch('id', undefined));
+  expectType<number>(form.watch('id'));
 
   form.handleSubmit((data) => {
     expectType<{ id: number }>(data);