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

MUI Select搭配react-hook-form切换i18n语言时值越界报错

问题描述

接入i18next实现国际化后,结合react-hook-form封装的MUI Select组件在切换语言时,控制台抛出MUI: You have provided an out-of-range value警告。

  • 多语言配置示例:
// 法语配置
{
  "component": {
    "firstOption": "Rouge",
    "secondOption": "Vert"
  }
}

// 英语配置
{
  "component": {
    "firstOption": "Red",
    "secondOption": "Green"
  }
}
  • 异常表现:法语环境下组件默认选中值为Rouge,切换到英语后选中值未同步更新为Red,仍保留旧值Rouge,该值不在英语环境的选项列表内,触发值越界警告。
  • 相关代码:
    • 封装组件Select.tsx:
import { useState, useEffect } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { SelectChangeEvent } from '@mui/material';

type SelectProps = {
  defaultValue: string;
  options: string[];
};

const Select = ({ defaultValue, options }: SelectProps) => {
  const [anchorEl] = useState<null | HTMLElement>(null);
  const [selectData, setSelectData] = useState('');
  const open = Boolean(anchorEl);
  const { control, register } = useForm();

  const registerData = register('selectData');
  const handleChange = (event: SelectChangeEvent) => {
    setSelectData(event.target.value);
  };

  useEffect(() => {
    setSelectData(defaultValue);
  }, [defaultValue]);

  return (
    <Controller
      name="Select"
      control={control}
      defaultValue={''}
      render={() => (
        <Select
          aria-controls={open ? 'select' : undefined}
          aria-expanded={open ? 'true' : undefined}
          aria-haspopup="true"
          id="select"
          {...registerData}
          value={selectData}
          onChange={(e: any) => {
            registerData.onChange(e);
            handleChange(e);
          }}
        >
          {options.map((option) => (
            <div key={option} value={option}>
              {option}
            </div>
          ))}
        </Select>
      )}
    />
  );
};

export default Select;
  • 调用方App.tsx:
import { useTranslation } from 'react-i18next';
import Select from '../../shared/Select/Select';

function App() {
  const { t } = useTranslation('pages');

  return (
    <div>
      <Select
        defaultValue={t('component.firstOption')}
        options={[t('component.firstOption'), t('component.secondOption')]}
      />
    </div>
  );
}

export default App;
问题根因

一共5个核心问题导致异常:

  1. 选项值和展示文案强耦合:直接把多语言翻译后的动态文案作为Select选项的value,切换语言后同个选项的value会随翻译结果变化,旧的选中值自然无法匹配新语言环境下的选项列表。
  2. react-hook-form绑定逻辑错误:同时混用register和Controller两种表单绑定方式,且Controller的defaultValue设为空字符串,和组件内部维护的selectData状态完全脱节,表单值无法随props变化同步更新。
  3. 组件递归渲染风险:自定义组件命名为Select,渲染时直接写<Select>会调用组件自身,触发无限递归报错,需要给导入的MUI Select组件设置别名规避命名冲突。
  4. MUI Select子组件用法错误:MUI Select的选项必须使用官方提供的MenuItem组件,普通div元素无法被正确识别为选项,会导致值匹配逻辑异常。
  5. 状态同步逻辑失效:虽然写了监听defaultValue更新selectData的useEffect,但因为表单状态和组件内部状态脱节,更新逻辑没有实际生效,也没有做选项列表变化时的值合法性校验。
修复方案
  1. 解耦选项值和展示文案:使用固定、与语言无关的枚举值作为选项value,多语言翻译结果仅作为展示文案,从根源避免切换语言导致value变化。
  2. 修正react-hook-form绑定逻辑:仅使用Controller做表单绑定,去掉重复的register调用,移除组件内部冗余的selectData状态,把值控制权统一交给react-hook-form管理。
  3. 解决命名冲突:给导入的MUI Select组件设置别名,避免和自定义组件重名导致递归。
  4. 替换错误子组件:使用MUI官方的MenuItem组件作为选项容器,保证值识别逻辑正常。

修复后完整代码

  • 调整后的Select.tsx:
import { useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
// 给MUI Select设置别名,避免和自定义组件重名
import { Select as MUISelect, MenuItem } from '@mui/material';

// 选项类型定义,value固定,label为多语言展示文案
type Option = {
  value: string;
  label: string;
}

type SelectProps = {
  defaultValue: string;
  options: Option[];
};

const Select = ({ defaultValue, options }: SelectProps) => {
  const [anchorEl] = useState<null | HTMLElement>(null);
  const open = Boolean(anchorEl);
  const { control } = useForm({
    defaultValues: {
      selectData: defaultValue
    }
  });

  return (
    <Controller
      name="selectData"
      control={control}
      defaultValue={defaultValue}
      render={({ field }) => (
        <MUISelect
          aria-controls={open ? 'select' : undefined}
          aria-expanded={open ? 'true' : undefined}
          aria-haspopup="true"
          id="select"
          {...field}
        >
          {options.map((option) => (
            <MenuItem key={option.value} value={option.value}>
              {option.label}
            </MenuItem>
          ))}
        </MUISelect>
      )}
    />
  );
};

export default Select;
  • 调整后的App.tsx:
import { useTranslation } from 'react-i18next';
import Select from '../../shared/Select/Select';

function App() {
  const { t } = useTranslation('pages');

  // value使用固定枚举值,label走多语言翻译
  const options = [
    {
      value: 'firstOption',
      label: t('component.firstOption')
    },
    {
      value: 'secondOption',
      label: t('component.secondOption')
    }
  ]

  return (
    <div>
      <Select
        defaultValue="firstOption"
        options={options}
      />
    </div>
  );
}

export default App;

修复后切换语言时,选项的value始终是固定枚举值,只有展示的label会随语言切换,不会再出现值不在选项范围内的问题,越界警告会完全消除,也解决了原代码存在的递归渲染、表单状态不同步等隐性问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 22:03:29