Как удалить дубликаты файлов при перетаскивании файлов в компонент React с помощью HTML-перетаскивания

У меня есть компонент React перетаскивания, написанный на чистом HTML. Компонент позволяет пользователям перетаскивать один или несколько файлов. Как только файлы помещаются в зону перетаскивания, имена файлов отображаются под зоной перетаскивания вместе с крестообразной кнопкой «x» для удаления любого из перетаскиваемых файлов, как показано на снимке экрана ниже:

введите описание изображения здесь

Этот компонент отображает только уникальные файлы. Это означает, что если перетаскивать повторяющиеся файлы, экран отображает эти файлы только один раз. Однако где-то в моем коде дубликаты файлов сохраняются, и здесь проблема в том, что если я удаляю файл из пользовательского интерфейса и предполагаю, что он был удален несколько раз, этот файл будет появляться снова и снова всякий раз, когда я удаляю его из UI. Он продолжает появляться столько раз, сколько я его перетаскивал в dropzone. Это проблема, которую я не могу решить в своем коде.

Я хочу, чтобы этот код был обновлен так, чтобы, если файл удаляется несколько раз, он должен отображаться только по одному (что уже происходит), но также, когда я удаляю этот файл из пользовательского интерфейса, он не должен появляться снова.

Ниже я предоставляю все мои App.js и App.css, которые вы можете легко скопировать и вставить и запустить поведение, если хотите:

App.js:

import React, { useRef, useState, useEffect } from "react";
import "./App.css";

function App() {
  const fileInputRef = useRef();
  const [selectedFiles, setSelectedFiles] = useState([]);
  const [validFiles, setValidFiles] = useState([]);
  const [unsupportedFiles, setUnsupportedFiles] = useState([]);
  const [errorMessage, setErrorMessage] = useState("");

  // useEffect Hook that removes stops duplicate files to be displayed
  useEffect(() => {
    let filteredArr = selectedFiles.reduce((acc, current) => {
      const x = acc.find((item) => item.name === current.name);
      if (!x) {
        return acc.concat([current]);
      } else {
        return acc;
      }
    }, []);
    setValidFiles([...filteredArr]);
  }, [selectedFiles]);

  // fileDrop method that access all the files which are being dragged and sends to handleFiles method
  const fileDrop = (e) => {
    preventDefault(e);
    const files = e.dataTransfer.files;
    if (files.length) {
      handleFiles(files);
    }
  };

  // fileSelected method that sends the files to handleFiles
  const filesSelected = () => {
    if (fileInputRef.current.files.length) {
      handleFiles(fileInputRef.current.files);
    }
  };

  // handleFiles method that receives all the files being dragged
  // validates if the files are of correct type
  // sets the selectedFiles state if valid or sets errorMessage state variable
  const handleFiles = (files) => {
    for (let i = 0; i < files.length; i++) {
      if (validateFile(files[i])) {
        setSelectedFiles((prevArray) => [...prevArray, files[i]]);
      } else {
        files[i]["invalid"] = true;
        setSelectedFiles((prevArray) => [...prevArray, files[i]]);
        setErrorMessage("File type not permitted");
        setUnsupportedFiles((prevArray) => [...prevArray, files[i]]);
      }
    }
  };

  // method that holds the valid allowed file types and checks if each of the file is valid or not
  const validateFile = (file) => {
    const validTypes = [
      "image/jpeg",
      "image/jpg",
      "image/png",
      "image/gif",
      "image/x-icon",
    ];
    if (validTypes.indexOf(file.type) === -1) {
      return false;
    }

    return true;
  };

  // method that receives the file name and handles the deletion of the file from all state variables.
  const removeFile = (name) => {
    const index = validFiles.findIndex((e) => e.name === name);
    const index2 = selectedFiles.findIndex((e) => e.name === name);
    const index3 = unsupportedFiles.findIndex((e) => e.name === name);
    validFiles.splice(index, 1);
    selectedFiles.splice(index2, 1);
    setValidFiles([...validFiles]);
    setSelectedFiles([...selectedFiles]);
    if (index3 !== -1) {
      unsupportedFiles.splice(index3, 1);
      setUnsupportedFiles([...unsupportedFiles]);
    }
  };

  // methods to prevent default browser bahavior on dragOver, dragEnter and dragLeave
  const preventDefault = (e) => {
    e.preventDefault();
  };

  const dragOver = (e) => {
    preventDefault(e);
  };

  const dragEnter = (e) => {
    preventDefault(e);
  };

  const dragLeave = (e) => {
    preventDefault(e);
  };

  // final HTML returned by the App component
  return (
    <div>
      <p className="title">React Drag and Drop Image Upload</p>
      <div className="content">
        <>
          <div className="container">
            <div
              className="drop-container"
              onDragOver={dragOver}
              onDragEnter={dragEnter}
              onDragLeave={dragLeave}
              onDrop={fileDrop}
            >
              <div className="drop-message">
                Drag & Drop files here or click to select file(s)
              </div>
              <input
                ref={fileInputRef}
                className="file-input"
                type="file"
                multiple
                onChange={filesSelected}
              />
            </div>
            <div className="file-display-container">
              {validFiles.map((data, i) => (
                <div className="file-status-bar" key={i}>
                  <div>
                    <span
                      className={`file-name ${
                        data.invalid ? "file-error" : ""
                      }`}
                    >
                      {data.name}
                    </span>

                    {data.invalid && (
                      <span className="file-error-message">
                        ({errorMessage})
                      </span>
                    )}
                  </div>
                  <div
                    className="file-remove"
                    onClick={() => removeFile(data.name)}
                  >
                    X
                  </div>
                </div>
              ))}
            </div>
          </div>
        </>
      </div>
    </div>
  );
}

