React发票表单问题:如何让商品价格渲染到对应行输入框
发票表单价格渲染错位问题修复
问题核心
当前代码中,价格数据通过data数组单独维护,每次fields变化时都会批量请求所有商品价格并追加到data中,导致data数组长度远大于当前表单行数,索引完全错位,出现输入第一行商品ID,价格却渲染到第二行的情况。
修复方案:将价格与对应行字段绑定
把商品价格直接存储在fields的每一项对象中,让索引完全对应,从根源避免错位。
步骤1:修改初始状态
给fields的每个对象添加price属性:
const [fields, setFields] = useState([ { nomenclature: null, qte: null, price: "", // 新增价格字段 }, ]);
步骤2:修改输入变更逻辑
在输入商品ID时,单独请求对应行的价格,更新当前行的price字段,而非批量请求所有行:
const handleChange = async (i, event) => { const { name, value } = event.target; let newFormValues = [...fields]; newFormValues[i][name] = value; // 仅当修改商品ID且有值时,请求对应价格 if (name === "nomenclature" && value) { try { const response = await axios.get( `http://localhost:3001/api/product/${value}` ); newFormValues[i].price = response.data.price; } catch (error) { console.error("获取价格失败:", error); newFormValues[i].price = ""; // 请求失败清空价格 } } setFields(newFormValues); };
步骤3:修正价格输入框绑定
直接从当前行的fields对象中取价格:
<TextField id={`prix-${index}`} name="prix" fullWidth variant="outlined" placeholder="prix" InputProps={{ readOnly: true, }} value={fields[index].price} // 绑定当前行的price字段 />
步骤4:更新添加新行逻辑
新增行时,给新对象也加上price属性:
function Add() { let newField = { nomenclature: "", qte: "", price: "" }; setFields([...fields, newField]); }
步骤5:清理多余代码
删除单独的data状态和监听fields的useEffect,现在不需要批量请求数据,而是按需请求单条商品价格。
完整修复代码
const Facture = () => { //State const [fields, setFields] = useState([ { nomenclature: null, qte: null, price: "", }, ]); const [id, setId] = useState(""); // 若id无其他用途可删除 // 输入变更处理 const handleChange = async (i, event) => { const { name, value } = event.target; let newFormValues = [...fields]; newFormValues[i][name] = value; if (name === "nomenclature" && value) { try { const response = await axios.get( `http://localhost:3001/api/product/${value}` ); newFormValues[i].price = response.data.price; } catch (error) { console.error("获取价格失败:", error); newFormValues[i].price = ""; } } setFields(newFormValues); if (name === "nomenclature") setId(value); }; // 删除行 const removeFormFields = (i) => { let newFormValues = [...fields]; newFormValues.splice(i, 1); setFields(newFormValues); }; // 添加新行 function Add() { let newField = { nomenclature: "", qte: "", price: "" }; setFields([...fields, newField]); } return ( <ComponentSkeleton> {fields.map((field, index) => { return ( <Fragment key={index}> <Grid item xs={12} sm={6}> <TextField required id={`nom-${index}`} name="nomenclature" label="Nomenclature Produit" fullWidth autoComplete="shipping country" variant="outlined" value={field.nomenclature} onChange={(e) => handleChange(index, e)} /> </Grid> <Grid item xs={12} sm={2}> <TextField id={`prix-${index}`} name="prix" fullWidth variant="outlined" placeholder="prix" InputProps={{ readOnly: true, }} value={field.price} /> </Grid> <Grid item xs={12} sm={1}> <TextField required id={`qte-${index}`} name="qte" label="Qte" fullWidth variant="outlined" value={field.qte} onChange={(e) => handleChange(index, e)} /> </Grid> <Grid item xs={12} sm={2}> <TextField readOnly id={`total-${index}`} name="total" label="Prix total" fullWidth variant="outlined" value={field.price && field.qte ? (field.price * field.qte).toFixed(2) : ""} /> </Grid> <Grid item xs={12} sm={1}> <IconButton aria-label="delete" color="error" onClick={() => removeFormFields(index)} > <DeleteIcon /> </IconButton> </Grid> </Fragment> ); })} <Grid item xs={3}> <Button variant="contained" startIcon={<AddShoppingCartIcon color="white" />} onClick={Add} size="small" > Ajouter </Button> </Grid> <Grid item xs={3}></Grid> <Grid item xs={3}> <center> <b>Total</b> </center> </Grid> <Grid item xs={3}> <b>{fields.reduce((sum, item) => sum + (item.price * item.qte || 0), 0).toFixed(2)}</b> </Grid> <Grid item xs={3}></Grid> <Grid item xs={3}> <center> <Button variant="outlined" color="success" size="large" > Valider </Button> </center> </Grid> <Grid item xs={3}> <Button variant="outlined" color="error" size="large"> Annuler </Button> </Grid> </ComponentSkeleton> ); };
内容的提问来源于stack exchange,提问作者Firas mezrani
相关产品推荐
相关产品推荐

