如何使用 ReactJs 中的 dropzone 将文件及其描述添加到状态?

Posted

技术标签:

【中文标题】如何使用 ReactJs 中的 dropzone 将文件及其描述添加到状态?【英文标题】:How to add a file and its description to the state using dropzone in ReactJs? 【发布时间】:2021-01-27 07:10:55 【问题描述】:

如何使用选择、使用 react-dropzone 将一个或多个文件及其描述添加到组件的状态中。

我正在使用 Reactjs、dropzone 和 bootstrap,我想要实现的是:添加一个或多个文件(通过将它们拖动到一个区域),然后查看添加文件的列表和每个文件的选择输入(使用用户定义“类型”的选项)将所有这些保存在一个状态中,然后将该信息发送到 API。 类似于图像中显示的内容:

到目前为止,我的代码根据其扩展名(pdf、xlsx ...)和被拒绝的文件,返回了一个被接受的文件列表,但我不知道如何添加一个选择(带有“type”选项,可以是“summary”、“report”、“test”...)并将其保存为状态,然后将其发送到 API。

我目前使用react-dropzone的代码是这样的:

const baseStyle = 
  flex: 1,
  display: "flex",
  flexDirection: "column",
  alignItems: "center",
  padding: "20px",
  borderWidth: 2,
  borderRadius: 20,
  borderColor: "#26C2E7",
  borderStyle: "dashed",
  backgroundColor: "#fafafa",
  color: "#c4c4c4",
  outline: "none",
  transition: "border .24s ease-in-out"
;

const activeStyle = 
  borderColor: "#f2f"
;

const acceptStyle = 
  borderColor: "#f8f"
;

const rejectStyle = 
  borderColor: "#f2f"
;
function InputFiles(props) 
  const 
    acceptedFiles,
    fileRejections,
    isDragActive,
    isDragAccept,
    isDragReject,
    getRootProps,
    getInputProps
   = reactDropzone.useDropzone(
    accept: ".xlsx,.docx,.pdf"
  );

  const style = React.useMemo(
    () => (
      ...baseStyle,
      ...(isDragActive ? activeStyle : ),
      ...(isDragAccept ? acceptStyle : ),
      ...(isDragReject ? rejectStyle : )
    ),
    [isDragActive, isDragReject, isDragAccept]
  );

  const acceptedFileItems = acceptedFiles.map((file) => (
    <li key=file.path>
      file.path - file.size bytes
    </li>
  ));

  const fileRejectionItems = fileRejections.map(( file, errors ) => (
    <li key=file.path>
      file.path - file.size bytes
      <ul>
        errors.map((e) => (
          <li key=e.code>e.message</li>
        ))
      </ul>
    </li>
  ));

  return (
    <section className="container">
      /* <div ...getRootProps( style )> */
      <div ...getRootProps( style )>
        <input ...getInputProps() />
        <p>Drag 'n' drop some files here, or click to select files</p>
        <em>(Only *.pdf , *.xlsx , *.docx files will be accepted)</em>
      </div>
      <aside>
        <h4>Accepted files</h4>
        <ul>acceptedFileItems</ul>
        <h4>Rejected files</h4>
        <ul>fileRejectionItems</ul>
      </aside>
    </section>
  );


ReactDOM.render(<InputFiles />, document.body);

window.onload = function() 
 console.log('onload');
   
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.7.2/prop-types.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.1/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dropzone/11.2.0/index.js"></script>

我们的目标是得到这样的东西:

添加文件及其描述时,必须以组件的状态保存,目的是点击保存时向API发出POST请求,点击取消时必须删除状态信息

【问题讨论】:

【参考方案1】:

正如@Emmanuel 在他的回答中提到的那样,您可以将文件存储在一个状态中,这个实现使用名称作为键将文件存储在地图中。

import React,  useMemo, useState  from "react";
import  useDropzone  from "react-dropzone";

const baseStyle = 
  flex: 1,
  display: "flex",
  flexDirection: "column",
  alignItems: "center",
  padding: "20px",
  borderWidth: 2,
  borderRadius: 20,
  borderColor: "#26C2E7",
  borderStyle: "dashed",
  backgroundColor: "#fafafa",
  color: "#c4c4c4",
  outline: "none",
  transition: "border .24s ease-in-out"
