Skip to content

fix(type): restrict useWatch typed paths#799

Open
biubiukam wants to merge 1 commit into
react-component:masterfrom
biubiukam:fix/use-watch-types
Open

fix(type): restrict useWatch typed paths#799
biubiukam wants to merge 1 commit into
react-component:masterfrom
biubiukam:fix/use-watch-types

Conversation

@biubiukam

@biubiukam biubiukam commented Jun 17, 2026

Copy link
Copy Markdown

🤔 This is a ...

  • 🆕 New feature
  • 🐞 Bug fix
  • 📝 Site / documentation improvement
  • 📽️ Demo improvement
  • 💄 Component style improvement
  • 🤖 TypeScript definition improvement
  • 📦 Bundle size optimization
  • ⚡️ Performance optimization
  • ⭐️ Feature enhancement
  • 🌐 Internationalization
  • 🛠 Refactoring
  • 🎨 Code style optimization
  • ✅ Test Case
  • 🔀 Branch merge
  • ⏩ Workflow
  • ⌨️ Accessibility improvement
  • ❓ Other (about what?)

🔗 Related Issues

Related to ant-design/ant-design#58405.
close ant-design/ant-design#58405

💡 Background and Solution

Form.useWatch can infer watched value types from typed form instances, but the broad NamePath fallback overload also accepts unknown field paths when a typed form is provided. As a result, invalid field names can pass TypeScript checks.

This change keeps the runtime behavior unchanged and preserves the existing loose behavior for untyped forms. For typed form instances, the broad fallback overload is now limited to forms whose value type is any, so valid field paths continue to infer their value types while unknown top-level or nested paths are rejected by TypeScript.

The type coverage now verifies that:

  • a typed scalar field returns its declared value type;
  • a nested optional path preserves undefined in the return type;
  • unknown typed paths fail type checking.

📝 Change Log

Language Changelog
🇺🇸 English Improve Form.useWatch type checking for typed forms to reject unknown field paths.
🇨🇳 Chinese 改进 Form.useWatch 在类型化表单中的类型检查,未知字段路径将被拒绝。

✅ Test Plan

  • npm run lint:tsc
  • npm test -- tests/useWatch.test.tsx --runInBand
  • npm run lint
  • npm test -- --runInBand
  • npm run compile

Summary by CodeRabbit

发布说明

  • 功能改进

    • 优化了 useWatch 钩子的类型推导,提供更精准的 TypeScript 类型检查和编辑器智能补全支持。
  • 测试

    • 强化了 useWatch 钩子的类型安全验证测试,覆盖更多使用场景。
  • 文档

    • 更新了 useWatch 示例文档,移除冗余测试代码,精简示例输出内容。

@vercel

vercel Bot commented Jun 17, 2026

Copy link
Copy Markdown

@biubiukam is attempting to deploy a commit to the React Component Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bccc54e8-30d5-47b5-99b9-8ef7d8982a7b

📥 Commits

Reviewing files that changed from the base of the PR and between 62af919 and 38f2dca.

📒 Files selected for processing (3)
  • docs/examples/useWatch.tsx
  • src/hooks/useWatch.ts
  • tests/useWatch.test.tsx

Walkthrough

src/hooks/useWatch.ts 中新增 IsAnyAnyNamePath<TForm> 条件类型,将 useWatchdependencies 参数从 NamePath 收敛为受 form 泛型约束的有效字段路径,非 any 泛型场景下对无效路径产生编译错误。测试文件新增编译期断言工具类型及对应用例,示例文件移除无关的 demo5/more 订阅。

Changes

useWatch 依赖项类型约束

Layer / File(s) Summary
条件类型工具与重载签名更新
src/hooks/useWatch.ts
新增 IsAny<T>AnyNamePath<TForm> 条件类型;两个 useWatch 重载的 dependencies 参数由 NamePath 改为 AnyNamePath<TForm>ValueType 重载为 TForm 新增默认类型参数 = FormInstance
类型断言工具与 typescript 测试用例
tests/useWatch.test.tsx
新增编译期断言工具类型 IsAny/Equal/ExpectFieldType 扩展 value3: string 与嵌套 path1?.path2?: number;新增 useWatch 类型推导断言调用及两处 @ts-expect-error 验证非法路径;调整 JSON.stringify 输出字段。
示例文件清理
docs/examples/useWatch.tsx
移除对 demo5more['age', 'name', 'gender'])的 useWatch 订阅,并同步简化 console.log 输出内容。

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Suggested reviewers

  • zombieJ

Poem

🐇 兔子跳过类型迷宫,
AnyNamePath 守护字段之门,
无效路径?编译器皱眉头!
@ts-expect-error 笑着点头,
类型安全,从此不再忧。
✨ 咚咚咚,字段推导稳如山!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(type): restrict useWatch typed paths' clearly and concisely describes the main change: restricting type checking for useWatch with typed form instances.
Linked Issues check ✅ Passed The PR successfully addresses both issues from #58405: improves type inference for typed form fields and restricts invalid field paths through TypeScript type constraints.
Out of Scope Changes check ✅ Passed All changes are scoped to useWatch type improvements and related tests; no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

docs/examples/useWatch.tsx

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

src/hooks/useWatch.ts

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

tests/useWatch.test.tsx

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces stricter type checking for the useWatch hook by defining IsAny and AnyNamePath helper types to validate dependencies against the form's generic type. It also updates the test suite and documentation to reflect these changes. The review feedback highlights a critical limitation: restricting the fallback overload to never prevents compilation for paths of length 5 or more, even when explicit type overrides are provided. To resolve this, the reviewer suggests refactoring AnyNamePath and the useWatch overloads to accept and respect an explicit ValueType parameter, allowing users to bypass the strictness for deep or dynamic paths.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/hooks/useWatch.ts
Comment on lines +16 to +18
type IsAny<T> = 0 extends 1 & T ? true : false;
type AnyNamePath<TForm extends FormInstance> =
IsAny<GetGeneric<TForm>> extends true ? NamePath : never;

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;

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

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;

@biubiukam

Copy link
Copy Markdown
Author

@afc163 这个 PR 是否需要调整呢

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Form.useWatch未正常推导出类型且没有限制watch字段

1 participant