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

React自定义DropZone组件复用异常:多次调用仅识别首个实例

解决DropZone多实例上传时title混淆的问题

问题根源

  1. input id重复冲突:所有DropZone实例的input都使用固定的id="raised-button-file",而浏览器要求页面元素id必须唯一。点击第二个上传区域的label时,实际触发的是第一个input的change事件,导致始终使用第一个实例的title值。
  2. 全局共享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 &amp;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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 20:42:26