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

React PDF上传器问题:如何索引Div块关联对应PDF文件

解决React PDF上传器中View按钮总是显示最后一个文件的问题

嘿,我完全懂你现在的困扰!作为React新手,这种状态管理的小坑特别容易踩——咱们来一步步把它搞定。

问题根源

你现在的代码里,所有上传的PDF共享同一个fileDisplay、title和visible状态变量。不管点击哪个文件的View按钮,都会触发同一个模态框,而模态框显示的是最后一次更新的fileDisplay和title值,所以才会出现“点旧文件却显示新文件”的情况。

解决方案思路

核心是让每个上传的文件都保存自己的显示信息,而不是用全局的状态变量。我们需要:

  • 把uploadedFiles从单纯的文件名数组,改成包含文件名、文件预览URL的对象数组
  • 用一个单独的状态变量记录当前要查看的文件(而不是全局控制模态框显示)
  • 让每个View按钮点击时,把当前对应的文件设置为“要查看的文件”

修改后的完整代码

我把关键修改的地方用注释标出来了,你可以对照着看:

import { FilePicker } from 'react-file-picker';
import Button from 'react-bootstrap/Button';
import Modal from 'react-awesome-modal';
import '../styles/transcripts.css';
import { Component } from 'react'; // 别忘了导入Component!

class Transcripts extends Component {
  constructor() {
    super();
    this.state = {
      visible: false,
      uploadedFiles: [],
      currentViewingFile: null // 新增:记录当前要查看的文件对象
    };
  }

  openModal() {
    this.setState({ visible: true });
  }

  closeModal() {
    this.setState({ visible: false, currentViewingFile: null }); // 关闭时清空当前文件
  }

  handleFileChange = file => {
    // 检查文件是否已上传(现在要检查文件名)
    const isDuplicate = this.state.uploadedFiles.some(item => item.name === file.name);
    if (isDuplicate) {
      alert("Duplicate file. You've already uploaded this.");
    } else {
      // 创建文件预览URL,并把文件对象(包含name和url)添加到数组
      const fileUrl = URL.createObjectURL(file);
      this.setState(prevState => ({
        uploadedFiles: [...prevState.uploadedFiles, { name: file.name, url: fileUrl }]
      }));
    }
  };

  delete(itemName) {
    if (window.confirm("Delete '" + itemName + "'?")) {
      // 删除时要找到对应的文件并revoke URL,避免内存泄漏
      const fileToDelete = this.state.uploadedFiles.find(f => f.name === itemName);
      if (fileToDelete) {
        URL.revokeObjectURL(fileToDelete.url);
      }
      this.setState(prevState => ({
        uploadedFiles: prevState.uploadedFiles.filter(f => f.name !== itemName)
      }));
    }
  }

  // 修改:接收当前文件作为参数,点击时设置currentViewingFile并打开模态框
  handleViewFile(file) {
    this.setState({ currentViewingFile: file });
    this.openModal();
  }

  // 修改:模态框的内容依赖currentViewingFile
  renderModal() {
    const { currentViewingFile } = this.state;
    if (!currentViewingFile) return null;

    return (
      <Modal 
        visible={this.state.visible} 
        min-width="800" 
        min-height="200" 
        effect="fadeInDown" 
        onClickAway={() => this.closeModal()}
      >
        <div>
          <h1>{currentViewingFile.name}</h1>
          <embed src={currentViewingFile.url} width="800px" height="700px"/>
          <br /><br /><input type="text" />
        </div>
      </Modal>
    );
  }

  fileUploader() {
    return (
      <div>
        <FilePicker 
          extensions={["pdf"]}
          onChange={this.handleFileChange}
          onError={errMsg => alert("Invalid file type.\nMust be a .pdf file")}
        >
          <Button style={{ backgroundColor: "#f57505", width: "75px" }}>
            Upload
          </Button>
        </FilePicker>
      </div>
    );
  }

  renderTranscripts() {
    if (this.state.uploadedFiles.length === 0) {
      return (
        <div>
          <div id="rectanglePadding">
            <div id="nothingToShow">
              <h1>There are no transcripts to display! </h1>
            </div>
          </div>
          <div style={{ textAlign: "center" }}>
            <br />
            <br />
            {this.fileUploader()}
          </div>
        </div>
      );
    }
    const noteItems = this.state.uploadedFiles.map(file => ( // 现在遍历的是文件对象
      <span key={file.name}> {/* 别忘了加key!React列表需要唯一key */}
        <span id="blockStyling">
          <span> {" "}
            <a id="textStyling">{file.name}</a>&emsp;
          </span>
          <p />
          <p id="textStyling2">
            Potential Issues:
            <br />
            &nbsp;&nbsp;- Short by 2 math credits
            <br />
            &nbsp;&nbsp;- Missing an elective
          </p>
          <br />
          <div style={{ textAlign: "center" }}>
            {/* 修改:点击时调用handleViewFile并传入当前文件 */}
            <Button 
              style={{ backgroundColor: "#f57505", width: "165px" }} 
              onClick={() => this.handleViewFile(file)}
            >
              View
            </Button>
            <br />
            <Button 
              style={{ backgroundColor: "#f57505", width: "165px", }} 
              onClick={() => this.delete(file.name)}
            >
              Delete
            </Button>
            <br />
            <br />
          </div>
        </span>
        <br />
        <br />
        <br />
      </span>
    ));
    return (
      <div>
        <br />
        <h1 style={{ textDecoration: "underline", paddingLeft: "30px" }}> My Transcripts </h1>
        {noteItems}
        {/* 把模态框放在这里,只渲染一次 */}
        {this.renderModal()}
        <div style={{ textAlign: "center" }}>
          <br />
          <br />
          {this.fileUploader()}
        </div>
      </div>
    );
  }

  render() {
    return <div>{this.renderTranscripts()}</div>;
  }

  // 组件卸载时,清理所有文件的URL,避免内存泄漏
  componentWillUnmount() {
    this.state.uploadedFiles.forEach(file => {
      URL.revokeObjectURL(file.url);
    });
  }
}

export default Transcripts;

关键修改点说明

  1. 状态结构优化:把uploadedFiles改成对象数组,每个对象包含name(文件名)和url(预览URL),这样每个文件都有自己的独立信息。
  2. 当前查看文件状态:新增currentViewingFile状态,专门记录用户点击的要查看的文件,模态框的内容完全依赖这个变量。
  3. View按钮逻辑:每个View按钮点击时,把对应的文件对象传给handleViewFile,设置currentViewingFile后再打开模态框。
  4. 内存泄漏防护:在删除文件和组件卸载时,调用URL.revokeObjectURL()释放文件URL占用的内存,这是很重要的细节哦!
  5. 列表key:给遍历的列表项加上key={file.name},符合React的列表渲染规范。

这样修改后,每个文件的View按钮都会触发对应文件的预览,再也不会串文件啦!

内容的提问来源于stack exchange,提问作者coder_coder123

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 12:03:12