export default App;

App.css:

.title {
    font-size: 2rem;
    text-align: center !important;
    margin-top: 10%;
    color: #4aa1f3;
    font-weight: bold;
}

.content {
    background-color: white;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}

.file-input {
    display: none;
}

.container {
    transform: translateY(-100%);
}

.container p {
    color: red;
    text-align: center;
}

.drop-container {
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0;
    width: 800px;
    height: 200px;
    border: 4px dashed #4aa1f3;
}

.drop-message {
    text-align: center;
    color: #4aa1f3;
    font-family: Arial;
    font-size: 20px;
}

.file-display-container {
    position: fixed;
    width: 805px;
}

.file-status-bar {
    width: 100%;
    vertical-align: top;
    margin-top: 10px;
    margin-bottom: 20px;
    position: relative;
    line-height: 50px;
    height: 50px;
}

.file-status-bar>div {
    overflow: hidden;
}

.file-name {
    display: inline-block;
    vertical-align: top;
    margin-left: 50px;
    color: #4aa1f3;
}

.file-error {
    display: inline-block;
    vertical-align: top;
    margin-left: 50px;
    color: #9aa9bb;
}

.file-error-message {
    color: red;
}

.file-remove {
    position: absolute;
    top: 20px;
    right: 10px;
    line-height: 15px;
    cursor: pointer;
    color: red;
    margin-right: -10px;
}

Большое спасибо за помощь и поддержку.


person Syed Noman Ahmed    schedule 15.11.2020    source источник


Ответы (1)


Мне удалось реализовать логику в двух своих функциях, чтобы справиться с этим дублированием. Первая функция, которую я обновил, - это функция fileDrop и аналогичная логика, которую я написал во второй функции filesSelected, чтобы делать то же самое, если пользователь просматривает файлы вместо перетаскивания.

const fileDrop = (e) => {
    preventDefault(e);
    const files = e.dataTransfer.files;

    if (files.length) {
      let filteredArray = [];
      for (var index = 0; index < files.length; index++) {
        let exists = false;
        for (var i = 0; i < selectedFiles.length; i++) {
          if (selectedFiles[i].name === files[index].name) {
            exists = true;
            break;
          }
        }
        if (!exists) {
          filteredArray.push(files[index]);
        }
      }
      handleFiles(filteredArray);
    }
  };

  const filesSelected = () => {
    const files = fileInputRef.current.files;
    if (files.length) {
      let filteredArray = [];
      for (var index = 0; index < files.length; index++) {
        let exists = false;
        for (var i = 0; i < selectedFiles.length; i++) {
          if (selectedFiles[i].name === files[index].name) {
            exists = true;
            break;
          }
        }
        if (!exists) {
          filteredArray.push(files[index]);
        }
      }
      handleFiles(filteredArray);
    }
  };

Как вы можете видеть в обеих функциях, я делаю следующее:

  1. Циклический просмотр всех файлов, выбранных или перетаскиваемых пользователем
  2. Для каждого файла в цикле установка переменной exists в false
  3. Для каждого файла в цикле выполняется вложенный цикл для всех файлов, которые у меня уже есть в массиве selectedFiles.
  4. Во вложенном цикле для каждого из файлов в массиве selectedFiles я сравниваю, совпадает ли имя этого файла с именем файла, которое в настоящее время повторяется во внешнем цикле (массив файлов).
  5. Если условие истинно, для переменной exists устанавливается значение true, и внутренний цикл завершается, а внешний цикл переходит к следующему файлу в массиве файлов.
  6. Если условие ложно, это означает, что существующая переменная ложна, что означает, что новый выбранный или перетаскиваемый файл не существует в массиве selectedFiles; поэтому я помещаю его в новый массив filterArray.
  7. После завершения внешнего цикла у нас есть только те файлы в filterArray, которых еще нет в массиве selectedFiles, и поэтому я передаюfilteredArray методу handleFiles, в котором всегда будут уникальные файлы.
person Syed Noman Ahmed    schedule 28.12.2020