Skip to content

Conversation

@cactuser-Lu
Copy link

@cactuser-Lu cactuser-Lu commented Dec 2, 2025

fix ant-design/ant-design#55970

Summary by CodeRabbit

  • Bug修复

    • 优化对话框关闭流程,避免在动画/运动状态下误触发或丢失关闭操作,修复可见性切换导致的异常显示
  • 性能改进

    • 强化定时器管理与清理,确保卸载或状态变更时清除所有定时器,减少内存泄漏与资源累积
  • 行为改进

    • 在持续不可见情形下强制重置动画渲染,保证下次展示时动画一致性

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Dec 2, 2025

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

A member of the Team first needs to authorize it.

@gemini-code-assist
Copy link

Summary of Changes

Hello @cactuser-Lu, 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!

This pull request implements a fix to prevent "DOM residue" issues in the Dialog component, specifically when a dialog is deactivated. It introduces a delayed closing mechanism using a timeout to ensure that the doClose function, responsible for removing the dialog from the DOM, is reliably executed. This addresses scenarios where the dialog might not be properly cleaned up due to subtle timing issues with animation states, leading to lingering elements in the document.

Highlights

  • Introduced a closeTimerRef: A new useRef is added to manage a timeout for closing the dialog, ensuring proper cleanup.
  • Delayed doClose execution: When a dialog is no longer visible (visible is false) and animatedVisible is true, if there's no active motion, a 500ms timeout is now used to call doClose(). This ensures the dialog is properly removed from the DOM even if motion states are not perfectly aligned.
  • Robust cleanup: Timers are now cleared on component unmount and whenever the visible prop changes, preventing potential memory leaks or unexpected behavior related to the dialog's lifecycle.
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 by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

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 pull request 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.

@coderabbitai
Copy link

coderabbitai bot commented Dec 2, 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

在对话框关闭与内容渲染中分别加入延迟关闭计时与强制重挂载控制:src/Dialog/index.tsx 增加 closeTimerRef 并调整可见性副作用以在动效未开始时延迟或立即关闭;src/Dialog/Content/index.tsx 通过 motionKey 动态 key 在特定不可见序列下强制重置 CSSMotion

Changes

内聚体 / 文件(s) 变更概述
对话框关闭定时管理
src/Dialog/index.tsx
新增 closeTimerRef 管理延迟关闭;修改控制 visible 的 effect:每次运行清除旧定时器;当 visible 由 true->false 且仍处于动画状态时,若 contentRef 报告支持 motion 且当前不在 motion 中则立即调用 doClose,否则启动 500ms 定时器在条件仍满足时调用 doClose;在相关 effect 与卸载清理中均清除定时器以防泄漏。
内容重挂载以处理 motion 行为
src/Dialog/Content/index.tsx
新增 motionKey 状态与 prevVisibleRef 跟踪 visible 变化;当连续渲染中 visible 为 false 并满足条件时递增 motionKey,并将其作为 CSSMotionkey,从而在特定不可见序列下强制 remount 以避免 motion 挂起问题。

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 分钟

  • 关注点:
    • src/Dialog/index.tsx:定时器在多次可见性切换、不同分支与卸载路径上的创建/清理与可能的 race condition。
    • src/Dialog/Content/index.tsxmotionKey 更新条件是否会导致多余 remount 或意外状态丢失;与 CSSMotion 行为契合性。

Possibly related PRs

Suggested reviewers

  • zombieJ

诗篇

🐰 我在洞上轻轻跳,
计时器滴答不慌忙,
动画停稳再道别,
卸载归位心欢畅,
胡萝卜笑着请喝汤 🥕✨

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR标题准确概括了主要改动:解决Modal组件激活时的DOM残留问题,与代码修改内容相符。
Linked Issues check ✅ Passed 代码改动直接解决了问题#55970:通过引入延时关闭、motion支持检测和CSSMotion重挂载机制,确保Modal完全卸载。
Out of Scope Changes check ✅ Passed 所有改动均围绕Modal卸载问题展开,包括Dialog和Content组件的timer管理及动画处理,未发现超出范围的改动。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e169a3c and db0d5eb.

📒 Files selected for processing (1)
  • src/Dialog/index.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/Dialog/index.tsx

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

@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 aims to fix a DOM residue issue by introducing a fallback timer to ensure the dialog closes properly. The approach is generally sound, but I have a few suggestions for improvement. The closing logic has a minor flaw that introduces an unnecessary delay when animations are disabled, for which I've provided a corrected code snippet. Additionally, a magic number is used for the timeout duration, and I recommend extracting it into a named constant for better readability. Adding a test case to cover this specific scenario would also be valuable to prevent future regressions.

