Search code examples
javascriptreactjsbrowserfilereaderarraybuffer

FileReader().readAsArrayBuffer(file) result gets stuck after first loaded file


I'm using this code to generate an md5 hash string to use as the file names in my storage. I read the file (image) contents and calculate the hash with the md5 library.

Somehow the result of the method readAsArrayBuffer(file) of the FileReader() API is getting stuck after I read the first file.

CodeSandBox: https://codesandbox.io/s/file-reader-md5so-nem6v

If I change the reading method to readAsText(file) the results seems normal and I get a different hash for a different file. But I was told that readAsArrayBuffer() makes more sense for image files. That's why I'm using it.

What could be happening? Do I need to clear some buffer?

GIF of the strange behavior:

enter image description here

EDIT: It turns out you need to create a typed array from the ArrayBuffer to work with the results from readAsArrayBuffer()

From: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer

The ArrayBuffer object is used to represent a generic, fixed-length raw binary data buffer.

It is an array of bytes, often refered to in other languages as a "byte array".

You cannot directly manipulate the contents of an ArrayBuffer; instead, you create one of the typed array objects or a DataView object which represents the buffer in a specific format, and use that to read and write the contents of the buffer.

import React, { useState } from "react";
import ReactDOM from "react-dom";
import md5 from "md5";

import "./styles.css";

function App() {
  const [images, setImages] = useState([{ md5Hash: null }, { md5Hash: null }]);

  function onFileSelect(event, index) {
    console.log("onFileSelect...");
    generateHashAndSave(event.target.files[0], index);
  }

  function generateHashAndSave(file, index) {
    console.log("Generate Hash...");
    const reader = new FileReader();
    reader.onload = event => {
      setImages(prevState => {
        const aux = Array.from(prevState);
        aux[index].md5Hash = md5(event.target.result);
        return aux;
      });
    };
    reader.readAsArrayBuffer(file);
  }

  const inputItems = images.map((item, index) => (
    <input
      key={index}
      type="file"
      onChange={event => onFileSelect(event, index)}
      accept=".jpg,.jpeg,.png,.gif"
    />
  ));

  return (
    <React.Fragment>
      {inputItems}
      <div><b>File 1 md5Hash: </b>{images[0].md5Hash}</div>
      <div><b>File 2 md5Hash: </b>{images[1].md5Hash}</div>
    </React.Fragment>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Solution

  • I am not 100% sure what's going on, but it looks like a problem with the md5() library you're using. If you explicitly turn the ArrayBuffer instance into a Uint8Array then things aren't weird:

      aux[index].md5Hash = md5(new Uint8Array(event.target.result));
    

    gives different hash values for different images.

    It's possible that your md5() does not in fact expect ArrayBuffer arguments, so in that case it's not really a "bug" I suppose.