You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MUI CustomSelect移动端无限渲染,如何传递event参数?

问题

基于MUI实现的CustomSelect组件在桌面端运行正常,onChange绑定的handleChange函数仅打印一次日志;但在移动端(含开发模式)会触发51次渲染后崩溃,报错:

Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

崩溃后会再打印一次日志。

尝试用e.preventDefault()解决,但无法在onChange中传入event,出现类型错误:

"Type '(e: any, newValue: any) => void' is not assignable to type '(value: number | null) => void'."

请问如何为CustomSelect传递event参数?

相关代码:

const CustomSelect = forwardRef(function CustomSelect(
      props: SelectUnstyledProps<number>,
      ref: React.ForwardedRef<any>,
   ) {
   const components: SelectUnstyledProps<number>['components'] = {
      Root: StyledButton,
      Listbox: StyledListbox,
      Popper: StyledPopper,
      ...props.components,
   };

    return <SelectUnstyled {...props} ref={ref} components={components}/>;
});

JSX使用代码:

<CustomSelect value={value} onChange={(e: any, newValue: any) => handleChange(e, newValue)}>
{$languageOptions.map((c, i) => (
    <StyledOption key={i} value={c.value}>
         <>
           ....
         </>
    </StyledOption>
))}
 </CustomSelect>

曾尝试扩展props类型:props: SelectUnstyledProps<number> & {event: any},,并修改返回部分:

return <SelectUnstyled {...props} ref={ref} components={components} onChange={(event) => props.onChange(event, props.event)} />;

但出现更多错误。

解决方案

1. 修正CustomSelect的props类型,支持带event的onChange

首先得自定义符合需求的props类型,覆盖SelectUnstyledProps里onChange的类型,让它能接收event参数:

import { SelectUnstyledProps } from '@mui/base/SelectUnstyled';

// 自定义props类型,修改onChange的签名
type CustomSelectProps = Omit<SelectUnstyledProps<number>, 'onChange'> & {
  onChange?: (event: React.MouseEvent | React.KeyboardEvent, value: number | null) => void;
};

const CustomSelect = forwardRef(function CustomSelect(
  props: CustomSelectProps,
  ref: React.ForwardedRef<any>,
) {
  const components: SelectUnstyledProps<number>['components'] = {
    Root: StyledButton,
    Listbox: StyledListbox,
    Popper: StyledPopper,
    ...props.components,
  };

  // 包装onChange,把SelectUnstyled的事件和值传递给父组件
  const handleSelectChange = (value: number | null, event: React.MouseEvent | React.KeyboardEvent) => {
    props.onChange?.(event, value);
  };

  return (
    <SelectUnstyled
      {...props}
      ref={ref}
      components={components}
      onChange={handleSelectChange} // 使用包装后的处理函数
    />
  );
});

2. 排查无限循环的根本原因

注意:e.preventDefault()大概率解决不了无限循环问题,得先找到触发多次渲染的根源:

  • 检查handleChange里是不是直接或间接调用了setState,导致组件重渲染后又触发onChange,形成循环
  • 确认value状态的更新逻辑有没有问题,是不是每次渲染都生成了新的引用(比如直接赋值对象/数组而不是用稳定引用)
  • 看看移动端有没有特殊的触摸事件触发逻辑,导致SelectUnstyled的onChange被多次调用

3. 正确使用修改后的组件

现在父组件里就能正常接收event参数了:

const handleChange = (e: React.MouseEvent | React.KeyboardEvent, newValue: number | null) => {
  // 按需用e.preventDefault(),但先确认是不是真的需要
  // e.preventDefault();
  console.log(newValue);
  // 你的状态更新逻辑
};

<CustomSelect value={value} onChange={handleChange}>
  {/* 选项内容 */}
</CustomSelect>

内容的提问来源于stack exchange,提问作者codmitu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 00:50:37