Skip to content

fix: cleanup SpeechRecognition on component unmount#1826

Open
cxybd wants to merge 1 commit intoant-design:mainfrom
cxybd:fix/speech-recognition-cleanup
Open

fix: cleanup SpeechRecognition on component unmount#1826
cxybd wants to merge 1 commit intoant-design:mainfrom
cxybd:fix/speech-recognition-cleanup

Conversation

@cxybd
Copy link
Contributor

@cxybd cxybd commented Mar 16, 2026

🐛 Bug Description

Found a memory leak in useSpeech hook where SpeechRecognition instance is not cleaned up on component unmount.

Issue

The SpeechRecognition instance created in ensureRecognition() is stored in recognitionRef but never cleaned up, causing:

  1. Memory Leak: Instances accumulate on component mount/unmount cycles
  2. Privacy Risk: Recording may continue after component unmounts
  3. Resource Waste: Microphone permission remains occupied
  4. Event Listener Leak: onstart/onend/onresult callbacks not removed

Fix

Added useEffect cleanup to:

  • Stop recognition when component unmounts (with try-catch as it might not be started)
  • Clear all event handlers (onstart, onend, onresult)
  • Release instance reference

🔧 Changes

  • File: packages/x/components/sender/hooks/use-speech.ts
  • Lines Changed: +17 insertions
  • Type: Bug Fix

✅ Testing

  • Lint passed
  • No breaking changes
  • Cleanup only runs on unmount

📊 Severity

High - Affects all Sender components using speech recognition feature

Summary by CodeRabbit

发布说明

  • Bug 修复
    • 改进了语音识别功能的资源清理机制,确保在组件卸载时正确停止识别并清理相关资源,防止潜在的内存泄漏和异常情况。

- Add useEffect cleanup to stop recognition and clear event handlers
- Prevents memory leak when component unmounts during recording
- Fixes potential privacy issue with background recording
- Wrapped stop() in try-catch as recognition might not be started
@github-actions
Copy link
Contributor

github-actions bot commented Mar 16, 2026

Preview failed

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, 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 resolves a critical memory leak and resource management issue within the useSpeech hook. By implementing a cleanup mechanism using useEffect, it ensures that SpeechRecognition instances are properly terminated and their associated resources, such as microphone access and event listeners, are released when the component using the hook unmounts. This prevents the accumulation of instances, potential privacy risks, and improves overall application stability and performance.

Highlights

  • Memory Leak Fix: Addressed a critical memory leak in the useSpeech hook by ensuring SpeechRecognition instances are properly cleaned up when a component unmounts.
  • Resource Management: Prevented continuous recording, occupied microphone permissions, and event listener leaks by stopping recognition and clearing event handlers on component unmount.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • packages/x/components/sender/hooks/use-speech.ts
    • Added a useEffect hook to clean up SpeechRecognition instances on component unmount.
    • Implemented logic to stop recognition, clear event handlers (onstart, onend, onresult), and nullify the instance reference.
Activity
  • No human activity has been recorded on this pull request yet.
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.

@dosubot dosubot bot added bug Something isn't working javascript Pull requests that update Javascript code labels Mar 16, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 16, 2026

📝 Walkthrough

演示

该变更为语音识别组件的自定义 Hook 添加了清理逻辑,在组件卸载时停止识别、移除事件处理器并清空引用。使用 try-catch 包装以处理识别未启动的情况。

变更

内聚组/文件 摘要
SpeechRecognition 清理逻辑
packages/x/components/sender/hooks/use-speech.ts
在 React effect 中添加组件卸载时的清理逻辑:停止 SpeechRecognition(若活跃)、移除 onstart/onend/onresult 事件处理器、清空引用,并使用 try-catch 容错处理。

代码审查工作量估算

🎯 2 (Simple) | ⏱️ ~8 分钟

诗歌

🐰 噢呀,清理真是棒棒哒,
卸载时事件全抹掉,
try-catch 来保驾护航,
语音识别乖乖听话!

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题清晰准确地反映了主要变化:在组件卸载时清理SpeechRecognition。标题使用了标准的'fix'前缀,指明这是一个bug修复,并明确说明了采取的行动和涉及的组件。

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Tip

CodeRabbit can use Trivy to scan for security misconfigurations and secrets in Infrastructure as Code files.

Add a .trivyignore file to your project to customize which findings Trivy reports.

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 correctly addresses a memory leak by adding a cleanup effect for the SpeechRecognition instance. The implementation is mostly correct. I've added one comment to refine the cleanup logic to prevent potential state updates on an unmounted component, which could cause warnings in React.

