Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions docs/examples/useWatch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,8 @@ export default () => {
const demo2 = Form.useWatch(['demo1', 'demo2'], form);
const demo3 = Form.useWatch(['demo1', 'demo2', 'demo3'], form);
const demo4 = Form.useWatch(['demo1', 'demo2', 'demo3', 'demo4'], form);
const demo5 = Form.useWatch(['demo1', 'demo2', 'demo3', 'demo4', 'demo5'], form);
const more = Form.useWatch(['age', 'name', 'gender'], form);
const hidden = Form.useWatch(['hidden'], { form, preserve: true });
console.log('main watch', values, demo1, demo2, main, age, demo3, demo4, demo5, more, hidden);
console.log('main watch', values, demo1, demo2, main, age, demo3, demo4, hidden);
return (
<>
<Form
Expand Down
11 changes: 7 additions & 4 deletions src/hooks/useWatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import { getNamePath, getValue } from '../utils/valueUtil';

type ReturnPromise<T> = T extends Promise<infer ValueType> ? ValueType : never;
type GetGeneric<TForm extends FormInstance> = ReturnPromise<ReturnType<TForm['validateFields']>>;
type IsAny<T> = 0 extends 1 & T ? true : false;
type AnyNamePath<TForm extends FormInstance> =
IsAny<GetGeneric<TForm>> extends true ? NamePath : never;
Comment on lines +16 to +18

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

By restricting the fallback overload to never for all typed forms, any path of length 5 or more (which exceeds the explicit 4-level overloads) will fail to compile, even if the path is valid or if the user explicitly overrides the type parameter (e.g., useWatch<string>(['a', 'b', 'c', 'd', 'e'], form)).

We can make AnyNamePath more flexible by allowing it to accept NamePath if the user explicitly provides a custom ValueType (other than Store) or any. This preserves strict type checking for the default case while allowing explicit type overrides for deep or dynamic paths.

type IsAny<T> = 0 extends 1 & T ? true : false;
type AnyNamePath<TForm extends FormInstance, ValueType = Store> = 
  IsAny<GetGeneric<TForm>> extends true
    ? NamePath
    : IsAny<ValueType> extends true
      ? NamePath
      : Store extends ValueType
        ? never
        : NamePath;


export function stringify(value: any) {
try {
Expand Down Expand Up @@ -75,13 +78,13 @@ function useWatch<ValueType = Store, TSelected = unknown>(
// ------- selector type end -------

function useWatch<TForm extends FormInstance>(
dependencies: NamePath,
dependencies: AnyNamePath<TForm>,
form?: TForm | WatchOptions<TForm>,
): any;

function useWatch<ValueType = Store>(
dependencies: NamePath,
form?: FormInstance | WatchOptions<FormInstance>,
function useWatch<ValueType = Store, TForm extends FormInstance = FormInstance>(
dependencies: AnyNamePath<TForm>,
form?: TForm | WatchOptions<TForm>,
): ValueType;
Comment on lines +85 to 88

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Pass the ValueType generic parameter to AnyNamePath so that explicit type overrides (e.g., useWatch<string>(...)) can bypass the never restriction for deep or dynamic paths.

Suggested change
function useWatch<ValueType = Store, TForm extends FormInstance = FormInstance>(
dependencies: AnyNamePath<TForm>,
form?: TForm | WatchOptions<TForm>,
): ValueType;
function useWatch<ValueType = Store, TForm extends FormInstance = FormInstance>(
dependencies: AnyNamePath<TForm, ValueType>,
form?: TForm | WatchOptions<TForm>,
): ValueType;


function useWatch(
Expand Down
26 changes: 22 additions & 4 deletions tests/useWatch.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import { Input } from './common/InfoField';
import { stringify } from '../src/hooks/useWatch';
import { changeValue } from './common';

type IsAny<T> = 0 extends 1 & T ? true : false;
type Equal<X, Y> =
(<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false;
type Expect<T extends true> = T;

describe('useWatch', () => {
beforeEach(() => {
jest.useFakeTimers();
Expand Down Expand Up @@ -274,6 +279,8 @@ describe('useWatch', () => {
demo?: string;
demo2?: string;
id?: number;
value3: string;
path1?: { path2?: number };
demo1?: { demo2?: { demo3?: { demo4?: string } } };
};

Expand All @@ -286,9 +293,19 @@ describe('useWatch', () => {
const demo2 = Form.useWatch(['demo1', 'demo2'], form);
const demo3 = Form.useWatch(['demo1', 'demo2', 'demo3'], form);
const demo4 = Form.useWatch(['demo1', 'demo2', 'demo3', 'demo4'], form);
const demo5 = Form.useWatch(['demo1', 'demo2', 'demo3', 'demo4', 'demo5'], form);
const more = Form.useWatch(['age', 'name', 'gender'], form);
const value3 = Form.useWatch('value3', form);
const pathValue = Form.useWatch(['path1', 'path2'], form);
const demo = Form.useWatch<string>(['demo']);
const typeCheck: [
Expect<Equal<IsAny<typeof value3>, false>>,
Expect<Equal<typeof value3, string>>,
Expect<Equal<typeof pathValue, number | undefined>>,
] = [true, true, true];

// @ts-expect-error not a field of FieldType
Form.useWatch('unknown', form);
// @ts-expect-error not a nested field of FieldType
Form.useWatch(['path1', 'unknown'], form);

const values2 = Form.useWatch(
_values => ({ newName: _values.name, newAge: _values.age }),
Expand All @@ -309,9 +326,10 @@ describe('useWatch', () => {
demo2,
demo3,
demo4,
demo5,
more,
value3,
pathValue,
demo,
typeCheck,
values2,
values3,
})}
Expand Down