Material UI Autocomplete组件Bug求助:新增产品后显示异常
解决Autocomplete添加后显示异常的问题
核心问题分析
value与options类型不匹配,匹配逻辑错误
你的Autocomplete组件中,value传递的是p.name(字符串类型),但isOptionEqualToValue里却在比较option.name和value.name——如果options是字符串数组(从product.map生成的结果),那么option本身就是字符串,没有name属性;value是字符串,也没有name属性,这会导致该函数永远返回false,Autocomplete无法正确关联选中值和下拉选项,从而出现显示异常。使用index作为key导致组件复用异常
用数组index作为key,当添加新项时,React会复用之前的组件实例,导致Autocomplete的状态没有正确重置,引发显示错乱。product状态初始化与使用矛盾
product初始化为[""](字符串数组),但后续却用option.name访问属性,这会导致初始options全部是"loading...",且类型不统一。
修复代码步骤
1. 修正Autocomplete的value匹配逻辑
如果product是从后端获取的对象数组(比如[{name: "产品1"}, {name: "产品2"}]),保持isOptionEqualToValue的同时,要确保value的结构和options元素一致;如果是字符串数组,直接比较字符串本身即可。
2. 替换index为唯一key
给每个productList项添加唯一id(比如时间戳或uuid),避免使用index作为key。
3. 修正product状态初始化
将product初始化为对象数组,和后端返回结构保持一致。
完整修复后的代码示例
import { useState } from 'react'; import { Autocomplete, TextField, CircularProgress } from '@mui/material'; const WaybillPage = () => { // 给每个产品项添加唯一id,避免用index当key const [productList, setProductList] = useState([{ id: Date.now(), name: "", quantity: 1 }]); // product初始化为对象数组,与后端返回结构一致 const [product, setProduct] = useState([{ name: "" }]); const [loading, setLoading] = useState(false); // 假设使用react-hook-form,保留你的errors和register // const { register, formState: { errors } } = useForm(); const handleQuantityChange = (e, index) => { const { name, value } = e.target; const list = [...productList]; list[index][name] = value; setProductList(list); }; const handleLabelChange = (name, index, newValue) => { const list = [...productList]; // 根据newValue类型赋值,兼容对象或字符串 list[index][name] = newValue?.name || newValue; setProductList(list); }; const handleServiceAdd = () => { // 添加新项时生成唯一id setProductList([...productList, { id: Date.now(), name: "", quantity: 0 }]); }; return ( <div> {productList.map((p, index) => ( <Autocomplete key={p.id} // 使用唯一id作为key // 当options是对象数组时的匹配逻辑 isOptionEqualToValue={(option, value) => option.name === value.name} // 匹配选中值,无匹配时返回空对象 value={product.find(item => item.name === p.name) || { name: "" }} disablePortal onChange={(e, newValue) => { handleLabelChange("name", index, newValue); }} id={`controllable-states-demo-${p.id}`} // 设置唯一id // 渲染选项,加载时显示loading options={loading ? [{ name: "loading..." }] : product} renderOption={(props, option) => ( <li {...props}>{option.name}</li> )} renderInput={(params) => ( <TextField // 每个字段使用唯一名称,避免表单注册冲突 {...register(`productNameV-${p.id}`)} error={errors[`productNameV-${p.id}`] ? true : false} helperText={errors[`productNameV-${p.id}`]?.message} {...params} label={`Product ${index + 1}`} InputProps={{ ...params.InputProps, endAdornment: ( <> {loading ? <CircularProgress color="inherit" size={20} /> : null} {params.InputProps.endAdornment} </> ), }} /> )} /> ))} <button onClick={handleServiceAdd}>添加产品</button> </div> ); }; export default WaybillPage;
额外注意事项
- 确保
product状态更新时是正确的对象数组,加载完成后替换掉loading状态 - 表单注册的字段名要唯一,避免多个TextField共用同一个字段名导致表单状态冲突
- 如果你的
options确实是字符串数组,可简化isOptionEqualToValue为(option, value) => option === value,并保持value为字符串类型
内容的提问来源于stack exchange,提问作者Commodore
相关产品推荐
相关产品推荐