Comment on lines 170 to 178
if (hasMotion && !inMotion) {
doClose();
} else {
closeTimerRef.current = setTimeout(() => {
if (!visible && animatedVisible) {
doClose();
}
}, 500);
}

Choose a reason for hiding this comment

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

high

The current logic introduces an unnecessary 500ms delay for closing the dialog when motion is disabled (hasMotion is false). If there's no motion, doClose() should be called immediately. The condition should be updated to close the dialog if motion is disabled OR if it's not currently in motion.

Suggested change
if (hasMotion && !inMotion) {
doClose();
} else {
closeTimerRef.current = setTimeout(() => {
if (!visible && animatedVisible) {
doClose();
}
}, 500);
}
if (!hasMotion || !inMotion) {
doClose();
} else {
closeTimerRef.current = setTimeout(() => {
if (!visible && animatedVisible) {
doClose();
}
}, 500);
}

if (!visible && animatedVisible) {
doClose();
}
}, 500);

Choose a reason for hiding this comment

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

medium

The timeout duration 500 is a magic number. It's better to define it as a constant with a descriptive name (e.g., const DIALOG_CLOSE_FALLBACK_TIMEOUT = 500;) at the top of the component. This improves readability and makes the purpose of the timeout clearer and easier to modify in the future.

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 (1)
src/Dialog/Content/index.tsx (1)

38-48: 优化逻辑避免初次渲染时的不必要重挂载,并澄清注释说明。

当前实现存在以下问题:

  1. 初次渲染时的额外重挂载:当对话框初始状态为隐藏(visible=false)时,prevVisibleRef.current 初始化为 false,useEffect 在首次渲染时会触发条件 !visible && !prevVisibleRef.current,导致 motionKey 递增并强制 CSSMotion 重新挂载。这在普通场景下是不必要的性能开销。

  2. 注释不够清晰:注释说"when visible changes from false to false",但这个表述容易引起混淆。实际逻辑是"当 visible 在当前和上一次渲染/激活周期中都为 false 时触发"。建议更明确地说明这是为了处理 React.Activity 重新激活时的场景。

建议优化逻辑以避免初次渲染时的不必要重挂载:

  // Force remount CSSMotion when visible changes from false to false
  // This handles React.Activity scenarios where component state is preserved
  const [motionKey, setMotionKey] = React.useState(0);
  const prevVisibleRef = useRef(visible);
+ const isMountedRef = useRef(false);

  React.useEffect(() => {
-   if (!visible && !prevVisibleRef.current) {
+   // Only trigger remount after initial mount, when visible is false in consecutive cycles
+   if (isMountedRef.current && !visible && !prevVisibleRef.current) {
      setMotionKey(k => k + 1);
    }
+   isMountedRef.current = true;
    prevVisibleRef.current = visible;
  }, [visible]);

或者更新注释以更清晰地说明预期行为:

- // Force remount CSSMotion when visible changes from false to false
- // This handles React.Activity scenarios where component state is preserved
+ // Force remount CSSMotion when visible remains false across render/activation cycles
+ // This handles React.Activity reactivation scenarios where component state is preserved
+ // but DOM may be in an inconsistent state, causing residual elements
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0955502 and e169a3c.

📒 Files selected for processing (1)
  • src/Dialog/Content/index.tsx (2 hunks)
🔇 Additional comments (1)
src/Dialog/Content/index.tsx (1)

78-78: Verify implementation impact of key-based remounting on Dialog animation cycles.

Using key={motionKey} to force CSSMotion remount is a standard React pattern for clearing component state. When motionKey increments, the old CSSMotion instance unmounts and a new one mounts, clearing any lingering animation state.

However, this needs verification to ensure:

  1. No animation anomalies occur during rapid dialog open/close sequences
  2. No performance degradation in scenarios without React.Activity
  3. The motionKey increment logic properly aligns with visibility state changes

@codecov
Copy link

codecov bot commented Dec 3, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.50%. Comparing base (401b183) to head (ad8512c).

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #525      +/-   ##
==========================================
+ Coverage   98.46%   98.50%   +0.04%     
==========================================
  Files           8        8              
  Lines         195      201       +6     
  Branches       68       69       +1     
==========================================
+ Hits          192      198       +6     
  Misses          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.

@cactuser-Lu cactuser-Lu marked this pull request as draft December 4, 2025 12:57
@cactuser-Lu cactuser-Lu closed this Dec 5, 2025
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.

Modal 组件与 Activity 组件结合使用后未正常卸载

1 participant