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

如何用React-Hook-Form实现兼具新增与编辑功能的表单

复用表单实现产品新增与编辑功能

要解决同一个表单同时支持新增和编辑的问题,关键在于利用React Hook Form的reset方法动态控制表单初始值,同时统一管理弹窗状态。以下是具体实现方案:

核心修改步骤

  • 合并新增、编辑的弹窗状态,用一个状态变量管理弹窗的显示状态、操作模式(新增/编辑)和当前产品数据
  • 打开弹窗时,根据操作模式调用reset方法:新增模式重置为空值,编辑模式填充选中产品的字段值
  • 统一表单提交逻辑,根据当前操作模式调用对应的API(新增/更新)
  • 修复原有代码中的逻辑漏洞(比如删除产品时的数组过滤未返回新数组)

修改后的完整代码

import { useForm } from "react-hook-form";
import { useState, useEffect } from "react";
import axios from "axios";
import {
  TableContainer, Paper, Table, TableHead, TableRow, TableCell, TableBody,
  Tooltip, IconButton, Grid, Button, Dialog, DialogTitle, DialogContent,
  DialogActions, TextField
} from "@mui/material";
import { AddIcon, EditIcon, DeleteIcon } from "@mui/icons-material";

const ENDPOINT = "你的API地址";

export default function App() {
  // ReactHookForm 配置,加上默认值初始化
  const {
    register,
    handleSubmit,
    formState: { errors },
    reset // 新增reset方法,用于重置表单值
  } = useForm({
    defaultValues: {
      pro_nom: "",
      pro_description: "",
      pro_instruction: "",
      pro_prix: 0
    }
  });

  const [produits, setProduits] = useState([]);
  // 合并弹窗状态:open(是否显示)、mode(add/edit)、currentProduct(编辑时的产品数据)
  const [dialogState, setDialogState] = useState({
    open: false,
    mode: "add",
    currentProduct: null
  });
  const [openSuppr, setOpenSuppr] = useState({ open: false, produitId: null });

  // 加载产品列表
  const chargement = async () => {
    const res = await fetch(`${ENDPOINT}/api/produits`);
    const data = await res.json();
    setProduits(data);
  };

  // 新增产品提交
  const onSubmitAdd = async (data) => {
    await axios.post(`${ENDPOINT}/api/produits`, data, {
      headers: { 'Content-Type': 'application/json' }
    });
    setProduits(prev => [...prev, data]);
    closeDialog();
  };

  // 编辑产品提交
  const onSubmitUpdate = async (data) => {
    const productId = dialogState.currentProduct.pro_id;
    await axios.put(`${ENDPOINT}/api/produits/${productId}`, data, {
      headers: { 'Content-Type': 'application/json' }
    });
    // 更新本地列表
    setProduits(prev => prev.map(item => 
      item.pro_id === productId ? { ...item, ...data } : item
    ));
    closeDialog();
  };

  // 统一提交处理:根据模式调用不同方法
  const handleFormSubmit = (data) => {
    if (dialogState.mode === "add") {
      onSubmitAdd(data);
    } else {
      onSubmitUpdate(data);
    }
  };

  // 删除产品确认
  const confirmSuppr = async (data) => {
    setOpenSuppr({ open: false, produitId: data });
    await axios.delete(`${ENDPOINT}/api/produits/${data}`);
    // 修复过滤逻辑,返回新数组
    setProduits(prevState => 
      prevState.filter(produit => produit.pro_id !== data)
    );
  };

  // 打开新增弹窗
  const openAddDialog = () => {
    setDialogState({
      open: true,
      mode: "add",
      currentProduct: null
    });
    // 重置表单为空
    reset({
      pro_nom: "",
      pro_description: "",
      pro_instruction: "",
      pro_prix: 0
    });
  };

  // 打开编辑弹窗
  const openEditDialog = (produit) => {
    setDialogState({
      open: true,
      mode: "edit",
      currentProduct: produit
    });
    // 重置表单为当前产品数据
    reset({
      pro_nom: produit.pro_nom,
      pro_description: produit.pro_description,
      pro_instruction: produit.pro_instruction,
      pro_prix: produit.pro_prix
    });
  };

  // 关闭弹窗
  const closeDialog = () => {
    setDialogState(prev => ({ ...prev, open: false }));
  };

  const handleSuppr = (Bool, ProduitID) => {
    setOpenSuppr({ open: Bool, produitId: ProduitID });
  };

  useEffect(() => {
    chargement();
  }, []);

  return (
    <div>
      <TableContainer component={Paper}>
        <Table>
          <TableHead>
            <TableRow style={{ background: "#cad2c5" }}>
              <TableCell align="center">{"ID"}</TableCell>
              <TableCell align="center">{"Nom"}</TableCell>
              <TableCell align="center">{"Description"}</TableCell>
              <TableCell align="center">{"Instruction"}</TableCell>
              <TableCell align="center">{"Prix"}</TableCell>
              <TableCell align="center">{"Action"}</TableCell>
            </TableRow>
          </TableHead>
          <TableBody>
            {produits?.map((produit) => (
              <TableRow key={`PRO_${produit.pro_id}`}>
                <TableCell align="center">{produit.pro_id}</TableCell>
                <TableCell align="center">{produit.pro_nom}</TableCell>
                <TableCell align="center">{produit.pro_description}</TableCell>
                <TableCell align="center">{produit.pro_instruction}</TableCell>
                <TableCell align="center">{produit.pro_prix}{' €'}</TableCell>
                <TableCell align="center">
                  <Tooltip title="Modifier" placement="top">
                    <IconButton
                      style={{
                        background: "#ffca3a",
                        color: "white",
                        borderRadius: "10%",
                      }}
                      onClick={() => openEditDialog(produit)}
                    >
                      <EditIcon />
                    </IconButton>
                  </Tooltip>
                  <Tooltip title="Supprimer" placement="top">
                    <IconButton
                      style={{
                        background: "#ff595e",
                        color: "white",
                        borderRadius: "10%",
                      }}
                      onClick={() => handleSuppr(true, produit.pro_id)}
                    >
                      <DeleteIcon />
                    </IconButton>
                  </Tooltip>
                </TableCell>
              </TableRow>
            ))}
          </TableBody>
        </Table>
      </TableContainer>

      <Grid>
        <Button
          variant="contained"
          endIcon={<AddIcon />}
          onClick={openAddDialog}
        >
          {"Ajouter"}
        </Button>

        {/* 复用的新增/编辑弹窗 */}
        <Dialog open={dialogState.open} onClose={closeDialog}>
          <DialogTitle>
            {dialogState.mode === "add" ? "Ajout d'un produit" : "Modification du produit"}
          </DialogTitle>
          <form onSubmit={handleSubmit(handleFormSubmit)}>
            <DialogContent>
              <TextField
                type={"text"}
                name="pro_nom"
                {...register("pro_nom", { required: true })}
                label="Nom"
                placeholder="Hamburger"
                autoFocus
                variant="outlined"
                fullWidth
                margin="normal"
              />
              {errors.pro_nom && <span style={{ color: "red" }}>Champ requis.</span>}

              <TextField
                type={"text"}
                name="pro_description"
                {...register("pro_description", { required: true })}
                label="Description"
                placeholder="Description du produit"
                variant="outlined"
                fullWidth
                margin="normal"
              />
              {errors.pro_description && <span style={{ color: "red" }}>Champ requis.</span>}

              <TextField
                type={"text"}
                name="pro_instruction"
                {...register("pro_instruction")}
                label="Instruction"
                placeholder="Instruction particulière"
                variant="outlined"
                fullWidth
                margin="normal"
              />

              <TextField
                type={"number"}
                name="pro_prix"
                {...register("pro_prix", { required: true, min: 0, valueAsNumber: true })}
                label="Prix"
                placeholder="Obligatoire"
                variant="outlined"
                fullWidth
                margin="normal"
              />
              {errors.pro_prix && <span style={{ color: "red" }}>Champ requis.</span>}
            </DialogContent>
            <DialogActions>
              <Button type="reset">{"Effacer"}</Button>
              <Button onClick={closeDialog}>{"Fermer"}</Button>
              <Button type="submit">{"Confirmer"}</Button>
            </DialogActions>
          </form>
        </Dialog>

        {/* 删除确认弹窗 */}
        <Dialog open={openSuppr.open} onClose={() => handleSuppr(false, null)}>
          <DialogTitle>Confirmation de suppression</DialogTitle>
          <DialogContent>
            Êtes-vous sûr de vouloir supprimer ce produit ?
          </DialogContent>
          <DialogActions>
            <Button onClick={() => handleSuppr(false, null)}>Annuler</Button>
            <Button onClick={() => confirmSuppr(openSuppr.produitId)} color="error">Supprimer</Button>
          </DialogActions>
        </Dialog>
      </Grid>
    </div>
  );
}

关键说明

  1. 表单复用:通过dialogState.mode区分新增和编辑模式,弹窗标题、提交逻辑会自动适配
  2. 动态设置初始值:打开弹窗时调用reset方法,新增时重置为空,编辑时填充产品数据,避免了value/defaultValue导致的状态残留问题
  3. 统一提交逻辑:handleFormSubmit根据当前模式调用新增或更新接口,减少冗余代码
  4. 修复删除逻辑:修正了原有setProduits中过滤数组未返回新数组的问题,确保删除后列表正确更新

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 15:15:46