React项目卡片ID传递异常与useEffect清理修复组件状态更新警告
问题原因与解决方案
问题1:任意卡片按钮触发操作时获取的ID都是最后一张卡片的ID
原因
- 所有卡片共用了同一组
open/openUpdate弹窗开关状态,点击任意卡片的按钮都会打开所有卡片对应的弹窗,最终视觉上仅能看到最后渲染的弹窗,绑定的回调自然指向最后一张卡片的数据。 - 循环生成弹窗时,回调函数通过闭包捕获的
medication变量为同一引用,循环结束后该变量的值固定为数组最后一项,无论触发哪个弹窗的操作,拿到的都是最后一个ID。
修复方案
全局仅保留1个删除弹窗、1个编辑弹窗,不要在列表循环中生成重复弹窗。新增activeMedicationId状态存储当前操作的药物ID,点击卡片上的编辑/删除按钮时,先将当前药物ID写入该状态再打开弹窗,操作时直接从状态中取ID即可。
问题2:多次更新同个药物剂量时触发已卸载组件更新警告
原因
- 直接修改从localStorage读取的数组引用,没有将药物列表存入组件状态,修改数据后组件无法触发正常重渲染,新旧DOM节点状态不一致。
- 弹窗关闭时表单组件被卸载,但React Hook Form仍有未处理的表单状态更新任务,导致对已卸载的组件执行状态更新。
- 重复生成的弹窗卸载时存在残留的事件监听与状态更新逻辑,引发内存泄漏。
修复方案
- 将药物列表存入
useState状态,修改数据时先更新状态再同步到localStorage,触发组件正常重渲染。 - 弹窗关闭时调用React Hook Form提供的
reset方法清空表单状态,清除pending的更新任务。 - 移除循环中的弹窗代码,全局仅保留两个弹窗实例。
修正后核心代码
export default function DisplayMedications() { // 引入RHF的reset方法,替换为你的实际校验规则 const { control, handleSubmit, formState, reset } = useForm({ resolver: yupResolver(yourSchema) }); // 药物列表状态 const [medications, setMedications] = useState([]); // 当前操作的药物ID const [activeMedicationId, setActiveMedicationId] = useState(null); // 删除弹窗状态 const [open, setOpen] = useState(false); const handleClose = () => { setOpen(false); setActiveMedicationId(null); }; // 编辑弹窗状态 const [openUpdate, setOpenUpdate] = useState(false); const handleCloseUpdate = () => { setOpenUpdate(false); setActiveMedicationId(null); reset(); // 关闭弹窗时清空表单 }; // 初始化从localStorage拉取药物列表 useEffect(() => { const storedMedications = localStorage.getItem("medications"); if (storedMedications) { setMedications(JSON.parse(storedMedications)); } }, []); // 删除药物逻辑 function deleteMedication() { const newMedications = medications.filter(med => med.id !== activeMedicationId); setMedications(newMedications); localStorage.setItem("medications", JSON.stringify(newMedications)); handleClose(); } // 更新剂量逻辑 function updateDose(data) { const newMedications = medications.map(med => { if (med.id === activeMedicationId) { return {...med, dose: data.dose} } return med; }); setMedications(newMedications); localStorage.setItem("medications", JSON.stringify(newMedications)); handleCloseUpdate(); } return ( <> {/* 仅循环渲染卡片 */} {medications.map((medication) => ( <Box key={medication.id}> <Card sx={cardSx}> <CardContent> <Typography sx={typographyMedicationSx} variant="h5"> Medication: {medication.medication} </Typography> <Typography sx={typographyMedicationSx} variant="h5"> Dose: {medication.dose} mg </Typography> </CardContent> <Box> <Button onClick={() => { setActiveMedicationId(medication.id); setOpenUpdate(true); }} size="large" sx={buttonSx} variant="contained" > Edit Dose </Button> <Button onClick={() => { setActiveMedicationId(medication.id); setOpen(true); }} color="error" size="large" sx={buttonSx} variant="contained" > Delete Med </Button> </Box> </Card> </Box> ))} {/* 全局唯一删除弹窗 */} <Box> <Dialog open={open} onClose={handleClose} TransitionComponent={Transition} > <Typography variant="h5" sx={{m: 5}}> Are you sure you want to delete the medication? </Typography> <Box> <Button onClick={deleteMedication} size="large" sx={buttonSx} variant="contained" > Yes </Button> <Button onClick={handleClose} color="error" size="large" sx={buttonSx} variant="contained" > No </Button> </Box> </Dialog> </Box> {/* 全局唯一编辑弹窗 */} <Box> <Dialog open={openUpdate} onClose={handleCloseUpdate} TransitionComponent={Transition} > <DialogTitle sx={dialogTitleSx}> <IconButton aria-label="close" onClick={handleCloseUpdate} sx={iconButtonSx} > <CloseIcon/> </IconButton> </DialogTitle> <form onSubmit={handleSubmit(updateDose)} noValidate > <Typography sx={updateDoseTypography} variant="h4"> Update dose </Typography> <Box sx={boxSx}> <Controller name="dose" control={control} defaultValue={""} render={({field: {ref, ...field}, fieldState: {error}}) => ( <Autocomplete {...field} autoHighlight disableClearable isOptionEqualToValue={(option, value) => option.id === value.id} id="dose-autocomplete" onChange={(event, value) => field.onChange(value.label)} options={doseSuggestions} renderInput={(params) => ( <TextField required error={!!error} helperText={error?.message} id="dose" label="Dose" name="dose" type="numeric" inputRef={ref} {...params} /> )} /> )} /> <Button disabled={!formState.isValid} size="large" sx={formButtonSx} type="submit" variant="contained" > Submit </Button> </Box> </form> </Dialog> </Box> </> ) }
内容的提问来源于stack exchange,提问作者Valerie Hosler
相关产品推荐
相关产品推荐

