Skip to content

fix(select): merge raw trigger element event handlers#1215

Merged
afc163 merged 1 commit intoreact-component:masterfrom
QDyanbing:fix/merge-raw-trigger-event-handlers
Feb 26, 2026
Merged

fix(select): merge raw trigger element event handlers#1215
afc163 merged 1 commit intoreact-component:masterfrom
QDyanbing:fix/merge-raw-trigger-event-handlers

Conversation

@QDyanbing
Copy link
Contributor

@QDyanbing QDyanbing commented Feb 26, 2026

🐞 Bug 修复

问题背景

在使用 getRawInputElement 提供自定义触发元素时,rc-select 会对该元素进行 clone 并注入内部的 domProps

当前实现中,如果自定义元素与 domProps 中存在同名事件处理函数(如 onFocusonBlur 等),内部函数会直接覆盖用户传入的函数,导致用户回调无法触发。


修复内容

在克隆自定义触发元素时:

  • 仍然以 domProps 为基础,保证内部逻辑正常执行;
  • 当双方存在同名且均为函数的属性时,进行函数合并而不是覆盖;
  • 执行顺序为:内部函数优先,随后执行用户函数。

该修改不会影响现有功能逻辑,仅修复事件被覆盖的问题。


测试说明

新增测试用例,确保:

  • 自定义触发元素上的 onFocus / onBlur 等事件能够正常触发;
  • 内部逻辑行为保持不变。

Summary by CodeRabbit

发布说明

  • Bug Fixes

    • 修复了自定义输入元素的事件处理器被覆盖的问题,确保用户提供的回调函数正确执行。
  • Tests

    • 添加测试用例以验证自定义输入元素的事件处理器在搜索场景下的正确性。

@vercel
Copy link

vercel bot commented Feb 26, 2026

@QDyanbing 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
Contributor

Summary of Changes

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

此拉取请求解决了 rc-select 组件中一个关键的事件处理问题。当用户提供自定义触发元素时,内部逻辑错误地覆盖了用户定义的事件处理函数。通过引入智能合并机制,确保了内部功能和用户自定义行为都能按预期执行,从而提升了组件的灵活性和可靠性。

Highlights

  • 修复自定义触发元素事件覆盖问题: 当使用 getRawInputElement 提供自定义触发元素时,rc-select 内部的 domProps 会覆盖自定义元素上同名的事件处理函数(如 onFocusonBlur),导致用户回调无法触发。
  • 事件处理函数合并逻辑: 在克隆自定义触发元素时,如果自定义元素和 domProps 存在同名且均为函数的属性,现在会进行函数合并而不是覆盖。执行顺序为内部函数优先,随后执行用户函数。
  • 新增测试用例: 增加了新的测试用例,以确保自定义触发元素上的 onFocus / onBlur 等事件能够正常触发,并且内部逻辑行为保持不变。

🧠 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
  • src/SelectInput/index.tsx
    • 修改了 SelectInput 组件中处理 RootComponent 的逻辑,以合并而不是覆盖自定义触发元素的事件处理函数。
  • tests/Custom.test.tsx
    • 新增了一个测试用例,验证自定义输入元素的 onFocusonBlur 事件处理函数不会被覆盖,能够正常触发。
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
Contributor

coderabbitai bot commented Feb 26, 2026

概述

在 SelectInput 组件中添加了一个 RootComponent props 合并层,将原始 props 与 DOM props 合并,对于两端都是函数的 props 进行回调组合,使原始回调和 DOM 回调都能被调用。同时增加了测试覆盖以验证自定义输入元素的事件处理器不会被覆盖。

变更内容

Cohort / File(s) Summary
RootComponent Props 合并
src/SelectInput/index.tsx
添加 props 合并逻辑,从 RootComponent.props 收集原始 props,与 domProps 合并,对两端都是函数的 props 进行回调组合,确保两个回调都能被调用。
事件处理器保留测试
tests/Custom.test.tsx
新增测试用例验证通过 getRawInputElement 渲染的自定义输入元素的 onFocus 和 onBlur 事件处理器不会被覆盖。

