How to only dispatch FileList from dataTransfer af

2020-04-18 06:23发布

I'm using a Node package module called react-dropzone and React-Redux to allow users to drag a file onto the window and display the file info.

The react-dropzone component is giving me the FileList from the standard event.dataTransfer.files.

The problem I'm having is that when I dispatch that FileList to the Redux store, the File items aren't yet loaded in the FileList array, so when I dispatch the File data, I just get {"preview":"blob:file:///f30eafb8-8a77-4402-9111-379590b4a03f"}.

If I log the File items to the console, they show up just fine because their data is loaded by the time I look at it but I can't figure out how to only dispatch the file information after it's loaded.

Here's an example of what I see if I log one of the File items to the console from the FileList array.

{
  lastModified: 1473709614000,
  lastModifiedDate: Mon Sep 12 2016 12:46:54 GMT-0700 (PDT),
  name: "test_image.jpg",
  path: "/Users/mitchconquer/Desktop/test_image.jpg",
  preview: "blob:file:///0fe69686-125d-464a-a0a8-a42e497d3808",
  size: 41805,
  type: "image/jpeg",
  webkitRelativePath: ""
}

I believe the problem is just that the File object isn't fully loaded, but I can't figure out a way to only fire the action once the data is fully loaded. Could someone point me on the right path?

Here's my component that's using react-dropzone:

// FileDrop.js
import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router';
import styles from './Home.css';
import Dropzone from 'react-dropzone';

export default class FileDrop extends Component {
  static propTypes = {
    message: PropTypes.string.isRequired,
    setFile: PropTypes.func.isRequired
  };

  onDrop(files, event) {
    console.log({files});
    this.props.setFile(files);
  }

  render() {
    return (
      <div>
        <Dropzone onDropAccepted={this.onDrop.bind(this)} multiple={false} disablePreview={false}>
          <div>{this.props.message}</div>
        </Dropzone>
      </div>
    );
  }
}

...and here's my action file that has the setFile method that's used in the above onDrop method:

// actions/files.js
export const SET_FILE = 'SET_FILE';

// ACTION CREATORS

function _setFile(file) {
  return {
    type: SET_FILE,
    file
  };
}

// ACTION CREATOR CREATORS

export function setFile(file) {
  return _setFile(file[0]);
}

...and here's the reducer that's handling that action:

// reducers/files.js
import { SET_FILE } from '../actions/files';

export const initialState = [];

export default function files(state = initialState, action = {}) {
  switch (action.type) {
    case SET_FILE:
      const newState = state.slice();
      const file = action.file;
      newState.push(file);
      return newState;
    default:
      return state;
  }
}

3条回答
手持菜刀,她持情操
2楼-- · 2020-04-18 06:28

For those of you who are having this issue, for me I think the issue was that the Files in the Filelist weren't "plain ol' JavaScript objects" like I thought they were but in fact Files/Blobs.

I'm not sure of the details -- and would love it if someone would explain them -- but What I Think I Know is that the blob doesn't have keys like a standard JS object does so you can't just push it to the Redux store and see they key-value pairs of the properties I was expecting (lastModified, lastModifiedDate, name, path, preview, size, type, webkitRelativePath).

If you just looked at the blob, they only property was the preview because that was a property specifically set on the File/Blob by React Dropzone. To see the other properties, you have to call each one on the blob.

This is a simplified version of my solution below. I take in the Filelist array, create a new object to set the key-values on and iterate through each key and set it with its property on the newFile object. Finally I return that object. You can then set that object in your store and it will have the necessary data to work with the files.

onDrop(files) {
    const fileBlob = files[0];
    const newFile = {};
    _fileProperties.forEach(key => {
      newFile[key] = fileBlob[key];
    });

   return newFile;
}

const _fileProperties = [
  'lastModified',
  'lastModifiedDate',
  'name',
  'path',
  'preview',
  'size',
  'type',
  'webkitRelativePath'
];

There are probably lots of better ways of doing this and I'm sure my understanding is off so I'd still love it if someone would explain this better. In the meantime, this solution worked for me.

There are also some other places for more info such as MDN's File drag and drop article.

查看更多
萌系小妹纸
3楼-- · 2020-04-18 06:43

I had the same issue. I tried solutions provided by @Mitch Conquer and comment made by @Ishmael Smyrnow, but it returns the plain object instead of a File object. Also as discussed here , it's not recommended to store non-serializable objects in store. Therefore, what I did was, first converted file to base64 and store by just using FileReader.

const reader = new FileReader();
reader.readAsDataURL(file);

And then after converted back into file as solution provided by here.

export const dataURLtoFile = (dataurl, filename) => {
    var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
    bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
    while(n--){
        u8arr[n] = bstr.charCodeAt(n);
    }
    return new File([u8arr], filename, {type:mime});
}
查看更多
劫难
4楼-- · 2020-04-18 06:46

I tweaked the onDrop attribute as follows: onDrop={this.onDrop}, and added the file properties into my method - this worked for me.

I think the reason the above didn't quite work is because I have other input elements on in my component - the file upload is just part of a set I'm capturing.

Hope this helps!

查看更多
登录 后发表回答