如何用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> ); }
关键说明
- 表单复用:通过
dialogState.mode区分新增和编辑模式,弹窗标题、提交逻辑会自动适配 - 动态设置初始值:打开弹窗时调用
reset方法,新增时重置为空,编辑时填充产品数据,避免了value/defaultValue导致的状态残留问题 - 统一提交逻辑:
handleFormSubmit根据当前模式调用新增或更新接口,减少冗余代码 - 修复删除逻辑:修正了原有
setProduits中过滤数组未返回新数组的问题,确保删除后列表正确更新
内容的提问来源于stack exchange,提问作者Quentin
相关产品推荐
相关产品推荐