可能相关的 PR

建议的审阅者

  • afc163
  • zombieJ

兔子之诗

🐰 Props 合并起,回调齐声唱,
原始与新生,共舞在选择框,
事件不再丢,函数双双响!✨


🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题准确总结了PR的主要变更:修复了当使用getRawInputElement提供自定义触发元素时,事件处理器被覆盖的问题,通过合并事件处理器来保留用户提供的回调函数。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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

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.

Code Review

这个 PR 很好地修复了自定义触发器元素上的事件处理器被覆盖的问题。合并处理器的方案是正确的,保证了内部逻辑和用户回调都能执行。新增的测试用例也有效地验证了修复。我有一个建议,可以重构部分实现以提高代码的可读性和类型安全。

Comment on lines +222 to +244
const originProps = (RootComponent as any).props || {};
const mergedProps = { ...originProps, ...domProps };

Object.keys(originProps).forEach((key) => {
const originVal = originProps[key];
const domVal = domProps[key];

if (typeof originVal === 'function' && typeof domVal === 'function') {
mergedProps[key] = (...args: any[]) => {
domVal(...args);
originVal(...args);
};
}
});

if (React.isValidElement<any>(RootComponent)) {
return React.cloneElement(RootComponent, {
...domProps,
...mergedProps,
ref: composeRef((RootComponent as any).ref, rootRef),
});
}

return <RootComponent {...domProps} ref={rootRef} />;
return <RootComponent {...mergedProps} ref={rootRef} />;
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

处理 RootComponent 的逻辑可以进行重构以提高代码清晰度和类型安全。通过首先检查 RootComponent 是否为 ReactElement,我们可以将元素和组件类型的处理逻辑分开。这避免了在 RootComponent 是组件类型时不必要地执行属性合并逻辑,同时也移除了 any 类型断言,使代码更健壮且易于维护。

    if (React.isValidElement(RootComponent)) {
      const originProps = RootComponent.props || {};
      const mergedProps = { ...originProps, ...domProps };

      Object.keys(originProps).forEach(key => {
        const originVal = originProps[key];
        const domVal = domProps[key];

        if (typeof originVal === 'function' && typeof domVal === 'function') {
          mergedProps[key] = (...args: any[]) => {
            domVal(...args);
            originVal(...args);
          };
        }
      });

      return React.cloneElement(RootComponent, {
        ...mergedProps,
        ref: composeRef(RootComponent.ref, rootRef),
      });
    }

    return <RootComponent {...domProps} ref={rootRef} />;

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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/Custom.test.tsx (1)

48-49: 断言建议改为精确调用次数,防止重复触发漏检

当前断言只校验“至少调用一次”,如果未来出现重复触发也会通过。建议改为 toHaveBeenCalledTimes(1)

✅ 建议修改
-    expect(onFocus).toHaveBeenCalled();
-    expect(onBlur).toHaveBeenCalled();
+    expect(onFocus).toHaveBeenCalledTimes(1);
+    expect(onBlur).toHaveBeenCalledTimes(1);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/Custom.test.tsx` around lines 48 - 49, The assertions in
Custom.test.tsx use toHaveBeenCalled() which only checks "at least once" and can
miss duplicated triggers; update the expectations for the mocks onFocus and
onBlur to assert exact invocation counts by replacing
expect(onFocus).toHaveBeenCalled() and expect(onBlur).toHaveBeenCalled() with
expect(onFocus).toHaveBeenCalledTimes(1) and
expect(onBlur).toHaveBeenCalledTimes(1) so the test fails if handlers fire
multiple times.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/SelectInput/index.tsx`:
- Around line 225-235: The current merging loop in SelectInput (iterating
Object.keys(originProps) and combining any same-named functions into
mergedProps) wrongly combines non-event functions; change the condition to only
compose keys that are event handlers (e.g., match /^on[A-Z]/) and where both
originProps[key] and domProps[key] are functions, leaving other same-named
functions untouched; use the existing symbols originProps, domProps, mergedProps
and only create the combined wrapper for keys matching the event pattern so
non-event functions that rely on return values are not altered.

