How to make Previews in react-dropzone work with files other than images?
Asked Answered
M

1

14

I need help with how to make the react-dropzone NPM package in making uploaded files show a preview on files other than Image files (*.png, *.jpg/jpeg, *.gif, etc. — those all generate a preview just fine).

Currently, when I use Dropzone for uploading accompanying files on a web form, if I Upload an Image file (*.png, *.jpg, etc.), the Preview we have set up shows just fine with a small thumbnail of the Uploaded file. (see pic below)

However, if I upload another type of file, say an MS-Outlook *.docx, *.xlsx, or say an Adobe Acrobat *.pdf, it just gives me a blank box with a broken file image and whatever alt="..." text I had put in there, in this case, "Uploaded File Preview." (see pic below)

The code we are using was copied nearly verbatim from the Preview example on the React Dropzon Web Site, so I'm wondering if I missed something here?

Here's what I've tried —

  1. Create a react-dropzone <Dropzone> page, with have it set to accept any file type, and giving it the ability to do Previews with the code from https://react-dropzone.js.org/#previews, having the "Preview" <aside> at the bottom of the <Dropzone> section of the code.
  2. Drag and Drop any sort of Image file (*.png, *.jpg or *.jpeg, *.gif, etc.).
  3. Look at the Preview that appears (should be ok, a thumbnail of the image file).
  4. Now Drag and Drop any other sort of file (.doc, .xls, *.pdf, etc.) onto the <Dropzone>.
  5. Look at the Preview that appears for that file (should be blank w/ a "broken file" image and whatever alt="..." text you have in there to describe the file).

Dropzone gets imported at the top of the file—

import React, { Component, /* useCallback */ } from "react";
...
import Dropzone from "react-dropzone";
...

Styling/CSS Code for Thumbnails —

...
const thumbsContainer = {
  display: "flex",
  flexDirection: "row",
  flexWrap: "wrap",
  marginTop: 16
};

const thumb = {
  display: "inline-flex",
  borderRadius: 2,
  border: "1px solid #eaeaea",
  marginBottom: 8,
  marginRight: 8,
  width: 100,
  height: 100,
  padding: 4,
  boxSizing: "border-box"
};

const thumbInner = {
  display: "flex",
  minWidth: 0,
  overflow: "hidden"
};

const img = {
  display: "block",
  width: "auto",
  height: "100%"
};
...

The onDrop() callback function—

...
onDrop = (acceptedFiles, rejectedFiles) => {
  let files = acceptedFiles.map(async file => {
    let data = new FormData();
    data.append("file", file);

    let item = await axios
      .post("form/upload", data, {
        headers: {
          "X-Requested-With": "XMLHttpRequest",
          "Content-Type": "application/x-www-form-urlencoded"
        }
      })
      .then(response => {
        return Object.assign(file, {
          preview: URL.createObjectURL(file),
          filename: response.data.filename
        });
      })
      .catch(err => {
        let rejects = rejectedFiles.map(async file => {
          let data = new FormData();
          await data.append("file", file);

          console.log("There was an error while attempting to add your files:", err);
          console.log("The files that were rejected were:\n", rejects.join('\n'));
        })
      });
    return item;
  });
  Promise.all(files)
    .then(completed => {
      let fileNames = completed.map(function(item) {
        return item["filename"];
      });
      this.setState({ files: completed, fileNames: fileNames });
    })
    .catch(err => {
      console.log('DROPZONE ERROR:', err);
    });
  };
...

And the actual JSX code using <Dropzone> in the React.JS return()

...
<Form.Field>
  <label>Upload Files or Screenshots</label>
  <Dropzone accept={acceptedFileTypes} onDrop={this.onDrop}>
    {({ getRootProps, getInputProps, isDragActive }) => {
      return (
        <div
          {...getRootProps()}
          className={classNames("dropzone", {
            "dropzone--isActive": isDragActive
          })}
        >
          <input {...getInputProps()} />
          {isDragActive ? (
            <div>
              <div className="centered">
                <Icon name="cloud upload" size="big" />
              </div>
              <div className="centered">Drop Files Here.</div>
              <div className="centered">
                <Button className="drop-button">
                  Or Click to Select
                </Button>
              </div>
            </div>
          ) : (
            <div>
              <div className="centered">
                <Icon name="cloud upload" size="big" />
              </div>
              <div className="centered">
                Drag and Drop Supporting Files here to Upload.
              </div>
              <div className="centered">
                <Button className="drop-button">
                  Or Click to Select
                </Button>
              </div>
            </div>
          )}
        </div>
      );
    }}
  </Dropzone>
  <aside style={thumbsContainer}>{thumbs}</aside>
