Skip to content

Conversation

EmilyyyLiu
Copy link
Contributor

@EmilyyyLiu EmilyyyLiu commented Sep 9, 2025

关联issue:ant-design/ant-design#54854
替换 useMergedState 为 useControlledState

Summary by CodeRabbit

  • 重构
    • 统一将多个日期/范围选择器及相关 hooks 的内部状态管理切换为更一致的受控模式,提升模式、开始/结束值与内部值的可预测性与同步性,对外行为与 API 未变。
  • 杂务
    • 升级依赖 @rc-component/util 到 1.3.0,优化内部状态钩子实现与渲染一致性。

Copy link

vercel bot commented Sep 9, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
picker Ready Ready Preview Comment Sep 10, 2025 3:00am

Copy link

coderabbitai bot commented Sep 9, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

在多个 Picker 组件与其钩子中,将状态管理钩子从 useMergedState 统一替换为 useControlledState(并更新导入);useDelayState 增加强制重渲染机制以配合新钩子;同时将 package.json 中的 @rc-component/util 依赖从 ^1.2.1 升级到 ^1.3.0

Changes

Cohort / File(s) Summary of changes
依赖更新
package.json
@rc-component/util 依赖版本从 ^1.2.1 升级为 ^1.3.0
PickerInput:RangePicker / SinglePicker
src/PickerInput/RangePicker.tsx, src/PickerInput/SinglePicker.tsx
将管理 mode 的钩子由 useMergedState 替换为 useControlledState;更新导入并将初始化签名从对象式({ value: ... } / postState)改为直接的默认值作为第一参、受控值作为第二参;内部行为衔接保留。
PickerPanel
src/PickerPanel/index.tsx
三处内部状态(mergedMode、innerValue、mergedPickerValue)从 useMergedState 迁移至 useControlledStatepostState 式默认回退改为在默认参数中直接回退(如 `picker
PickerInput 钩子:useRangePickerValue / useRangeValue
src/PickerInput/hooks/useRangePickerValue.ts, src/PickerInput/hooks/useRangeValue.ts
将内部值管理从 useMergedState 改为 useControlledState,将惰性初始化与对象式 value 参数切换为直接传入默认值与受控值;导入更新,外部 API 签名未变。
PickerInput 钩子:useDelayState(行为变化)
src/PickerInput/hooks/useDelayState.ts
useMergedState 替换为 useControlledState;新增本地 state 和 forceUpdate 用于强制重渲染,并通过 triggerUpdate = useEvent(...) 在异步/延迟更新路径中触发 setState + 强制刷新;保留其余 refs、清理与 onChange 调用逻辑。

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor 用户 as 用户
  participant Picker as SinglePicker/RangePicker
  participant Panel as PickerPanel
  participant UCS as useControlledState

  用户->>Picker: 传入 props(mode / picker / value / defaults)
  Picker->>UCS: useControlledState(default, controlledValue)
  note right of UCS #DFF2E1: 默认值为第一参,受控值为第二参
  UCS-->>Picker: 返回 [state, setState]

  用户->>Panel: 传入 props(mode / value / pickerValue / defaults)
  Panel->>UCS: mergedMode = useControlledState(picker || 'date', mode)
  Panel->>UCS: innerValue = useControlledState(defaultValue, value)
  Panel->>UCS: mergedPickerValue = useControlledState(defPicker || mergedValue[0] || now, pickerValue)
  UCS-->>Panel: 返回各 [state, setState]

  Note over Picker,Panel: useMergedState 的对象式 value/postState 逻辑被改为 useControlledState 的直接参数模式

  participant DelayHook as useDelayState
  DelayHook->>UCS: useControlledState(default, value)
  DelayHook->>DelayHook: triggerUpdate -> setState + forceUpdate(强制重渲染)
  DelayHook-->>Picker: 更新状态并触发渲染/回调
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • afc163

Poem

我在代码田里轻轻跳,
钩子换新步子妙,
默认先行受控随,
强制刷新保步调,
胡萝卜堆里我蹦跳 🥕🐇

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed 标题使用 “refactor” 前缀,简洁地指出升级 utils 依赖并替换 useMergedState 的主要改动,准确反映了 PR 的核心内容且易于同事快速理解。
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link
Contributor

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

Choose a reason for hiding this comment

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

Summary of Changes

Hello @EmilyyyLiu, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求通过将各种选择器组件中的状态管理从 useMergedState 迁移到 useControlledState 来进行重构。此更改与 @rc-component/util 库的升级保持一致,确保了受控和非受控组件状态处理的一致性和潜在改进。

Highlights

  • 依赖升级: 将 @rc-component/util 依赖升级到 ^1.3.0 版本。
  • 状态管理钩子替换: 在多个组件中,将 useMergedState 替换为 useControlledState,以优化状态管理逻辑。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

socket-security bot commented Sep 9, 2025

All alerts resolved. Learn more about Socket for GitHub.

This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored.

View full report

Copy link
Contributor

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

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 successfully upgrades the @rc-component/util dependency and replaces the deprecated useMergedState with useControlledState. The changes are generally correct, but I've found one area for improvement in src/PickerInput/hooks/useRangePickerValue.ts. The refactoring has removed lazy initialization for a state hook, which could lead to a minor performance degradation. My suggestion restores the original lazy initialization behavior.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/PickerPanel/index.tsx (2)

2-2: 从顶层导入 warning:与其它文件导入路径不一致

本文件从 '@rc-component/util' 顶层导入 warning,而 RangePicker 等仍使用 '@rc-component/util/lib/warning'。建议统一导入风格,避免包入口差异带来的树摇/类型分辨差异。

- import { useEvent, useControlledState, warning } from '@rc-component/util';
+ import { useEvent, useControlledState } from '@rc-component/util';
+ import warning from '@rc-component/util/lib/warning';

230-231: 未受控模式对 picker 变更的同步(可选增强)

PickerPanel 无“打开态”钩子来复位模式。若外部动态切换 picker(如从 'date' 切到 'month')且未受控,建议在 picker 变化时同步 mergedMode,避免面板与 picker 不一致。

 const [mergedMode, setMergedMode] = useControlledState<PanelMode>(picker || 'date', mode);
+// 当外部未受控且 picker 变化时,重置模式
+React.useEffect(() => {
+  if (mode === undefined) {
+    setMergedMode(picker || 'date');
+  }
+  // 仅依赖 picker,避免用户在面板内逐级切换时被外部覆盖
+}, [picker]);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 08fd30a and 50b7e6e.

📒 Files selected for processing (6)
  • package.json (1 hunks)
  • src/PickerInput/RangePicker.tsx (2 hunks)
  • src/PickerInput/SinglePicker.tsx (2 hunks)
  • src/PickerInput/hooks/useRangePickerValue.ts (2 hunks)
  • src/PickerInput/hooks/useRangeValue.ts (2 hunks)
  • src/PickerPanel/index.tsx (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/PickerPanel/index.tsx (1)
src/interface.tsx (1)
  • PanelMode (86-86)
src/PickerInput/RangePicker.tsx (1)
src/interface.tsx (1)
  • PanelMode (86-86)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: WIP
🔇 Additional comments (11)
package.json (1)

44-44: 确认 util 升级与 API 匹配

  • 验证 @rc-component/[email protected] 顶层导出是否包含 useControlledState 和 warning(可运行 npm view @rc-component/[email protected] exports 或在 node_modules/@rc-component/util 中 grep -R 进行验证)
  • 确认仓库内已无 useMergedState 引用:rg -n "useMergedState"
  • 安装 father 和 typescript 后在本地执行 npm run compile 与 npm run lint:tsc ,确保编译与类型校验均通过
src/PickerInput/hooks/useRangeValue.ts (2)

1-1: import 迁移到 useControlledState 合理

从 @rc-component/util 引入 useControlledState 与 useEvent,一致性良好。


120-121: 用 useControlledState 取代 useMergedState:语义差异提示

useControlledState(defaultValue, value) 不再支持 postState/惰性默认值选项;默认值变化后不会自动重置未受控状态(与过去 useMergedState 的行为基本一致)。请确认业务上没有依赖“默认值在 prop 变化时重置内部状态”的语义。

src/PickerInput/SinglePicker.tsx (2)

1-1: 引入 useControlledState 替换 useMergedState

顶层从 @rc-component/util 引入,风格与本 PR 其他文件一致。


262-262: 模式状态改用 useControlledState

未受控场景下默认以 picker 作为初始值;结合下方 useLayoutEffect 在打开时重置到 picker,可覆盖“默认值不随 prop 动态变化”的潜在差异,符合预期。

src/PickerInput/RangePicker.tsx (2)

1-1: 引入 useControlledState OK

与全局迁移方案一致。


313-315: modes 使用 useControlledState

默认值设为 [picker, picker],受控时由 mode 驱动;未受控时结合打开态的重置逻辑可保持历史行为。建议回归测试:切换 activeIndex、切换 picker、打开/关闭时的面板模式是否与预期一致。

src/PickerInput/hooks/useRangePickerValue.ts (2)

1-1: import 调整为 useControlledState

一致性良好。


75-83: 默认值计算从惰性变为即时计算:影响极小但请确认

此前 useMergedState(() => getDefaultPickerValue(i)) 为惰性初始化;现在直接调用 getDefaultPickerValue(i) 作为初值,仅在首次渲染时使用。该函数开销很小,通常无影响,但请确认 generateConfig.getNow() 不会引入额外副作用。

src/PickerPanel/index.tsx (2)

241-247: 值状态改为 useControlledState

与对外 API 兼容;triggerChange 中的变更判定仍以 isSame 比较语义值,行为保持。


280-283: pickerValue 改为 useControlledState

默认回退至 defaultPickerValue || mergedValue[0] || now,且在 value[0] 变化时(pickerValue 未受控)用 effect 同步,避免视图日期漂移,逻辑妥当。

Copy link

codecov bot commented Sep 9, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.80%. Comparing base (95da35c) to head (ae9def7).
⚠️ Report is 3 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master     #940   +/-   ##
=======================================
  Coverage   98.80%   98.80%           
=======================================
  Files          65       65           
  Lines        2669     2680   +11     
  Branches      742      719   -23     
=======================================
+ Hits         2637     2648   +11     
  Misses         29       29           
  Partials        3        3           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@EmilyyyLiu EmilyyyLiu changed the title [WIP]refactor: Upgrade utils and replace useMergedState refactor: Upgrade utils and replace useMergedState Sep 9, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8d2fe35 and a940494.

📒 Files selected for processing (1)
  • src/PickerInput/hooks/useDelayState.ts (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: WIP
🔇 Additional comments (1)
src/PickerInput/hooks/useDelayState.ts (1)

1-1: 请手动验证 @rc-component/util 导出
当前在沙箱环境中未能找到 @rc-component/util 模块,可能依赖未安装或路径不符。请在本地安装依赖后,确认 useControlledStateuseEvent 是否在根包(index.d.ts)中导出,且其类型签名(泛型、参数顺序)与预期一致。如发现未导出,请改为从具体路径导入或采用回退方案。

@zombieJ
Copy link
Member

zombieJ commented Sep 10, 2025

@gemini-code-assist,重新 CR。测试用例失败,应该有什么不一致的地方需要看看:

 FAIL  tests/range.spec.tsx (7.661 s)
  ● Picker.Range › fixed open need repeat trigger onOpenChange

    expect(jest.fn()).toHaveBeenCalledTimes(expected)

    Expected number of calls: 2
    Received number of calls: 1

      840 |         jest.runAllTimers();
      841 |       });
    > 842 |       expect(onOpenChange).toHaveBeenCalledTimes(i + 1);
          |                            ^
      843 |     }
      844 |   });

Copy link
Contributor

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

Choose a reason for hiding this comment

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

Code Review

本次 PR 主要将 useMergedState 替换为来自升级后的 @rc-component/utiluseControlledState hook,并升级了相关依赖。整体重构清晰,但在 src/PickerPanel/index.tsx 文件中,对 useMergedStatepostState 逻辑的迁移可能不够完整,存在 mode prop 为 null 时的行为差异,我已在具体文件中提出了修改建议。除此之外,其他文件的修改都是正确的,很好地完成了重构目标。

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/PickerInput/hooks/useDelayState.ts (1)

33-37: 修复测试失败:重复值更新时也应触发 onChange

失败用例“Picker.Range › fixed open need repeat trigger onOpenChange”期望重复触发 onOpenChange。当前以 state !== nextValueRef.current 作为触发条件导致第二次相同值更新被抑制,回调仅调用 1 次。

建议在此处不做“是否变化”的短路判断,保证重复事件也能通知上层(此前通过增加 forceUpdate 仅保证了重渲染,但未保证回调触发):

   const doUpdate = useEvent(() => {
-    triggerUpdate(nextValueRef.current);
-
-    if (onChange && state !== nextValueRef.current) {
-      onChange(nextValueRef.current);
-    }
+    const next = nextValueRef.current;
+    triggerUpdate(next);
+    // 为满足“fixed open 需要重复触发 onOpenChange”的语义,重复值也回调
+    if (onChange) {
+      onChange(next);
+    }
   });

请在本改动与上一条“仅在相同值时 forceUpdate”的优化一并落地后复跑测试,预期次数应由 1 次恢复为 2 次。

♻️ Duplicate comments (1)
src/PickerInput/hooks/useDelayState.ts (1)

14-15: 请核对 useControlledState 的参数顺序(很可能应为 value 在前、defaultValue 在后)

当前传参为 (defaultValue, value)。多个生态(如 React Spectrum)的 useControlledState 签名是 (value, defaultValue, onChange?),若此处顺序相反,会导致“受控/非受控”语义颠倒,进而影响后续回调触发与渲染节奏。

若确认 @rc-component/util 的签名为 (value, defaultValue),请应用以下修正:

-  const [state, setState] = useControlledState<T>(defaultValue, value);
+  const [state, setState] = useControlledState<T>(value, defaultValue);

为确保万无一失,请先确认 1.3.0 版本的 d.ts/实现签名:

What is the TypeScript signature of `useControlledState` in `@rc-component/[email protected]`? Is it `(value, defaultValue, onChange?)` or `(defaultValue, value)`?
🧹 Nitpick comments (1)
src/PickerInput/hooks/useDelayState.ts (1)

16-22: forceUpdate 使用建议:仅在“值未变化”时触发以避免双渲染

当前 setState(nextState) 后无条件 forceUpdate({}),会导致状态变化场景产生一次额外渲染。可在“值未变化(Object.is(state, nextState))”时再强制刷新,既保留“相同值也要重渲染”的诉求,又减少不必要渲染。

   const triggerUpdate = useEvent((nextState: T) => {
     setState(nextState);
-    forceUpdate({});
+    // 仅当值未变更时强制刷新,避免多一次 render
+    if (Object.is(nextState, state)) {
+      forceUpdate({});
+    }
   });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a940494 and ae9def7.

📒 Files selected for processing (1)
  • src/PickerInput/hooks/useDelayState.ts (3 hunks)
🔇 Additional comments (1)
src/PickerInput/hooks/useDelayState.ts (1)

1-1: 引入来源更新 LGTM

从 rc-util 切换到 @rc-component/util 并引入 useControlledState 没问题。版本已在 npm 上于 8 天前发布到 1.3.0。(npmjs.com)

@zombieJ zombieJ merged commit a6b3b68 into react-component:master Sep 10, 2025
9 checks passed
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.

2 participants