
react 中 select 组件失焦后重置、需多次点击才能生效,通常是因为未正确绑定受控值(value 属性缺失),导致组件从受控模式退化为非受控模式。
在你提供的代码中,<Select> 组件使用了 onChange 回调更新状态,但缺少 value={difficulty} 属性——这使得 React 无法将该 <Select> 视为受控组件(controlled component)。一旦组件失去焦点(例如点击其他表单字段),React 会因内部状态不一致而重置其显示值,表现为“回到 placeholder”;同时,首次交互可能因状态同步延迟或 DOM 值未及时映射,导致需多次点击才生效。
✅ 正确做法:显式绑定 value 并确保初始值合法(避免 undefined 导致 placeholder 强制显示)
import { EnumDifficulty } from "@/components/Utils/EnumDifficulty";
import { useState } from "react";
const [difficulty, setDifficulty] = useState<string | undefined>(undefined);
return (
<FormControl isRequired>
<FormLabel>Niveau</FormLabel>
<Select
placeholder="Sélectionner le niveau"
value={difficulty || ""} // ✅ 关键修复:提供空字符串兜底,避免 undefined 导致 placeholder 强制激活
onChange={(e) => setDifficulty(e.target.value || undefined)}
>
{EnumDifficulty.map((diff) => (
<option key={diff} value={diff}> {/* ✅ 为每个 option 显式设置 value,与 state 类型对齐 */}
{diff}
</option>
))}
</Select>
</FormControl>
);? 注意事项:
- 永远为 <option> 设置 value 属性:仅靠子文本无法可靠映射到 e.target.value,尤其当选项含空格或特殊字符时;
- 避免用 uuidv4() 作 key:它每次渲染都生成新 ID,破坏 React 列表复用机制,可能导致选项闪烁或选择异常;应使用稳定唯一值(如 diff 本身,前提是无重复);
- 初始化状态建议明确类型:useState<string | undefined>(undefined) 比 useState() 更安全,配合 TypeScript 可提前捕获类型错误;
- 若需默认选中某项,可初始化为 useState<string>("Easy") 并移除 placeholder(或保留并设 value="" 作为“未选择”态)。
? 总结:React 表单元素的稳定性 = value + onChange 的完整闭环。漏掉 value,就等于放弃控制权——组件将自行管理内部状态,与 React 状态脱节,最终引发不可预测的行为。









