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

React项目卡片ID传递异常与useEffect清理修复组件状态更新警告

问题原因与解决方案

问题1:任意卡片按钮触发操作时获取的ID都是最后一张卡片的ID

原因

  1. 所有卡片共用了同一组open/openUpdate弹窗开关状态,点击任意卡片的按钮都会打开所有卡片对应的弹窗,最终视觉上仅能看到最后渲染的弹窗,绑定的回调自然指向最后一张卡片的数据。
  2. 循环生成弹窗时,回调函数通过闭包捕获的medication变量为同一引用,循环结束后该变量的值固定为数组最后一项,无论触发哪个弹窗的操作,拿到的都是最后一个ID。

修复方案

全局仅保留1个删除弹窗、1个编辑弹窗,不要在列表循环中生成重复弹窗。新增activeMedicationId状态存储当前操作的药物ID,点击卡片上的编辑/删除按钮时,先将当前药物ID写入该状态再打开弹窗,操作时直接从状态中取ID即可。

问题2:多次更新同个药物剂量时触发已卸载组件更新警告

原因

  1. 直接修改从localStorage读取的数组引用,没有将药物列表存入组件状态,修改数据后组件无法触发正常重渲染,新旧DOM节点状态不一致。
  2. 弹窗关闭时表单组件被卸载,但React Hook Form仍有未处理的表单状态更新任务,导致对已卸载的组件执行状态更新。
  3. 重复生成的弹窗卸载时存在残留的事件监听与状态更新逻辑,引发内存泄漏。

修复方案

  1. 将药物列表存入useState状态,修改数据时先更新状态再同步到localStorage,触发组件正常重渲染。
  2. 弹窗关闭时调用React Hook Form提供的reset方法清空表单状态,清除pending的更新任务。
  3. 移除循环中的弹窗代码,全局仅保留两个弹窗实例。

修正后核心代码

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 23:15:05