---

Nitpick comments:
In `@tests/Custom.test.tsx`:
- Around line 48-49: The assertions in Custom.test.tsx use toHaveBeenCalled()
which only checks "at least once" and can miss duplicated triggers; update the
expectations for the mocks onFocus and onBlur to assert exact invocation counts
by replacing expect(onFocus).toHaveBeenCalled() and
expect(onBlur).toHaveBeenCalled() with expect(onFocus).toHaveBeenCalledTimes(1)
and expect(onBlur).toHaveBeenCalledTimes(1) so the test fails if handlers fire
multiple times.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 700c9f8 and 1b6a7a4.

📒 Files selected for processing (2)
  • src/SelectInput/index.tsx
  • tests/Custom.test.tsx

Comment on lines +225 to +235
Object.keys(originProps).forEach((key) => {
const originVal = originProps[key];
const domVal = domProps[key];

if (typeof originVal === 'function' && typeof domVal === 'function') {
mergedProps[key] = (...args: any[]) => {
domVal(...args);
originVal(...args);
};
}
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

仅合并事件处理函数,避免误合并普通函数属性

当前会合并所有同名函数属性,不仅是事件处理器。这样可能误伤非事件函数(例如依赖返回值的函数),与本次修复目标不一致。建议只对事件键(如 onFocus/onBlur)做组合调用。

🔧 建议修改
 Object.keys(originProps).forEach((key) => {
   const originVal = originProps[key];
   const domVal = domProps[key];

-  if (typeof originVal === 'function' && typeof domVal === 'function') {
+  const isEventHandler = /^on[A-Z]/.test(key);
+  if (isEventHandler && typeof originVal === 'function' && typeof domVal === 'function') {
     mergedProps[key] = (...args: any[]) => {
       domVal(...args);
       originVal(...args);
     };
   }
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Object.keys(originProps).forEach((key) => {
const originVal = originProps[key];
const domVal = domProps[key];
if (typeof originVal === 'function' && typeof domVal === 'function') {
mergedProps[key] = (...args: any[]) => {
domVal(...args);
originVal(...args);
};
}
});
Object.keys(originProps).forEach((key) => {
const originVal = originProps[key];
const domVal = domProps[key];
const isEventHandler = /^on[A-Z]/.test(key);
if (isEventHandler && typeof originVal === 'function' && typeof domVal === 'function') {
mergedProps[key] = (...args: any[]) => {
domVal(...args);
originVal(...args);
};
}
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/SelectInput/index.tsx` around lines 225 - 235, The current merging loop
in SelectInput (iterating Object.keys(originProps) and combining any same-named
functions into mergedProps) wrongly combines non-event functions; change the
condition to only compose keys that are event handlers (e.g., match /^on[A-Z]/)
and where both originProps[key] and domProps[key] are functions, leaving other
same-named functions untouched; use the existing symbols originProps, domProps,
mergedProps and only create the combined wrapper for keys matching the event
pattern so non-event functions that rely on return values are not altered.

@codecov
Copy link

codecov bot commented Feb 26, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.43%. Comparing base (700c9f8) to head (1b6a7a4).
⚠️ Report is 2 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1215   +/-   ##
=======================================
  Coverage   99.43%   99.43%           
=======================================
  Files          31       31           
  Lines        1239     1248    +9     
  Branches      427      451   +24     
=======================================
+ Hits         1232     1241    +9     
  Misses          7        7           

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

@afc163 afc163 merged commit 344404d into react-component:master Feb 26, 2026
9 of 10 checks passed
@QDyanbing QDyanbing deleted the fix/merge-raw-trigger-event-handlers branch February 26, 2026 12:32
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