Comment on lines +140 to +150
if (recognitionRef.current) {
try {
recognitionRef.current.stop();
} catch (e) {
// Recognition might not be started
}
recognitionRef.current.onstart = null;
recognitionRef.current.onend = null;
recognitionRef.current.onresult = null;
recognitionRef.current = null;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The current cleanup logic calls recognitionRef.current.stop() before nullifying the event handlers. The stop() method can trigger the onend event handler, which in turn calls setRecording(false). Since this cleanup function runs on component unmount, this will cause a state update on an unmounted component, leading to a warning from React.

To prevent this, the event handlers should be nullified before calling stop(). It's also slightly cleaner to store recognitionRef.current in a local variable to avoid repeated property access.

      const recognition = recognitionRef.current;
      if (recognition) {
        // Nullify handlers first to prevent state updates on an unmounted component.
        recognition.onstart = null;
        recognition.onend = null;
        recognition.onresult = null;

        try {
          recognition.stop();
        } catch (e) {
          // Recognition might not be started
        }
        recognitionRef.current = null;
      }

@codecov
Copy link

codecov bot commented Mar 16, 2026

Bundle Report

Changes will increase total bundle size by 1.61MB (78.51%) ⬆️⚠️, exceeding the configured threshold of 5%.

Bundle name Size Change
antdx-array-push 3.53MB 1.61MB (83.56%) ⬆️⚠️

Affected Assets, Files, and Routes:

view changes for bundle: antdx-array-push

Assets Changed:

Asset Name Size Change Total Size Change (%)
antdx.js (New) 3.53MB 3.53MB 100.0% 🚀
antdx.min.js (Deleted) -1.92MB 0 bytes -100.0% 🗑️

Copy link
Contributor

@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.

🧹 Nitpick comments (1)
packages/x/components/sender/hooks/use-speech.ts (1)

140-149: 建议在调用 stop() 前先解绑事件处理器,防止卸载阶段回调触发导致状态更新。

根据 Web Speech API 规范,调用 stop()onendonresult 仍可能触发(服务可能继续返回已收集音频的识别结果)。当前代码在 Line 142 先执行 stop(),再在 Line 146-148 置空回调处理器,存在回调在组件卸载期间仍被执行并修改状态的风险。更安全的做法是先置空处理器,再调用 stop(),可有效避免这类竞态问题。

参考调整方案
   React.useEffect(() => {
     return () => {
       if (recognitionRef.current) {
+        const recognition = recognitionRef.current;
+        recognition.onstart = null;
+        recognition.onend = null;
+        recognition.onresult = null;
         try {
-          recognitionRef.current.stop();
+          recognition.stop();
         } catch (e) {
           // Recognition might not be started
         }
-        recognitionRef.current.onstart = null;
-        recognitionRef.current.onend = null;
-        recognitionRef.current.onresult = null;
         recognitionRef.current = null;
       }
     };
   }, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/x/components/sender/hooks/use-speech.ts` around lines 140 - 149, In
the use-speech hook, avoid calling recognitionRef.current.stop() before clearing
event handlers because onend/onresult may still fire and update state during
unmount; update the teardown to first null out
recognitionRef.current.onstart/onend/onresult (and any other handlers) and only
then call recognitionRef.current.stop(), finally set recognitionRef.current =
null so callbacks cannot run after stop(); target the block that currently calls
recognitionRef.current.stop() and then clears handlers to perform the handler
unbinding first.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/x/components/sender/hooks/use-speech.ts`:
- Around line 140-149: In the use-speech hook, avoid calling
recognitionRef.current.stop() before clearing event handlers because
onend/onresult may still fire and update state during unmount; update the
teardown to first null out recognitionRef.current.onstart/onend/onresult (and
any other handlers) and only then call recognitionRef.current.stop(), finally
set recognitionRef.current = null so callbacks cannot run after stop(); target
the block that currently calls recognitionRef.current.stop() and then clears
handlers to perform the handler unbinding first.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 53693f3f-9ac8-43b9-ab50-ece6a7413d8e

📥 Commits

Reviewing files that changed from the base of the PR and between 440b8ea and fa861c5.

📒 Files selected for processing (1)
  • packages/x/components/sender/hooks/use-speech.ts

@codecov
Copy link

codecov bot commented Mar 16, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.42%. Comparing base (440b8ea) to head (fa861c5).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1826   +/-   ##
=======================================
  Coverage   97.42%   97.42%           
=======================================
  Files         149      149           
  Lines        5044     5053    +9     
  Branches     1449     1440    -9     
=======================================
+ Hits         4914     4923    +9     
  Misses        128      128           
  Partials        2        2           

☔ 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.

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

Labels

bug Something isn't working javascript Pull requests that update Javascript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant