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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 19:54:55