React自定义DropZone组件复用异常:多次调用仅识别首个实例
解决DropZone多实例上传时title混淆的问题
问题根源
- input id重复冲突:所有DropZone实例的input都使用固定的
id="raised-button-file",而浏览器要求页面元素id必须唯一。点击第二个上传区域的label时,实际触发的是第一个input的change事件,导致始终使用第一个实例的title值。 - 全局共享FileReader:组件外部定义的
reader被所有实例共享,异步读取文件时可能出现回调覆盖的潜在问题。
解决方案
修改DropZone组件,确保每个实例拥有独立的input标识和FileReader实例:
import { Alert, Box, Snackbar, Stack, Typography } from "@mui/material"; import { ReactElement, useState, useId } from "react"; const allowdImageTypes: string[] = ["image/jpeg", "image/jpg", "image/png"]; const DropZone = ({ title, imagesHandler, }: { title: string; imagesHandler: Function; }): ReactElement => { const [error, setError] = useState<null | string>(null); const [previewFile, setPreviewFile] = useState<any>(null); // 为每个实例生成唯一的input id const fileInputId = useId(); const photoHandler = (event: { target: { files: any } }) => { imagesHandler({ [title]: "" }); const temp: Blob | undefined = event?.target!.files[0]; if (temp) { if (!allowdImageTypes.includes(temp.type)) { setError("File does not support. You must use .png or .jpg "); return; } // 1 MB max if (temp.size > 10e5) { setError("Please upload a file smaller than 1 MB"); return; } // 每个上传操作创建独立的FileReader实例,避免共享冲突 const reader = new FileReader(); reader.onload = function (e) { imagesHandler({ [title]: temp }); setPreviewFile(e.target?.result); }; reader.readAsDataURL(temp); } }; return ( <Box> <Stack mt={2} width={1}> <input accept=".png, .jpg, .jpeg" style={{ display: "none" }} id={fileInputId} type="file" onChange={(e) => photoHandler(e)} /> <label htmlFor={fileInputId} style={{ cursor: "pointer", textAlign: "center", borderColor: "#ccc", padding: "1rem", border: "1px dashed", borderRadius: 8, }} > <Typography variant="subtitle2" color="info.main" display="flex" justifyContent="center" alignItems="center" > Click to upload {title ? title.toString().toLowerCase() : "photo"} </Typography> <Typography variant="caption" noWrap color="info.main"> Size &lt;= 1MB </Typography> </label> </Stack> {previewFile && ( <Box> <img src={previewFile} style={{ maxHeight: 150, width: "100%" }} alt="thumbnail" /> </Box> )} {error && ( <Snackbar open={error ? true : false} autoHideDuration={3000} onClose={() => setError(null)} > <Alert elevation={6} variant="filled" severity="error"> {error} </Alert> </Snackbar> )} </Box> ); }; export default DropZone;
额外优化(可选)
在ImageUpload组件中使用函数式更新状态,避免闭包导致的状态不一致问题:
const imageHandler = (imgData: { header?: any; thumb?: any }) => { console.log(imgData); setInput(prevInput => ({ ...prevInput, ...imgData })); };
修改后效果
每个DropZone实例拥有独立的input标识和FileReader,点击不同上传区域时,会触发对应实例的处理逻辑,正确传递当前实例的title值给imagesHandler。
内容的提问来源于stack exchange,提问作者M K Garwa
相关产品推荐
相关产品推荐