</Form.Field>
...

Expected behavior —

I would prefer all file types to generate a correct preview.

My Set-up —

  • MacBook Pro 13-Inch 2018
  • 2.7 GHz Intel Core i7 processor
  • 16 GB 2133 MHz LPDDR3 RAM memory
  • MacOS version 10.14.4
  • React & React-DOM versions 16.5.2
  • Node.JS version 10.15.2
  • yarn version 1.15.2
  • React Dropzone version 10.1.4
  • Google Chrome for Mac OS Browser version 74.0.3729.131 (Official Build) (64-bit)
  • Upload to an AWS EC2 instance container server.

... So, am I doing something wrong so that previews on other files besides Images aren't working? Is this not a feature for anything but Image files? Please advise.

A couple other questions —

  1. If Previews are not able to be generated for files other than Image files with react-dropzone, is there a way to have Image Files generate Previews, but all other files just list the File name being uploaded and such, as in the some of the other examples on your React Dropzone Web Site? If so, how do you switch between the two to have both as you drag & drop files onto the <Dropzone>?
  2. In our code, which, again, we copied nearly verbatim from your examples at the React Dropzone Web Site, we have noticed that when you drag and drop on set of files onto the <Dropzone>, and then attempt to drag and drop a second time, that wipes the first set of files from being uploaded and replaces them with the new files just dropped on the <Dropzone>. Is there a way to have the <Dropzone> cumulatively add files on each time they are dropped there, not just wipe out the previous files and replace with the new? If so, what are the steps to do that, please?
  3. Is there a way, in the case of having the blank box File Preview for all other files, to get rid of the "Broken File" icon?

I do appreciate any constructive reply, please. Thank you in advance.

Murrah answered 13/5, 2019 at 22:40 Comment(3)
It seems that this is somewhat browser-dependent. Safari generates previews for PDFs nicely, on both macOS and iOS.Monmouthshire
I would imagine unless you have a service or library to generate the previews you may be out of luck. I would work towards displaying a relevant icon for non image file types.Macneil
Have you tried the <object> tag, which is natively supported in HTML? This generates a preview for a variety of different mime types. As an alternative: you can create a preview for the types you support (e.g. all images and pdf) and for the rest, add a colored rectangle with one corner crestfallen and the file extension as text inside it?Heroin
I
0

Just to clarify, React-dropzone is not the one displaying the previews on your page. You are doing that using an image tag.
Now for your requirement to show a preview of all document file types, you will first need a parser for the required types, since a browser does not support reading a document(.doc/.xlsx) other than a pdf.
Something like react-doc-viewer.

With that you can read the uploaded files and use the package renderer to display the files

const docs = [
    { uri: "https://url-to-my-pdf.pdf" },
    { uri: require("./example-files/pdf.pdf") }, // Local File
];
... 
<DocViewer documents={docs} />;
  1. If you don't want to install a library to do this and just need to show the file names, you can just change the part of the code where you display the selected files.
const thumbs = files.map(file => {
  // Add other images types as needed
  if(["image/png", "image/jpeg"].includes(file.type)){
    return (
      <div style={thumb} key={file.name}>
        <div style={thumbInner}>
          <img
            src={file.preview}
            style={img}
            // Revoke data uri after image is loaded
            onLoad={() => { URL.revokeObjectURL(file.preview) }}
          />
        </div>
      </div>
    );
  }
  // Handling other file types
  return (
    <div style={thumb} key={file.name}>
      <div style={thumbInner}>
        <p>{file.name}</p
      </div>
    </div>
  );
});

You can add different conditions for different file mime types.
Full list of file mime types can be found in mdn docs

  1. If you want to append the data to the same list, you need to change the onDrop() function since in the Promise resolve part you are overwriting the list.
let fileNames = [...this.state.filenames, 
   ...completed.map(function(item) {
     return item["filename"];
})];
this.setState({files: [...this.state.files, ...completed], fileNames: fileNames });
  1. Part 1 should fix this issue
Isopropyl answered 24/7, 2023 at 7:6 Comment(1)
yeah, you're right, I didn't realize it used the MS server to view it, since I haven't actually used it. So I guess parsing local files is off the table.Isopropyl

© 2022 - 2024 — McMap. All rights reserved.