;

const activeStyle = 
  borderColor: "#f2f"
;

const acceptStyle = 
  borderColor: "#f8f"
;

const rejectStyle = 
  borderColor: "#f2f"
;
function InputFiles(props) 
  const [files, setFiles] = useState();
  const 
    fileRejections,
    isDragActive,
    isDragAccept,
    isDragReject,
    getRootProps,
    getInputProps
   = useDropzone(
    onDrop: (acceptedFiles) => 
      setFiles((prevFiles) =>
        acceptedFiles.reduce(
          (acc, file) => (
            ...acc,
            [file.name]: 
              file,
              fileType: ""
            
          ),
          prevFiles
        )
      );
    ,
    accept: ".xlsx,.docx,.pdf"
  );
  const style = useMemo(
    () => (
      ...baseStyle,
      ...(isDragActive ? activeStyle : ),
      ...(isDragAccept ? acceptStyle : ),
      ...(isDragReject ? rejectStyle : )
    ),
    [isDragActive, isDragReject, isDragAccept]
  );
  const acceptedFileItems = Object.keys(files).map((fileName) => 
    const currentFile = files[fileName].file;
    const onSelectChange = (e) => 
      e.persist();
      setFiles((prevFiles) => 
        return 
          ...prevFiles,
          [fileName]: 
            ...prevFiles[fileName],
            fileType: e.target.value
          
        ;
      );
    ;

    return (
      <li key=fileName>
        <div style= display: "flex" >
          <span>
            currentFile.path - currentFile.size bytes
          </span>
          <select value=currentFile.fileType onChange=onSelectChange>
            <option value=""></option>
            <option value="summary">summary</option>
            <option value="description">report</option>
            <option value="report">description</option>
          </select>
        </div>
      </li>
    );
  );

  const fileRejectionItems = fileRejections.map(( file, errors ) => (
    <li key=file.path>
      file.path - file.size bytes
      <ul>
        errors.map((e) => (
          <li key=e.code>e.message</li>
        ))
      </ul>
    </li>
  ));

  return (
    <section className="container">
      /* <div ...getRootProps( style )> */
      <div ...getRootProps( style )>
        <input ...getInputProps() />
        <p>Drag 'n' drop some files here, or click to select files</p>
        <em>(Only *.pdf , *.xlsx , *.docx files will be accepted)</em>
      </div>
      <aside>
        <h4>Accepted files</h4>
        <ul>acceptedFileItems</ul>
        <h4>Rejected files</h4>
        <ul>fileRejectionItems</ul>
        <button onClick=() => console.log(files)>console log files</button>
      </aside>
    </section>
  );


export default InputFiles;

你可以在这里查看工作https://codesandbox.io/s/react-drop-zone-select-vhc2l

【讨论】:

非常感谢,这正是我想要的。【参考方案2】:

useDropZone 使用回调 onDrop,它可以让您像这样获取文件的名称:

 const onDrop = React.useCallback((acceptedFiles) => 
    // Do something with the files
  , []);
  const  ...  = useDropzone( onDrop );

在每次删除文件时获得文件名称后,您可以将其存储在一个状态中,显示名称和选择,并在每次单击选择时相应地更新该状态。

还要注意doc 说它至少应该是 React 16.8,但您似乎使用的是 React 16.6。

【讨论】:

您能说得具体一点吗? @diedu 为你做了例子。

以上是关于如何使用 ReactJs 中的 dropzone 将文件及其描述添加到状态?的主要内容,如果未能解决你的问题,请参考以下文章

如何限制 dropzone.js 中的最大“总”文件大小?

如何克服 Laravel dropzone 文件上传中的 getClientOriginalName() 错误?

ASP.Net MVC 中的简单 Dropzone 实现 - 如何在控制器中获取数据?

如何使用 Jest 和 react-testing-library 测试 react-dropzone?

上传完成后如何将新文件名作为隐藏表单输入返回到 Dropzone?

如何使用 dropzone 上传 base64 图像资源?