Easy & Graceful Image Upload Previews


If your app has an image upload mechanism, one way to add some polish is to show a preview of the uploaded image. Fortunately, with FileReader this is super easy. Let's take a look!


Technologies Used

I've been using React and MobX a lot lately, and have really come to enjoy both (especially together). This demo will utilize those for keeping the view in sync with the state of a model, however any other ui framework could be used here. The key components we'll cover in this blog post are:

  • The file input, and handling its data on changes
  • The FormData object

A couple of techniques used in the demo that are MobX-specific, and good to know how to use if learning/using MobX, are:


The Input Element

The first thing we'll need to do is set up our input element to be of type file, and to tell it what types of files we accept. Also, we need to add our event handler for when changes occur to that input.

//inside the component's render()
<input type="file" accept="image/*" onChange={this.onImgFileChange} />

The event handler for the file's change event is pretty straight-forward. We need to check if there is a file (only allowing single upload in this scenario, but could easily modify for multiple), and then create a new FormData to house the data. If you're allowing file uploads, the way to send those to some backend is via FormData, so we'll use that here also. The actual image preview mechanism itself doesn't care about FormData, it just needs the file that was uploaded.

//event handler inside the component
onImgFileChange = evt => {
  const { files } = evt.target,
    hasFile = files && files.length > 0;
  if (hasFile) {
    //create a FormData to put the file and its name into
    //also, if we were to persist to some backend, FormData is what we'd send
    let fd = new FormData();
    fd.append('file', files[0]);
    fd.append('name', files[0].name);
    //here, do something with that form data
    //such as update the model with it, etc..
    this.uploadedImgFormData = fd;
  } else {
    //there was no file, so user must've cleared it
    //then we need to make sure our reference to
    //any prior FormData is cleared
    this.uploadedImgFormData = null;
  }
}

Get The Preview URL

Once we have a FormData with a file in it, we can use the FileReader to convert that to a data uri to use as an image's src. Here's what that looks like:

//assuming we have access to the FormData via same component
//however, the demo uses a model to manage that state
let {uploadImgFormData: fd} = this;
//check if browser support FileReader
if (window.FileReader && fd && fd.get('file')) {
  try {
    //See https://developer.mozilla.org/en-US/docs/Web/API/FileReader
    let fr = new FileReader();

    //here's where the magic happens - the FileReader can read a file input as a DataURL, 
    //which can then be used as an image's src
    fr.onload = action(() => this.imgPreviewUrl =  fr.result);
    fr.readAsDataURL(fd.get('file'));
  } catch (e) { }
}

Displaying The Preview

Once we have the preview url, we should display that as a preview. However, if there is no file uploaded, we should gracefully display some fallback. Let's use some simple getters to retrieve the preview url and file name, and feed those to an img element:

//getters on our component
get previewUrl() {
  return this.imgPreviewUrl || 'https://some/fallback.png';
}

get imgName() {
  let {uploadImgFormData: fd} = this;
  if (fd && fd.get('name')) {
    return fd.get('name');
  }
  return 'Select File';
}

Finally, we simply use those values in our img:

<img src={this.previewUrl} alt={this.imgName} />

Demo

Given that there's a demo, I just covered the most important parts here. To see the MobX stuff in action, check out the demo and the files in the sandbox. If you've not used MobX before, I highly recommend it as it really simplifies state management in React (and other) apps!

-Bradley